国光文件上传靶场 WriteUp

1. 靶场部署

1
2
3
git clone https://github.com/sqlsec/upload-labs-docker.git
cd upload-labs-docker
docker compose up -d

共13个关卡,端口号为30001-30013。docker默认不遵循ufw防火墙规则,如果不希望端口暴露在0.0.0.0,可以手动更改docker-compose.yml文件,将每个端口监听在127.0.0.1上,然后配合nginx反向代理到特定IP上。或者使用ufw-docker项目解决docker端口不遵循ufw规则的问题:https://github.com/chaifeng/ufw-docker#%E8%A7%A3%E5%86%B3-ufw-%E5%92%8C-docker-%E7%9A%84%E9%97%AE%E9%A2%98

2. WP

2.1. 前端JS验证

alt text

2.1.1. 禁用浏览器JS

查看前端网页源代码可以发现是通过js进行文件验证的,禁用浏览器js即可绕过。

在开发者工具中使用快捷键ctrl + shift + p,输入javascript,在选项中选择停用JavaScript:

alt text

随后即可上传任意文件:

alt text

2.1.2. 浏览器JS调试

很小众的方法,在检测函数中打断点,然后修改变量值绕过验证。

首先在定义了允许用户上传的文件类型变量whitelist后面一行(第40行)加上断点:

alt text

接着选择php文件进行上传,当浏览器执行到断点处(第40行)时,会自动暂停,此时whitelist变量的值为".jpg",".png",".gif",".jpeg",到控制台中,将whitelist的值重新赋值为".php",然后按F8(或者点击继续执行按钮)继续执行,即可成功上传php文件:

alt text

2.1.3. BP改文件格式

将shell.php文件后缀名改为jpg(过前端验证),然后使用BP抓包,将文件格式再改回php:

alt text

放行后即可上传成功。

2.1.4. BP改返回包

刷新页面(重新请求index.php),使用BP抓请求包,右键选择拦截index.php请求的响应包:

alt text

接着放行此请求包,在返回的响应包中,增加php文件格式:

alt text

再到网页中查看源代码,可以发现php格式已经成功添加:

alt text

继续上传php文件,即可成功。

注意:如果前端验证的js代码是外部引入的(没有在index.php中直接定义),那么还需编辑BP的请求拦截规则,将第一条规则中过滤的js扩展名删除。这样才可拦截到js文件的请求。
alt text

2.2. .htaccess绕过

alt text

直接上传php文件会失败,查看网页源代码,没有前端校验,说明是后端校验,那么将shell.php文件后缀名改为jpg并用bp抓包改回php格式会上传失败。直接上传shell.jpg可以成功,但是文件不会被解析

分析后端源代码,可以发现后端是以黑名单的模式进行校验,即所有处在黑名单中的文件格式都会被禁止上传:

alt text

但仔细观察可以发现,黑名单中并没有.htaccess文件,.htaccess文件是Apache服务器特有的配置文件,默认启用,用于配置Apache服务器,具体用法可以参看此文章:https://xz.aliyun.com/t/8267?time__1311=n4%2BxnD0Dc7exyDjxYqGNWP4IrVnuYA5GCzReD

其中我们可以利用到的.htaccess文件指令是AddHandler(SetHandler指令也可利用,但稍微长一些,具体用法参看刚提到的文章),可以将特定的文件格式与特定的MIME类型进行绑定,例如将.jpg文件与application/x-httpd-php进行绑定,这样Apache服务器在解析.jpg文件时,就会将其当作php文件进行解析。

Apache全局默认的文件扩展名与MIME类型的映射关系可以在/etc/mime.types文件中查看:

alt text

可以看到有多个和php相关的文件格式,其中有2个格式没有定义在黑名单中:

1
2
#application/x-httpd-php-source                 phps
#application/x-httpd-php3-preprocessed php3p

但这2个格式一个是php源代码文件,一个是php3的预编译文件,他们都不会被解析成php脚本文件执行,因此上传了也没用,但是可以上传.htaccess文件,将没有定义在黑名单中的格式与application/x-httpd-php进行绑定,使Apache服务器解析这些格式的文件时,将其作为php脚本进行解析:

