PHP 中的 RASP 實現流程分析

一、什麼是 RASP

RASP 全稱是 Runtime Application self-protection,即運行時應用自我保護,這是一種嵌入到應用程序內部,實時檢測來自外部的請求、輸入的技術。PHP 的 RASP 是通過 PHP 拓展的形式嵌入到PHP 的解釋器中。

RASP(Runtime Application self-protection)是一種在運行時檢測攻擊並且進行自我保護的一種技術。早在2012年,Gartner就開始關註RASP,惠普、WhiteHat Security等多傢國外安全公司陸續推出RASP產品,時至今日,惠普企業的軟件部門出售給瞭Micro Focus,RASP產品Application Defender隨之易主。而在國內,去年知道創宇KCon大會兵器譜展示瞭JavaRASP,前一段時間,百度開源瞭OpenRASP,去年年底,360的0kee團隊開始測試Skywolf,雖然沒有看到源碼和文檔,但它的設計思路或許跟RASP類似。而商業化的RASP產品有OneAPM的OneRASP和青藤雲的自適應安全產品。在國內,這兩傢做商業化RASP產品做得比較早。

二、PHP 拓展簡介

PHP 在不同的環境下有不同的工作模式,常見的有:命令行下的單進程模式和 Apache 環境下的多進程或者多線程模式。但不管是哪種模式下,都需要執行以下幾個流程:

圖1 單進程拓展執行流程

單進程模式下整個 PHP 的生命周期為:

圖2 單進程生命周期

多進程模式下的生命周期:

圖3 多進程生命周期

多進程下每個進程隻執行一次模塊初始化和模塊關閉,會不斷執行請求初始化-處理請求-請求關閉的過程。多線程模式下類似,隻是處理請求的是線程。

因此我們可以在模塊初始化(MINIT)或者請求初始化(RINIT)階段 hook,這樣每次處理請求的就是我們的業務邏輯函數,可以在我們的業務邏輯函數中對輸入、或者請求進行監測,判斷出異常後即可上報風險。

三、PHP 的 HOOK 實現

想要瞭解 hook 的方式,需要先看一下PHP對腳本的處理流程。

PHP 對腳本進行詞法分析和語分析後會生成 OPArray,也就是 OPCode 的數組,每個 OPCode 都代表一種不同的操作,名稱類似下面這種:

ZEND_ADD:執行兩個操作數的算術加法操作;

ZEND_EXIT:退出PHP執行;

Zend VM中則存在一個主分支循環(while(1)死循環),隻有當執行的 opcode 的 handler 的返回值是1(ZEND_VM_RETURN())時,這個循環才會結束,所以編譯器會為每個 PHP 腳本在最後添加一個 RETURN 的 OPCode。

以 ZEND_ADD 這個 opcode 為例,這個結構體裡包含有兩個操作數(op1和op2)、handler(函數指針)、result(運算後的結果)。Zend VM 會根據兩個操作數的類型,找到對應的handler,在源碼中對 ZEND_ADD 這個 opcode 的 handler 定義如下:

ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMP|VAR|CV, CONST|TMP|VAR|CV)
{
    USE_OPLINE
    zend_free_op free_op1, free_op2;
    SAVE_OPLINE();
    fast_add_function(&EX_T(opline->result.var).tmp_var,
        GET_OP1_ZVAL_PTR(BP_VAR_R),
        GET_OP2_ZVAL_PTR(BP_VAR_R) TSRMLS_CC);
    FREE_OP1();
    FREE_OP2();
    CHECK_EXCEPTION();
    ZEND_VM_NEXT_OPCODE();
}

函數後兩個參數分別代表 op1 和 op2 可接受的操作數類型。

處理工具會根據這個函數的定義,對 op1 和 op2 進行類型組合,生成16個處理特定類型的 handler函數。這些 handler 函數命名如下:

static int ZEND_FASTCALL  ZEND_ADD_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* handler code */ }
static int ZEND_FASTCALL  ZEND_ADD_SPEC_CONST_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* handler code */ }
static int ZEND_FASTCALL  ZEND_ADD_SPEC_CONST_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* handler code */ }
static int ZEND_FASTCALL  ZEND_ADD_SPEC_CONST_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* handler code */ }
static int ZEND_FASTCALL  ZEND_ADD_SPEC_TMP_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { /* handler code */ }
static int ZEND_FASTCALL  ZEND_ADD_SPEC_TMP_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)  { /* handler code */ }
......

規律為:static int ZEND_FASTCALL OPCode_SPEC_{OP1-TYPE}_{OP2-TYPE}_HANDLER

所以最終執行哪個 handler 是根據需要兩個操作數的類型決定的。

所以我們可以替換OPCode的handler,剛好源碼中有對應的接口zend_set_user_opcode_handler(zend_uchar opcode, user_opcode_handler_t handler)可供使用。

除瞭 OPCode 外,PHP 還有很多內置函數,比如 sprintf、 system、usort 等等,這些函數是沒有OPcode 的,但是這些函數都被保存在瞭全局函數表裡,可以通過 CG(function_table) 獲取,這些函數也有對應的handler函數指針,所以我們可以直接備份原先的 handler 後使用 function->internal_function.handler = new_handler 替換即可。

到此這篇關於PHP 中的 RASP 實現流程分析的文章就介紹到這瞭,更多相關PHP 的 RASP 實現內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: