httpoxy is a set of vulnerabilities that affect application code running in CGI, or CGI-like environments. It comes down to a simple namespace conflict:
Proxy
header from a request into the environment variables as HTTP_PROXY
HTTP_PROXY
is a popular environment variable used to configure an outgoing proxyThis leads to a remotely exploitable vulnerability. If you’re running PHP or CGI, you should block the Proxy
header.
Here’s how.
httpoxy is a vulnerability for server-side web applications. If you’re not deploying code, you don’t need to worry.
If a vulnerable HTTP client makes an outgoing HTTP connection, while running in a server-side CGI application, an attacker may be able to:
httpoxy is extremely easy to exploit in basic form. And we expect security researchers to be able to scan for it quickly. Luckily, if you read on and find you are affected, easy mitigations are available.
httpoxy was disclosed in mid-2016. If you’re reading about it now for the first time, you can probably relax and take your time reading about this quaint historical bug that hopefully no longer affects any of the applications you maintain. But you should verify that to your own satisfaction.
The content below this point reflects the original disclosure, and I’ll be leaving the site up and mostly unchanged, other than noting fix versions where I can. I guess I’m just saying: the time for urgency was last year.
A few things are necessary to be vulnerable:
HTTP_PROXY
becomes a real or emulated environment variableHTTP_PROXY
, and configures it as the proxyFor example, the confirmed cases we’ve found so far:
Language | Environment | HTTP client |
---|---|---|
PHP | php-fpm mod_php |
Guzzle 4+ Artax |
Python | wsgiref.handlers.CGIHandler twisted.web.twcgi.CGIScript |
requests |
Go | net/http/cgi | net/http |
But obviously there may be languages we haven’t considered yet. CGI is a common standard, and
HTTP_PROXY
seems to be becoming more popular over time. Take the below as a sample of the most
commonly affected scenarios:
>=4.0.0rc2,<6.2.1
are vulnerable, Guzzle 3 and below is not.So, for example, if you are using a Drupal module that uses Guzzle 6.2.0
and makes an outgoing HTTP request (for example,
to check a weather API), you are vulnerable to the request that plugin makes being “httpoxied”.
wsgiref.handlers.CGIHandler
os.environ['HTTP_PROXY']
, without checking if CGI is in use2.7.13
, 3.4.6
, 3.5.3
, 3.6.0
(see the Python advisory)net/http/cgi
package.
net/http/fcgi
package, by comparison, does not set actual environment variables, so it is not vulnerablenet/http
will trust and use HTTP_PROXY
for outgoing requests, without checking if CGI is in use1.7rc3
, all stable versions of >=1.7
The best immediate mitigation is to block Proxy
request headers as early as possible, and before they hit your
application. This is easy and safe.
Proxy
header is undefined by the IETF, and isn’t listed on the
IANA’s registry of message headers. This means
there is no standard use for the header at all; not even a provisional use-case.Proxy
header is safe!How you block a Proxy
header depends on the specifics of your setup. The earliest convenient place to block the header
might be at a web application firewall device, or directly on the webserver running Apache or NGINX. Here are a few of
the more common mitigations:
Use this to block the header from being passed on to PHP-FPM, PHP-PM etc.
fastcgi_param HTTP_PROXY "";
In FastCGI configurations, PHP is vulnerable (but many other languages that use NGINX FastCGI are not).
For specific NGINX coverage, we recommend that you read the official NGINX blog post on this vulnerability. The blog post provides a graphic depiction of how httpoxy works and more extensive mitigation information for NGINX.
For specific Apache coverage (and details for other Apache software projects like Tomcat), we strongly recommend you read the Apache Software Foundation’s official advisory on the matter. The very basic mitigation information you’ll find below is covered in much greater depth there.
If you’re using Apache HTTP Server with mod_cgi
, languages like Go and Python may be vulnerable (the HTTP_PROXY
env var
is “real”). And mod_php
is affected due to the nature of PHP. If you are using mod_headers, you can unset the
Proxy
header before further processing with this directive:
RequestHeader unset Proxy early
Example for using this in .htaccess
files:
<IfModule mod_headers.c>
RequestHeader unset Proxy
</IfModule>
If you are using mod_security, you can use a SecRule
to deny traffic with a Proxy
header. Here’s an example,
vary the action to taste, and make sure SecRuleEngine
is on. The 1000005 ID has been assigned to this issue.
SecRule &REQUEST_HEADERS:Proxy "@gt 0" "id:1000005,log,deny,msg:'httpoxy denied'"
Finally, if you’re using Apache Traffic Server, it’s not itself affected, but you can use it to strip the Proxy
header; very helpful
for any services sitting behind it. Again, see the ASF’s guidance,
but one possible configuration is:
Within plugin.config
, inside the configuration directory (e.g. /usr/local/etc/trafficserver
or /etc/trafficserver
),
add the following directive:
header_rewrite.so strip_proxy.conf
Add the following to a new file named strip_proxy.conf
in the same directory:
cond %{READ_REQUEST_HDR_HOOK}
rm-header Proxy
This will strip the header off requests:
http-request del-header Proxy
If your version of HAProxy is old (i.e. 1.4
or earlier), you may not have the http-request del-header
directive.
If so, you must also take care that headers are stripped from requests served after the first one over an HTTP 1.1
keep-alive connection. (i.e. take special note of the limitation described in the first paragraph of the 1.4 “header
manipulation” documentation)
For Varnish, the following should unset the header. Add it to the pre-existing vcl_recv section:
sub vcl_recv {
[...]
unset req.http.proxy;
[...]
}
For relayd, the following should remove the header. Add it to a pre-existing filter:
http protocol httpfilter {
match request header remove "Proxy"
}
To reject requests containing a Proxy
header
Create /path/to/deny-proxy.lua
, read-only to lighttpd, with the content:
if (lighty.request["Proxy"] == nil) then return 0 else return 403 end
Modify lighttpd.conf
to load mod_magnet
and run the above lua code:
server.modules += ( "mod_magnet" )
magnet.attract-raw-url-to = ( "/path/to/deny-proxy.lua" )
To strip the Proxy
header from the request, add the following to lighttpd.conf
:
req_header.remove "Proxy";
For detailed information about mitigating httpoxy on IIS, you should head to the official Microsoft article KB3179800, which covers the below mitigations in greater detail.
Also important to know: httpoxy does not affect any Microsoft Web Frameworks, e.g. not ASP.NET nor Active Server Pages.
But if you have installed PHP or any other third party framework on top of IIS, we recommend applying mitigation steps
to protect from httpoxy attacks. You can either block requests containing a Proxy
header, or clear the header. (The header
is safe to block, because browsers will not generally send it at all).
To block requests that contain a Proxy
header (the preferred solution), run the following command line.
appcmd set config /section:requestfiltering /+requestlimits.headerLimits.[header='proxy',sizelimit='0']
Note: appcmd.exe
is not typically in the path and can be found in the %systemroot%\system32\inetsrv
directory
To clear the value of the header, use the following URL Rewrite rule:
<system.webServer>
<rewrite>
<rules>
<rule name="Erase HTTP_PROXY" patternSyntax="Wildcard">
<match url="*.*" />
<serverVariables>
<set name="HTTP_PROXY" value="" />
</serverVariables>
<action type="None" />
</rule>
</rules>
</rewrite>
</system.webServer>
Note: URL Rewrite is a downloadable add-in for IIS and is not included in a default IIS installation.
You can block any request containing a Proxy
header (or ban the sending client) via the UrlToolkit:
UrlToolkit {
ToolkitID = block_httpoxy
Header Proxy .* DenyAccess
}
See more information at the hiawatha blog
Upgrade to >= 5.0.19
or >= 5.1.7
to mitigate. You can do this manually with one of these commands, or you’ll get
an upgrade notification soon.
/usr/local/lsws/admin/misc/lsup.sh -v 5.0.19 # or
/usr/local/lsws/admin/misc/lsup.sh -v 5.1.7
See more information at the litespeed blog
Upgrade to >= 2.0.2
and add this to your configuration:
setenv:
HTTP_PROXY: ""
More information can be found in this GitHub pull request.
Please let us know of other places where httpoxy is found. We’d be happy to help you communicate fixes for your platform, server or library if you are affected. Contact contact@httpoxy.org to let us know. Or create a PR or issue against the httpoxy-org repo in GitHub.
Userland PHP fixes don’t work. Don’t bother:
unset($_SERVER['HTTP_PROXY'])
does not affect the value returned from getenv()
, so is not an effective
mitigationputenv('HTTP_PROXY=')
does not work either (to be precise: it only works if that value is coming from an
actual environment variable rather than a header – so, it cannot be used for mitigation)CGI_HTTP_PROXY
to set the proxy for a CGI application’s internal requests, if necessary
HTTP_PROXY
, but you must assert that CGI is not in usePHP_SAPI == 'cli'
Otherwise, a simple check is to not trust HTTP_PROXY
if REQUEST_METHOD
is also set. RFC 3875 seems to require
this meta-variable:
The
REQUEST_METHOD
meta-variable MUST be set to the method which should be used by the script to process the request
HTTP_PROXY
Under CGITo put it plainly: there is no way to trust the value of an HTTP_
env var in a CGI environment. They cannot be
distinguished from request headers according to the specification. So, any usage of HTTP_PROXY
in a CGI context is
suspicious.
If you need to configure the proxy of a CGI application via an environment variable, use a variable name that will
never conflict with request headers. That is: one that does not begin with HTTP_
. We strongly recommend you go for
CGI_HTTP_PROXY
. (As seen in Ruby and libwww-perl’s mitigations for this issue.)
CLI-only code may safely trust $_SERVER['HTTP_PROXY']
or getenv('HTTP_PROXY')
. But bear in mind that code written
for the CLI context often ends up running in a SAPI eventually, particularly utility or library code. And, with open
source code, that might not even be your doing. So, if you are going to rely on HTTP_PROXY
at all, you should guard
that code with a check of the PHP_SAPI
constant.
A defense-in-depth strategy that can combat httpoxy (and entire classes of other security problems) is to severely restrict the outgoing requests your web application can make to an absolute minimum. For example, if a web application is firewalled in such a way that it cannot make outgoing HTTP requests, an attacker will not be able to receive the “misproxied” requests (because the web application is prevented from connecting to the attacker).
And, of course, another defense-in-depth strategy that works is to use HTTPS for internal requests, not just for
securing your site’s connections to the outside world. HTTPS requests aren’t affected by HTTP_PROXY
.
Using PHP as an example, because it is illustrative. PHP has a method called getenv()
1.
There is a common vulnerability in many PHP libraries and applications, introduced by confusing
getenv
for a method that only returns environment variables. In fact, getenv() is closer to the
$_SERVER
superglobal: it contains both environment variables and user-controlled data.
Specifically, when PHP is running under a CGI-like server, the HTTP request headers (data supplied
by the client) are merged into the $_SERVER
superglobal under keys beginning with HTTP_
. This is
the same information that getenv
reads from.
When a user sends a request with a Proxy
header, the header appears to the PHP application as getenv('HTTP_PROXY')
.
Some common PHP libraries have been trusting this value, even when run in a CGI/SAPI environment.
Reading and trusting $_SERVER['HTTP_PROXY']
is exactly the same vulnerability, but tends to happen much less often
(perhaps because of getenv’s name, perhaps because the semantics of the $_SERVER
superglobal are better understood among
the community).
Note that these examples require deployment into a vulnerable environment before there is actually a vulnerability
(e.g. php-fpm, or Apache’s ScriptAlias
)
$client = new GuzzleHttp\Client();
$client->get('http://api.internal/?secret=foo')
from wsgiref.handlers import CGIHandler
def application(environ, start_response):
requests.get("http://api.internal/?secret=foo")
CGIHandler().run(application)
cgi.Serve(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
res, _ := http.Get("http://api.internal/?secret=foo")
// [...]
More complete PoC repos (using Docker, and testing with an actual listener for the proxied request) have been prepared under the httpoxy Github organization.
Under the CGI spec, headers are provided mixed into the environment variables. (These are formally known as “Protocol-Specific Meta-Variables”2). That’s just the way the spec works, not a failure or bug.
The goal of the code, in most of the vulnerabilities, is to find the correct proxy to use, when auto-configuring a
client for the internal HTTP request made shortly after. This task in Ruby could be completed by
the find_proxy
method of URI::Generic
, which notes:
http_proxy
andHTTP_PROXY
are treated specially under the CGI environment, becauseHTTP_PROXY
may be set by Proxy: header. SoHTTP_PROXY
is not used.http_proxy
is not used too if the variable is case insensitive.CGI_HTTP_PROXY
can be used instead.
Other instances of the same vulnerability are present in other languages. For example, when
using Go’s net/http/cgi
module, and deploying as a CGI application. This indicates the vulnerability
is a standard danger in CGI environments.
This bug was first discovered over 15 years ago. The timeline goes something like:
The issue is discovered in libwww-perl and fixed. Reported by Randal L. Schwartz. 3
The issue is discovered in curl, and fixed there too (albeit probably not for Windows). Reported by Cris Bailiff. 4
In implementing HTTP_PROXY
for Net::HTTP
, the Ruby team notice and avoid the potential issue. Nice work Akira Tanaka! 5
The issue is mentioned on the NGINX mailing list. The user humbly points out the issue: “unless I’m missing something, which is very possible”. No, Jonathan Matthews, you were exactly right! 6
The issue is mentioned on the Apache httpd-dev mailing list. Spotted by Stefan Fritsch. 7
Scott Geary, an engineer at Vend, found an instance of the bug in the wild. The Vend security team found the vulnerability was still exploitable in PHP, and present in many modern languages and libraries. We started to disclose to security response teams.
So, the bug was lying dormant for years, like a latent infection: pox. We imagine that many people may have found the issue over the years, but never investigated its scope in other languages and libraries. If you’ve found a historical discussion of interest that we’ve missed, let us know. You can contact contact@httpoxy.org or create an issue against the httpoxy-org repo.
httpoxy has a number of CVEs assigned. These cover the cases where
Proxy
header available in such a way that the application cannot tell whether
it is a real environment variable, orHTTP_PROXY
environment variable by default in a CGI environment (but only where that application should have been
able to tell it came from a request)The assigned CVEs so far:
We suspect there may be more CVEs coming for httpoxy, as less common software is checked over. If you want to get a CVE assigned for an httpoxy issue, there are a couple of options:
We’ll be linking to official announcements from affected teams here, as they become available.
Over the past two weeks, the Vend security team worked to disclose the issue responsibly to as many affected parties as we could. We’d like to thank the members of:
There’s an extra page with some meta-discussion on the whole named disclosure thing and contact details. The content on this page is licensed as CC0 (TL;DR: use what you like, no permission/attribution necessary).
I’ve put together some more opinionated notes on httpoxy on my Medium account.
Regards,
Dominic Scheirlinck and the httpoxy disclosure team
July 2016
You can email contact@httpoxy.org, or, for corrections or suggestions, feel free to open an issue on the httpoxy-org repo.
Page updated at 2017-06-23 14:17 UTC
The fix applied correctly handles cases with case-insensitive environment variables. libwww-perl-5.51 announcement ↩
The fix applied to Curl does not correctly handle cases with case-insensitive environment variables - it specifically mentions the fix would not be enough for “NT” (Windows). The commit itself carries the prescient message “since it might become a security problem.” ↩
The mitigation in Ruby, like that for libwww-perl, correctly handles case-insensitive environment variables. ↩
The NGINX mailing list even had a PHP-specific explanation. ↩