.htaccess

1
2
# 将 .phps .php3p .png .jpg .gif 当做 PHP 文件解析
AddType application/x-httpd-php .phps .php3p .png .jpg .gif

上传完.htaccess文件后,再上传shell.png文件,即可成功执行:

alt text

注意:如果这一题不分析源代码的话,则需要逐个文件格式进行测试:.htaccessphpphp3php4phtml
.htaccess文件即使上传成功,也不一定会生效,因为此功能可以被禁用,具体可以参看:https://www.cnblogs.com/johnhery/p/9831635.html

2.3. MIME校验

alt text

传php文件会失败,将php格式改为jpg格式后能上传,但是不解析,查看后端源代码,发现只有MIME校验:

alt text

1
2
3
4
image/jpeg
image/png
image/gif
image/jpg

上传shell.php文件,bp抓包将MIME类型由application/octet-stream改为image/jpeg,即可成功上传:

alt text

或者上传shell.jpg文件,bp抓包将文件格式改回php,也可以上传成功。原因是浏览器默认会根据文件格式来设置MIME类型,jpg格式匹配的是符合后端要求的MIME类型(image/jpeg),因此符合上传要求,而改格式为php是为了让后端将其解析为php文件,从而执行php代码。

2.4. 文件头+MIME校验

alt text

上传php文件失败,将文件格式改为jpg上传也失败,说明不是后端不校验mime类型或者除了mime类型以外还有其他校验,查看后端源代码,发现是mime校验+文件头校验

alt text

允许的mime类型以及文件头如下:

1
2
3
4
5
6
7
8
9
10
MIME类型:
image/jpeg
image/jpg
image/png
image/gif

文件头及对应的文件格式:
89504E47 -- .png
FFD8FFE0 -- .jpg
47494638 -- .gif

同时满足mime类型和文件头要求才能上传成功。上传shell.php文件,bp抓包将mime类型改为image/jpeg,然后在十六进制编辑器中插入89504E47

alt text

或者直接在请求体最前面写GIF89a(GIF8也行,对应47494638)也可绕过:

alt text

放行后即可成功上传:

alt text

alt text

图片马(copy pic.png/b + shell.php/a shell.png)也可绕过,只需抓包修改文件格式即可:

alt text

2.5. 文件名关键字移除

alt text

上传shell.php文件可成功,但是上传后的文件被重命名成了shell.导致无法被解析:

alt text

猜测是后端对文件名中的关键字php进行了移除(替换为空字符),尝试字符串双写绕过。

将shell.php文件名改为shell.pphphp,上传后文件名中的关键字php被移除,剩下的shell.php被解析成php文件执行:

alt text

2.6. 文件名关键字替换

alt text

上传shell.php文件可以成功,但是上传后的文件又被重命名成了shell.导致无法被解析。尝试字符串双写绕过,上传后的文件名变为了shell.p hp.(hp前面有个空格),仍然无法被解析:

alt text

说明后端没有直接移除关键字php,而是将php替换成了空格,查看题目给出的源码,可以得知后端使用的字符串替换函数是str_replace()函数,这个函数相较于上一题的str_ireplace()函数,它会严格区分大小写,因此phpPHP不会视为相同的字符串,而PHP并不在黑名单中,因此可以修改shell.php为shell.PHP绕过:

alt text

注意:只有在Windows系统上,大写的PHP格式会被视为和小写的php格式相同的MIME(application/x-httpd-php)进行解析处理,而在Linux系统上,大写的PHP格式会被视为普通的纯文本文件(text/plain)进行处理,因此如果题目环境是Linux系统的话,这个方法就失效了。但是这一题特意强调了人工修改成了Windows的特性(不区分大小写),因此上传的PHP文件能被解析执行。如果不能执行,则需手动进入靶机,下载vim,然后编辑/etc/apache2/sites-enabled/app.conf文件,在IfModule块中添加AddType application/x-httpd-php .PHP,然后重启apache服务(apache2ctl restart)即可生效。

