国光文件上传靶场 WriteUp
国光文件上传靶场 WriteUp
lololowe1. 靶场部署
1 | git clone https://github.com/sqlsec/upload-labs-docker.git |
共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验证
2.1.1. 禁用浏览器JS
查看前端网页源代码可以发现是通过js进行文件验证的,禁用浏览器js即可绕过。
在开发者工具中使用快捷键ctrl + shift + p
,输入javascript
,在选项中选择停用JavaScript:
随后即可上传任意文件:
2.1.2. 浏览器JS调试
很小众的方法,在检测函数中打断点,然后修改变量值绕过验证。
首先在定义了允许用户上传的文件类型变量whitelist
后面一行(第40行)加上断点:
接着选择php文件进行上传,当浏览器执行到断点处(第40行)时,会自动暂停,此时whitelist
变量的值为".jpg",".png",".gif",".jpeg"
,到控制台中,将whitelist
的值重新赋值为".php"
,然后按F8(或者点击继续执行按钮)继续执行,即可成功上传php文件:
2.1.3. BP改文件格式
将shell.php文件后缀名改为jpg(过前端验证),然后使用BP抓包,将文件格式再改回php:
放行后即可上传成功。
2.1.4. BP改返回包
刷新页面(重新请求index.php),使用BP抓请求包,右键选择拦截index.php请求的响应包:
接着放行此请求包,在返回的响应包中,增加php文件格式:
再到网页中查看源代码,可以发现php格式已经成功添加:
继续上传php文件,即可成功。
注意:如果前端验证的js代码是外部引入的(没有在index.php中直接定义),那么还需编辑BP的请求拦截规则,将第一条规则中过滤的js扩展名删除。这样才可拦截到js文件的请求。
2.2. .htaccess绕过
直接上传php文件会失败,查看网页源代码,没有前端校验,说明是后端校验,那么将shell.php文件后缀名改为jpg并用bp抓包改回php格式会上传失败。直接上传shell.jpg可以成功,但是文件不会被解析。
分析后端源代码,可以发现后端是以黑名单的模式进行校验,即所有处在黑名单中的文件格式都会被禁止上传:
但仔细观察可以发现,黑名单中并没有.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
文件中查看:
可以看到有多个和php相关的文件格式,其中有2个格式没有定义在黑名单中:
1 | #application/x-httpd-php-source phps |
但这2个格式一个是php源代码文件,一个是php3的预编译文件,他们都不会被解析成php脚本文件执行,因此上传了也没用,但是可以上传.htaccess
文件,将没有定义在黑名单中的格式与application/x-httpd-php
进行绑定,使Apache服务器解析这些格式的文件时,将其作为php脚本进行解析:
.htaccess
1 | # 将 .phps .php3p .png .jpg .gif 当做 PHP 文件解析 |
上传完.htaccess
文件后,再上传shell.png文件,即可成功执行:
注意:如果这一题不分析源代码的话,则需要逐个文件格式进行测试:
.htaccess
、php
、php3
、php4
、phtml
.htaccess
文件即使上传成功,也不一定会生效,因为此功能可以被禁用,具体可以参看:https://www.cnblogs.com/johnhery/p/9831635.html
2.3. MIME校验
传php文件会失败,将php格式改为jpg格式后能上传,但是不解析,查看后端源代码,发现只有MIME校验:
1 | image/jpeg |
上传shell.php文件,bp抓包将MIME类型由application/octet-stream
改为image/jpeg
,即可成功上传:
或者上传shell.jpg文件,bp抓包将文件格式改回php,也可以上传成功。原因是浏览器默认会根据文件格式来设置MIME类型,jpg格式匹配的是符合后端要求的MIME类型(image/jpeg
),因此符合上传要求,而改格式为php是为了让后端将其解析为php文件,从而执行php代码。
2.4. 文件头+MIME校验
上传php文件失败,将文件格式改为jpg上传也失败,说明不是后端不校验mime类型或者除了mime类型以外还有其他校验,查看后端源代码,发现是mime校验+文件头校验:
允许的mime类型以及文件头如下:
1 | MIME类型: |
同时满足mime类型和文件头要求才能上传成功。上传shell.php文件,bp抓包将mime类型改为image/jpeg
,然后在十六进制编辑器中插入89504E47
:
或者直接在请求体最前面写GIF89a
(GIF8也行,对应47494638
)也可绕过:
放行后即可成功上传:
图片马(copy pic.png/b + shell.php/a shell.png
)也可绕过,只需抓包修改文件格式即可:
2.5. 文件名关键字移除
上传shell.php文件可成功,但是上传后的文件被重命名成了shell.
导致无法被解析:
猜测是后端对文件名中的关键字php
进行了移除(替换为空字符),尝试字符串双写绕过。
将shell.php文件名改为shell.pphphp
,上传后文件名中的关键字php
被移除,剩下的shell.php
被解析成php文件执行:
2.6. 文件名关键字替换
上传shell.php文件可以成功,但是上传后的文件又被重命名成了shell.
导致无法被解析。尝试字符串双写绕过,上传后的文件名变为了shell.p hp.
(hp前面有个空格),仍然无法被解析:
说明后端没有直接移除关键字php
,而是将php
替换成了空格,查看题目给出的源码,可以得知后端使用的字符串替换函数是str_replace()函数,这个函数相较于上一题的str_ireplace()函数,它会严格区分大小写,因此php
和PHP
不会视为相同的字符串,而PHP并不在黑名单中,因此可以修改shell.php为shell.PHP
绕过:
注意:只有在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截断路径
上传shell.php失败,重命名为shell.jpg可以成功上传,但不会解析。同时可以观察到提交文件后url地址会显示文件上传路径:
成功上传后的shell.jpg文件还会被重名:
那么可以通过修改上传路径来绕过。上传shell.jpg文件,bp抓包在请求路径后面添加1.php%00
,放行即可上传成功:
访问上传后的文件会提示Not Found:
原因是1.php%00
后面的%00
被url解码成了空字符(标识字符串结束),致使原本应该拼接在路径后面的xxx.jpg
被截断,实际的路径变成了upload/1.php
,访问该路径即可成功解析:
注意: %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. 空字符截断路径
这一关和上一关类似,上传后的shell.jpg也被重命名并且不会解析,但是上传路径不会显示在url中,因此需要通过bp抓包分析。
上传shell.jpg文件,bp抓包,可以看到请求表单中有一个名为save_path
的字段,其值为./upload/
:
该字段的值就是文件上传路径,因此可以通过修改该字段的值来绕过。在./upload/
后面加上%00
,然后选中并右键对其进行url编码,使其成为空字符:
这里需要手动url编码的原因是,http请求体内的数据不同于请求头中的url查询参数,是不会自动进行url编码的。
之后放行请求,再到浏览器中访问1.php
,即可成功解析:
2.9. 黑名单绕过
上传php文件会提示不支持该格式的后缀,并且其他不支持的后缀也显示了出来,包括 asp aspx php jsp:
只显示了这4中脚本格式而其他格式没有提及,那么说明使用的是黑名单过滤模式,只要不在黑名单中的脚本格式都可以上传。
能当作php脚本解析的格式除了php还有:phtml, pht, php3, php4, php5
上传以上几种格式的文件都可以成功解析:
2.10. 条件竞争漏洞
上传shell.php文件会提示只允许上传.jpg|.png|.gif类型文件,说明是白名单过滤模式,分析题目给出的后端源代码,可以发现,后端是先将上传的文件保存到UPLOAD_PATH,然后判断文件后缀是否在白名单中,如果在白名单中则将文件保留并重命名,否则删除改文件:
由于在删除文件之前,后端会先检查文件后缀是否在白名单中,而删除文件之前的检查是需要时间的(极短),可以利用这个时间差,大量且快速的上传webshell文件,并在删除文件之前快速访问该文件,使该文件被执行。webshell的代码如下:
1 | 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:
接着进入Intruder模块,清空payload,设置payload类型为Null Payload
,然后在payload配置中选择无限重复
,最后点击开始攻击
:
攻击过程中,会不断上传webshell,此时如果进入靶机(docker容器),在/var/www/app/upload/
目录下执行while true; do ls; sleep 1; done
命令,有可能看到shell.php文件被上传,停止此命令后文件又会消失:
接着在浏览器中访问http://靶机IP:30010/upload/shell.php
并用bp抓包,右键将其发送到Intruder模块,清空payload,设置payload类型为Null Payload
,然后在payload配置中选择无限重复
,最后点击开始攻击
。
攻击一段时间后,在攻击面板中选择按长度降序,查看最大的返回包渲染出的页面,如果使phpinfo信息,则说明shell.php文件被成功请求到了,并且1.php文件被成功生成:
到浏览器中访问http://靶机IP:30010/upload/1.php
,成功解析1.php文件:
注意: 上面的演示我使用的是新版的burpsuite(蓝色logo),旧版本的burpsuite(黄色logo)的界面有些不同,具体用法可以参看这篇文章:https://cloud.tencent.com/developer/article/1933685
2.11. 特定块逃过二次渲染
上传php文件会提示文件不是标准的图片格式,将文件格式重命名为jpg同样上传失败:
点击查看提示按钮后地址栏会出现文件本地包含:
那么可以上传图片马,然后文件包含该图片马,从而执行图片马中的php代码。
上传使用copy命令制作的图片马mkbk.jpg:
然后文件包含该图片马,发现写入到图片中的phpinfo()函数没有被执行:
将上传后的图片马下载下来,和上传前的图片马进行文件属性比较,可以发现上传后的图片马的大小比上传前的图片马的大小要小,说明上传后的图片马被二次渲染了,而附加在图片中的php代码在二次渲染过程中被删除了,因此无法执行:
那么可以尝试将php代码放在图片的特定块中,使其不会被删除。使用010editor对上传前后的图片马进行对比(CTRL+M),灰色的部分是上传后没有变化的,红色的部分是上传后改变的,那么就可以将php代码放在灰色的部分中,这样在二次渲染过程中会被保留:
手动将代码插入到灰色的部分会导致图片损坏,从而无法被后端处理(会导致上传失败),因此需要使用脚本完成插入操作。这里使用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
接着将生成的图片上传,然后文件包含该图片,成功执行phpinfo()函数:
GIF和png格式的二次渲染绕过推荐看国光原帖,已经非常详细了:https://www.sqlsec.com/2020/10/upload.html#GIF
2.12. /.绕过后缀检测
选择要上传的文件后,还需要填写文件上传后的命名,文件后缀格式中填写了php就会导致上传失败。由于题目提到了move_uploaded_file($temp_file, $img_path)函数,该函数的第2个参数$img_path是文件的保存位置,也是我们填写的文件名。而该函数会忽略掉文件名中的/.
,因此可以在文件名后面加上/.
来绕过文件后缀的检测。
选择shell.php文件,然后在文件命名处填写shell.php/.
,接着将其上传并访问上传后的链接,成功执行phpinfo()函数:
2.13. 数组绕过
这一关涉及到php代码审计,不是纯文件上传绕过,因此这里只做简单演示,具体分析推荐看这篇文章:https://blog.csdn.net/weixin_63279914/article/details/136607391
选择shell.jpg文件,并填写文件名shell.php
,上传并用bp抓包,将表单中的save_name
改成save_name[0]
:
接着复制该表单,并将save_name[0]
改成save_name[1]
,并将赋值改为jpg
:
然后发送该请求,成功上传shell.php文件,上传后的链接为:http://靶机IP:30013/upload/shell.php.jpg
删除该链接中的.jpg
后缀并访问,成功执行phpinfo()函数: