WACON2023

本文最后更新于:8 个月前

WACONCTF2023

warmup-revenge

比赛结束比较久了才回头复盘,自己起了个环境,大致有注册登录,以及留言板和给别人发消息的功能。先黑盒看看,注册时先试着写一些危险字符被直接ban了Invalid Value

测了下留言模块发现尖括号直接被转义了,留言模块还有上传文件的部分,本地起环境的时候观察upload目录发现上传的文件名字已经变成了一串哈希值,点击download会指向download.php,并传递了一个idx点击下载原先上传的文件

黑盒测试到这,这道题原先是一道xss,

翻翻源代码吧,note和board还有用户名等绝大多数用户显示输入的地方都被过滤过了,

1
2
3
4
5
6
7
8
9
10
//function.php
<?php
....
function clean_html($str) {
return htmlspecialchars($str, ENT_QUOTES);
}

function clean_sql($str) {
return addslashes($str);
}

因此希望实现xss,就得找到一个点不经过上面的这两个函数处理的地方

漏洞点出在对用户文件上传的部分,虽然上传后的文件被重命名为一个随机十六进制串(直接访问无法造成解析),但是可以注意到原先文件的名字被完整保留了下来并且被存入数据库,作为之后download文件下来的文件名

看看download.php的逻辑,通过参数中idx的值检索数据库中对应的文件,然后通过Content-disposition响应头的设置来正确下载文件:

1
2
3
4
5
6
7
8
9
10
11
$file = fetch_row('board', $query);
if(!$file) die('Not Found');

$filepath = $file['file_path'];
$original = $file['file_name'];
...
header("content-length: ".filesize($filepath));
header("content-disposition: attachment; filename=\"$original\"");
header("content-transfer-encoding: binary");
...
$fp = fopen($filepath, 'rb');

有没有办法能绕过这里的Content-disposition头的设置呢?变成inline,或者是没掉?这篇文章介绍了一种很好的方式来进行CLRF注入,以绕过Content-disposition,关于这道题的CLRF注入在之后会进行复现:

https://markitzeroday.com/xss/bypass/2018/04/17/defeating-content-disposition.html

但是这篇文章的姿势似乎在这道题里并不适用,因为并没有用户能够控制的Content-Type头,出现在Content-disposition头之前的设置头部是content-length,但它是一个数值,不过好在php在header()对响应头的设置中,规定了一旦出现\n或者\r会抛出

1
Header may not contain more than a single header, new line detected

包含\0就会抛出

1
Header may not contain NUL bytes

(参考php源码https://github.com/php/php-src/blob/master/main/SAPI.c#L7)

虽然一开始是为了安全性如此设置,但是在这里,它反而是让我们进行绕过的一把利剑,抛出异常后,该响应头就无法产生,因此便可以顺利绕过Content-disposition: attachment

再回到源代码,header的设置是插入$original,这里$original就是原原本本的我们上传的文件的名字,因此一旦它包含\n\r或者\0就会直接让该语句失效从而达到解析原来上传文件的效果

1
header("content-disposition: attachment; filename=\"$original\"");

上传文件的地方设置一个一字节的文件名

在hex模式中修改a的值为0d(\r的编码),然后发送

去用对应idx访问download.php,几乎是快成功了!

CSP绕过:

这道题的CSP如下,设置了script-src:self但是没有设置script的unsafe-inline,(那个unsafe-inline是css的,一开始看错了疑惑了半天,哭),因此只能执行来源于可受信任来源的资源,这里也就是当前源

1
Content-Security-Policy: default-src 'self'; style-src 'self' https://stackpath.bootstrapcdn.com 'unsafe-inline'; script-src 'self'; img-src data:

绕过也很简单,只需要先上传一个文件来让另一个文件的script标签来加载就行了:

得到对应idx为19

然后再上传一个文件用script标签来引用上一个js文本

这样一来就成功实现了xss了