2.7. %00截断路径

alt text

上传shell.php失败,重命名为shell.jpg可以成功上传,但不会解析。同时可以观察到提交文件后url地址会显示文件上传路径:

alt text

成功上传后的shell.jpg文件还会被重名:

alt text

那么可以通过修改上传路径来绕过。上传shell.jpg文件,bp抓包在请求路径后面添加1.php%00,放行即可上传成功:

alt text

访问上传后的文件会提示Not Found:

alt text

原因是1.php%00后面的%00被url解码成了空字符(标识字符串结束),致使原本应该拼接在路径后面的xxx.jpg被截断,实际的路径变成了upload/1.php,访问该路径即可成功解析:

alt text

注意: %00截断路径限制php版本为5.3.4以下,且需要php.ini中magic_quotes_gpc=Off,否则不会成功。更多分析推荐看这篇文章:http://www.admintony.com/%E5%85%B3%E4%BA%8E%E4%B8%8A%E4%BC%A0%E4%B8%AD%E7%9A%8400%E6%88%AA%E6%96%AD%E5%88%86%E6%9E%90.html

2.8. 空字符截断路径

alt text

这一关和上一关类似,上传后的shell.jpg也被重命名并且不会解析,但是上传路径不会显示在url中,因此需要通过bp抓包分析。

上传shell.jpg文件,bp抓包,可以看到请求表单中有一个名为save_path的字段,其值为./upload/

alt text

该字段的值就是文件上传路径,因此可以通过修改该字段的值来绕过。在./upload/后面加上%00,然后选中并右键对其进行url编码,使其成为空字符:

alt text

这里需要手动url编码的原因是,http请求体内的数据不同于请求头中的url查询参数,是不会自动进行url编码的。

之后放行请求,再到浏览器中访问1.php,即可成功解析:

alt text

2.9. 黑名单绕过

alt text

上传php文件会提示不支持该格式的后缀,并且其他不支持的后缀也显示了出来,包括 asp aspx php jsp:

alt text

只显示了这4中脚本格式而其他格式没有提及,那么说明使用的是黑名单过滤模式,只要不在黑名单中的脚本格式都可以上传。

能当作php脚本解析的格式除了php还有:phtml, pht, php3, php4, php5

alt text

上传以上几种格式的文件都可以成功解析:

alt text

2.10. 条件竞争漏洞

alt text

上传shell.php文件会提示只允许上传.jpg|.png|.gif类型文件,说明是白名单过滤模式,分析题目给出的后端源代码,可以发现,后端是先将上传的文件保存到UPLOAD_PATH,然后判断文件后缀是否在白名单中,如果在白名单中则将文件保留并重命名,否则删除改文件:

alt text

由于在删除文件之前,后端会先检查文件后缀是否在白名单中,而删除文件之前的检查是需要时间的(极短),可以利用这个时间差,大量且快速的上传webshell文件,并在删除文件之前快速访问该文件,使该文件被执行。webshell的代码如下:

1
<?php phpinfo(); fputs(fopen('1.php','w'),'<?php phpinfo(); eval($_REQUEST[1]);?>');?>

只要访问了该文件,就会显示phpinfo信息,并在当前目录下生成一个名为1.php的文件,内容为<?php phpinfo(); eval($_REQUEST[1]);?>

为了快速上传以及访问webshell,需要使用到bp的Intruder模块,首先上传webshell并用bp抓包,右键将其发送到Intruder:

alt text

接着进入Intruder模块,清空payload,设置payload类型为Null Payload,然后在payload配置中选择无限重复,最后点击开始攻击

alt text

alt text

攻击过程中,会不断上传webshell,此时如果进入靶机(docker容器),在/var/www/app/upload/目录下执行while true; do ls; sleep 1; done命令,有可能看到shell.php文件被上传,停止此命令后文件又会消失:

alt text

