HTTPOXY漏洞说明


这里有一个核心的背景是, 长久一来我们习惯了使用一个名为”http_proxy”的环境变量来设置我们的请求代理, 比如在命令行我们经常这么用:

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

包括我们自己, 也很有可能在日常的工作中写出如下的代码(我就曾经在写爬虫的时候写过):

    
    
    
    
  1. <?php
  2. $http_proxy = getenv("HTTP_PROXY");
  3. if ($http_proxy) {
  4.     $context = array(
  5.         'http' => array(
  6.             'proxy' => $http_proxy,
  7.             'request_fulluri' => true,
  8.         ),
  9.  
  10.     );
  11.     $s_context = stream_context_create($context);
  12. } else {
  13.     $s_context = NULL;
  14. }
  15. $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”)就变成可被控制的了. 那么如果你的所有类似的请求, 都会被代理到攻击者想要的地址,之后攻击者就可以伪造,监听,篡改你的请求了…

比如:

    
    
    
    
  1.  curl -H "Proxy:127.0.0.1:8000" http://host.com/httpoxy.php

所以, 这个漏洞要影响你, 有几个核心前提是:

  • 你的服务会对外请求资源
  • 你的服务使用了HTTP_PROXY(大写的)环境变量来代理你的请求(可能是你自己写,或是使用一些有缺陷的类库)
  • 你的服务跑在PHP的CGI模式下(cgi, php-fpm)

如果你没有满足上面的条件, 那么恭喜你,你不受此次漏洞影响 :) .

后记: 在微博上有同学提醒, 我可能把这个问题的影响潜意识的让大家觉得危害不大, 但实际上, 延伸一下: 所有HTTP_开头的环境变量在CGI下都是不可信的, 千万不要用于敏感操作, 另外一点就是, 我深刻的体会过, 做安全的同学想象力非常丰富, 虽然看似很小的一个点, 但到了安全的同学手里, 配合他们丰富的想象力, 强大的社工能力, 也是能做出巨大攻击效果的….

那知道了原理修复起来也很简单了, 以Nginx为例, 在配置中加入:

    
    
    
    
  1.    fastcgi_param HTTP_PROXY "";

所以建议, 即使你不受此次漏洞影响, 也应该加入这个配置.

而如果你是一个类库的作者,或者你因为什么原因没有办法修改服务配置, 那么你就需要在代码中加入对sapi的判断, 除非是cli模式, 否则永远不要相信http_proxy环境变量,

    
    
    
    
  1. <?php
  2. if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) {
  3.    //只有CLI模式下, HTTP_PROXY环境变量才是可控的
  4. }

就好比Guzzle的这个修复:Addressing HTTP_PROXY security vulnerability

补充: 从PHP5.5.38开始, getenv增加了第二个参数, local_only = false, 如果这个参数为true, 则只会从系统本地的环境变量表中获取, 从而修复这个问题, 并且默认的PHP将拦截HTTP_PROXY: fix


你可能感兴趣的:(HTTPOXY漏洞说明)