漏洞原文在這里, https://httpoxy.org/, 沒看懂的一定都是英語沒過6級的
這里有一個核心的背景是, 長久一來我們習慣了使用一個名為”http_proxy”的環境變量來設置我們的請求代理, 比如在命令行我們經常這么用:
- http_proxy=127.0.0.1:9999 wget http://www.laruence.com/
通過設置一個http_proxy的環境變量, 讓wget使用代理請求http://www.laruence.com/
有據可考的是, 這樣的設定最初來自1994年的CERN libwww 2.15, 我猜測大概是當時很多工具是基於這個類庫做的, 於是就慢慢成了一個既定標准吧. 只不過這些應用都要求http_proxy是全部小寫的, 還不足以造成今天這個漏洞.
但估計是因為環境變量習慣都是大寫的原因吧, 后來有的類庫開始支持大寫的HTTP_PROXY, 比如yum: https://www.centos.org/docs/5/html/yum/sn-yum-proxy-server.html
再后來很多的類庫, 各種語言的, 都開始支持這種配置, 有的支持大寫的, 有的支持小寫的, 還有的都支持.
- Guzzle(支持大寫):https://github.com/guzzle/guzzle/blob/10a49d5e1b8729c5e05cbdbf475b38b7099eb35e/src/Client.php#L167
- Artax(大寫, 小寫都支持): https://github.com/amphp/artax/blob/3e3eedafcecc82c3c86c3a00ca602b5efa9c2cfa/lib/HttpSocketPool.php#L26
包括我們自己, 也很有可能在日常的工作中寫出如下的代碼(我就曾經在寫爬蟲的時候寫過):
- <?php
- $http_proxy = getenv("HTTP_PROXY");
- if ($http_proxy) {
- $context = array(
- 'http' => array(
- 'proxy' => $http_proxy,
- 'request_fulluri' => true,
- ),
- );
- $s_context = stream_context_create($context);
- } else {
- $s_context = NULL;
- }
- $ret = file_get_contents("http://www.laruence.com/", false, $s_context);
那么問題來了, 在CGI(RFC 3875)的模式的時候, 會把請求中的Header, 加上HTTP_ 前綴, 注冊為環境變量, 所以如果你在Header中發送一個Proxy:xxxxxx, 那么PHP就會把他注冊為HTTP_PROXY環境變量, 於是getenv(“HTTP_PROXY”)就變成可被控制的了. 那么如果你的所有類似的請求, 都會被代理到攻擊者想要的地址,之后攻擊者就可以偽造,監聽,篡改你的請求了…
比如:
- curl -H "Proxy:127.0.0.1:8000" http://host.com/httpoxy.php
所以, 這個漏洞要影響你, 有幾個核心前提是:
- 你的服務會對外請求資源
- 你的服務使用了HTTP_PROXY(大寫的)環境變量來代理你的請求(可能是你自己寫,或是使用一些有缺陷的類庫)
- 你的服務跑在PHP的CGI模式下(cgi, php-fpm)
如果你沒有滿足上面的條件, 那么恭喜你,你不受此次漏洞影響 .
后記: 在微博上有同學提醒, 我可能把這個問題的影響潛意識的讓大家覺得危害不大, 但實際上, 延伸一下: 所有HTTP_開頭的環境變量在CGI下都是不可信的, 千萬不要用於敏感操作, 另外一點就是, 我深刻的體會過, 做安全的同學想象力非常豐富, 雖然看似很小的一個點, 但到了安全的同學手里, 配合他們豐富的想象力, 強大的社工能力, 也是能做出巨大攻擊效果的….
那知道了原理修復起來也很簡單了, 以Nginx為例, 在配置中加入:
- fastcgi_param HTTP_PROXY "";
所以建議, 即使你不受此次漏洞影響, 也應該加入這個配置.
而如果你是一個類庫的作者,或者你因為什么原因沒有辦法修改服務配置, 那么你就需要在代碼中加入對sapi的判斷, 除非是cli模式, 否則永遠不要相信http_proxy環境變量,
- <?php
- if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) {
- //只有CLI模式下, HTTP_PROXY環境變量才是可控的
- }
就好比Guzzle的這個修復:Addressing HTTP_PROXY security vulnerability
補充: 從PHP5.5.38開始, getenv增加了第二個參數, local_only = false, 如果這個參數為true, 則只會從系統本地的環境變量表中獲取, 從而修復這個問題, 並且默認的PHP將攔截HTTP_PROXY: fix