接着在浏览器中访问http://靶机IP:30010/upload/shell.php并用bp抓包,右键将其发送到Intruder模块,清空payload,设置payload类型为Null Payload,然后在payload配置中选择无限重复,最后点击开始攻击

攻击一段时间后,在攻击面板中选择按长度降序,查看最大的返回包渲染出的页面,如果使phpinfo信息,则说明shell.php文件被成功请求到了,并且1.php文件被成功生成:

alt text

到浏览器中访问http://靶机IP:30010/upload/1.php,成功解析1.php文件:

alt text

注意: 上面的演示我使用的是新版的burpsuite(蓝色logo),旧版本的burpsuite(黄色logo)的界面有些不同,具体用法可以参看这篇文章:https://cloud.tencent.com/developer/article/1933685

2.11. 特定块逃过二次渲染

alt text

上传php文件会提示文件不是标准的图片格式,将文件格式重命名为jpg同样上传失败:

alt text

点击查看提示按钮后地址栏会出现文件本地包含:

alt text

那么可以上传图片马,然后文件包含该图片马,从而执行图片马中的php代码。

上传使用copy命令制作的图片马mkbk.jpg:

alt text

然后文件包含该图片马,发现写入到图片中的phpinfo()函数没有被执行:

alt text

将上传后的图片马下载下来,和上传前的图片马进行文件属性比较,可以发现上传后的图片马的大小比上传前的图片马的大小要小,说明上传后的图片马被二次渲染了,而附加在图片中的php代码在二次渲染过程中被删除了,因此无法执行:

alt text

那么可以尝试将php代码放在图片的特定块中,使其不会被删除。使用010editor对上传前后的图片马进行对比(CTRL+M),灰色的部分是上传后没有变化的,红色的部分是上传后改变的,那么就可以将php代码放在灰色的部分中,这样在二次渲染过程中会被保留:

alt text

手动将代码插入到灰色的部分会导致图片损坏,从而无法被后端处理(会导致上传失败),因此需要使用脚本完成插入操作。这里使用jpg_payload脚本进行处理:https://github.com/BlackFan/jpg_payload

将渲染过图片马作为jpg_payload.php的参数进行执行:

1
php jpg_payload.php pic.jpg

如需修改payload,可以修改脚本中的$miniPayload变量(第28行),我修改的是$miniPayload = '<?php phpinfo();?>';

如果脚本运行后提示Something's wrong,建议换一张图片进行尝试。推荐使用国光制作的图片:https://image.3001.net/images/20201026/1603674733221.jpg

alt text

接着将生成的图片上传,然后文件包含该图片,成功执行phpinfo()函数:

alt text

GIF和png格式的二次渲染绕过推荐看国光原帖,已经非常详细了:https://www.sqlsec.com/2020/10/upload.html#GIF

2.12. /.绕过后缀检测

alt text

选择要上传的文件后,还需要填写文件上传后的命名,文件后缀格式中填写了php就会导致上传失败。由于题目提到了move_uploaded_file($temp_file, $img_path)函数,该函数的第2个参数$img_path是文件的保存位置,也是我们填写的文件名。而该函数会忽略掉文件名中的/.,因此可以在文件名后面加上/.来绕过文件后缀的检测。

选择shell.php文件,然后在文件命名处填写shell.php/.,接着将其上传并访问上传后的链接,成功执行phpinfo()函数:

alt text

2.13. 数组绕过

alt text

这一关涉及到php代码审计,不是纯文件上传绕过,因此这里只做简单演示,具体分析推荐看这篇文章:https://blog.csdn.net/weixin_63279914/article/details/136607391

选择shell.jpg文件,并填写文件名shell.php,上传并用bp抓包,将表单中的save_name改成save_name[0]

alt text

接着复制该表单,并将save_name[0]改成save_name[1],并将赋值改为jpg

alt text

然后发送该请求,成功上传shell.php文件,上传后的链接为:http://靶机IP:30013/upload/shell.php.jpg

删除该链接中的.jpg后缀并访问,成功执行phpinfo()函数:

alt text

3. 参考

https://www.sqlsec.com/2020/10/upload.html