利用 PHP 特性繞 WAF 測試
在測試繞過 WAF 執行遠程代碼之前,首先構造一個簡單的、易受攻擊的遠程代碼執行腳本,內容如圖:

第 6 行是一個比較明顯的命令執行代碼,第 3 行嘗試攔截 system、exec 或 passthru 等函數(PHP 中有許多其他函數可以執行系統命令,這三個是最常見的)。
這個腳本部署在 Cloudflare WAF 和 ModSecurity + OWASP CRS3 之后。對于第一個測試,嘗試讀取 passwd 的內容;
/cfwaf.php?code=system("cat /etc/passwd");

可以看到,被 CloudFlare 攔截了,我們可以嘗試使用未初始化變量的方式繞過,比如:
cat /etc$u/passwd

Cloudflare WAF 已被繞過,但是由于腳本檢查敏感函數,所以被腳本攔截,那么如何繞過腳本的函數檢測呢?我們看看關于字符串的 PHP 文檔:
https://secure.php.net/manual/en/language.types.string.php
PHP 字符串轉義序列:
- [0–7]{1,3} 八進制表示法的字符序列,它會自動溢出以適應一個字節(例如“\400”===“\000”)
- \x[0–9A-Fa-f]{1,2} 十六進制字符序列(例如“\x41”)
- \u{[0–9A-Fa-f]+} Unicode 代碼點序列,將作為該代碼點的 UTF-8 表示輸出到字符串(在 PHP 7.0.0 中添加)
不是每個人都知道 PHP 表示字符串的語法,而“PHP 變量函數”則成為我們繞過過濾器和規則的瑞士軍刀。
PHP變量函數
PHP 支持變量函數的概念。這意味著如果變量名后面附加了圓括號,PHP 將尋找與變量求值結果同名的函數,并嘗試執行它。除其他事項外,這可用于實現回調、函數表等。
這意味著語法如 $var(args); 和 "sting"(args; 等于 func(args); 。如果我可以通過使用變量或字符串來調用函數,則意味著我可以使用轉義序列而不是函數名。這里有一個例子:

第三種語法是十六進制符號的轉義字符序列,PHP 將其轉換為字符串“system”,然后使用參數“ls”轉換為函數系統。讓我們嘗試使用易受攻擊的腳本:

此技術不適用于所有 PHP 函數,變量函數不適用于 echo、print、unset()、isset()、empty()、include、require 。利用包裝函數將這些構造中的任何一個用作變量函數。
改進用戶輸入檢測
如果我從易受攻擊腳本的用戶輸入中排除雙引號和單引號等字符,會發生什么情況?即使不使用雙引號也可以繞過它嗎?讓我們試試:

正如您在第三行看到的,現在腳本阻止在 $_GET[code] 查詢字符串參數中使用“和”。我以前的有效負載現在應該被阻止:

幸運的是,在 PHP 中,我們并不總是需要引號來表示字符串。PHP 使您能夠聲明元素的類型,例如 $a = (string)foo; 在這種情況下,$a 包含字符串“foo”。此外,圓括號內沒有特定類型聲明的任何內容都被視為字符串:

在這種情況下,我們有兩種方法可以繞過新過濾器:第一種是使用類似 (system)(ls) 的方法;但是我們不能在代碼參數中使用“system”,所以我們可以像 (sy.(st).em)(ls); 一樣連接字符串。第二種是使用 $GET 變量。如果我發送像 ?a=system&b=ls&code=$GETa 這樣的請求;結果是:$GET[a] 將替換為字符串“system”,$GET[b] 將替換為字符串“ls”,我將能夠繞過所有過濾器!

讓我們嘗試使用第一個有效負載 (sy.(st).em)(whoami);

和第二個有效載荷 ?
?a=system&b=cat+/etc&c=/passwd&code=$\_GET[a]($\_GET[b].$\_GET[c]);

在這種情況下,沒有用,但您甚至可以在函數名稱和參數內部插入注釋(這可能有助于繞過阻止特定 PHP 函數名稱的 WAF 規則集)。以下所有語法均有效:
get_defined_functions 函數
此 PHP 函數返回一個多維數組,其中包含所有已定義函數的列表,包括內置(內部)函數和用戶定義函數。內部函數可以通過 $arr[“internal”] 訪問,用戶定義的函數可以使用 $arr[“user”] 訪問。例如:

這可能是另一種無需使用其名稱即可訪問系統功能的方法。如果我對“系統”進行 grep,我可以發現它的索引號并將其用作我的代碼執行的字符串:

顯然,這應該對我們的 Cloudflare WAF 和腳本過濾器有效:

字符數組
PHP 中的每個字符串都可以用作字符數組(幾乎像 Python 那樣),您可以使用語法 $string[2] 或 $string[-3] 引用單個字符串字符。這可能是另一種規避阻止 PHP 函數名稱的規則的方法。例如,使用這個字符串 $a=”elmsty/ “; 我可以編寫語法系統(“ls /tmp”);

如果幸運的話,您可以在腳本文件名中找到所需的所有字符。使用相同的技術,您可以使用類似的方法選擇所需的所有字符


OWASP CRS3
有了 OWASP CRS3,一切都變得更難了。首先,使用之前看到的技術,我只能繞過第一個偏執級別,這太神奇了!因為 Paranoia Level 1 只是我們可以在 CRS3 中找到的規則的一小部分,所以這個級別旨在防止任何誤報。對于 2 級偏執狂,由于規則 942430“受限 SQL 字符異常檢測(args):超出特殊字符數”,所有事情都變得困難。我能做的只是執行一個不帶參數的命令,如“ls”、“whoami”等。但我無法像使用 Cloudflare WAF 那樣執行類似 system(“cat /etc/passwd”) 的命令:

