Skip navigation

Security Labs

1 Post tagged with the apache_reverse_proxy tag
6

Today Apache acknowledged another reverse proxy issue (CVE-2011-4317) which I discovered while creating a QualysGuard vulnerability signature for an older problem CVE-2011-3368. Depending on the reverse proxy configuration, the vulnerability could allow access to internal systems from the Internet.

 

While reviewing the patch for the older issue CVE-2011-3368, it appeared that it was still possible to make use of a crafted request that could exploit a fully patched Apache Web Server (Apache 2.2.21 with CVE-2011-3368 patch applied) to allow access to internal systems if the reverse proxy rules are configured incorrectly. I submitted an advisory and proof of concept to Apache and Apache made the issue public today.

 

For a good description of the older CVE-2011-3368 issue as well as how a reverse proxy works please check the excellent blog post by Context.

 

Here is a description of the new issue CVE-2011-4317 and its proof of concept.

 

Apache’s patch for CVE-2011-3368

 

The patch for CVE-2011-3368 (see Figure 1) is straight forward and self explanatory. The “server/protocol.c” file was modified. The patch looks at the request being sent and returns a HTTP 400 Response (Bad Request) if the URL does not begin with a forward slash “/”.

 

--- httpd-2.2.21/server/protocol.c
+++ httpd-2.2.21/server/protocol.c
@@ -640,6 +640,25 @@

     ap_parse_uri(r, uri);

+    /* RFC 2616: +     *   Request-URI    = "*" | absoluteURI | abs_path | authority
+     * +     * authority is a special case for CONNECT.  If the request is not
+     * using CONNECT, and the parsed URI does not have scheme, and
+     * it does not begin with '/', and it is not '*', then, fail
+     * and give a 400 response. */
+    if (r->method_number != M_CONNECT
+        && !r->parsed_uri.scheme    <-- A
+        && uri[0] != '/'
+        && !(uri[0] == '*' && uri[1] == '\0')) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                      "invalid request-URI %s", uri);
+        r->args = NULL;
+        r->hostname = NULL;
+        r->status = HTTP_BAD_REQUEST;
+        r->uri = apr_pstrdup(r->pool, uri);
+    }
+
     if (ll[0]) {
         r->assbackwards = 0;
         pro = ll;

Figure 1

 

 

This part of the code takes care of the issue for CVE-2011-3368. However; if you carefully look at the patch, it does not process URIs that have a scheme (see Figure 1, A). So, if a malformed URL request with a scheme was constructed, it would still be possible to bypass security and gain access to systems on the internal server provided that the reverse proxy rules were incorrectly configured.

 

Proof of Concepts

 

Target: Fully patched Apache Web Server (Version 2.2.21) with CVE-2011-3368 patch applied, with a reverse proxy set up and incorrectly configured RewriteRule/ProxyPassMatch rules.

 

Rewrite rules in httpd.conf:

RewriteRule ^(.*) http://10.40.2.159$1
ProxyPassMatch ^(.*) http://10.40.2.159$1

 

Example 1:

GET @localhost::<PORT> HTTP/1.0\r\n\r\n
where <PORT> is any port number being requested.

 

To demonstrate the proof of concept, Tomcat was set up to run on port 8880 on the internal server. Please note that any application could be running on any port on the internal server and a malicious user could use the PoC to request access to an application running on that port.

 

Access to internal web server can be possible by using a crafted request like:
GET @localhost::8880 HTTP/1.0\r\n\r\n

 

The screenshot below shows that a basic query with the crafted request (see Figure 2, B) to the target results in access to the page at 8880 (see Figure 2, C).

 


Figure 2

 

Upon receiving the request, Apache translates the URL by applying the rewrite rules. The "uri" extracted is ":8880" which gets appended, resulting in the URL

http://10.40.2.159:8880

The "uri" extracted in this case is everything following the first occurrence of the colon (:) in the request. Since the crafted request has 2 colons (::), the second colon is treated as being part of the URI.

 

 

To view the URI being extracted based on the rewrite rules, “RewriteLogLevel” was set to 3 in Apache configuration file. The rewrite translation logs get written to the log file. The first step to come up with the crafted request was to review the log file by sending different requests and studying how the rewrite translation was working. In the case of Example 1, since everything following the first colon (:) was being treated as the URI, a second colon was appended with a port number to see the response. The server treated the second “:” as being part of the URI and since there was an application already running on the port, it was possible to gain access to the page.

 

Example 2:

GET <random_string>:@<internalservername> HTTP/1.0\r\n\r\n
where <random_string> is any string, <internalservername> is the domain of an internal server being requested.

 

Access to internal web server can be possible by using a crafted request like:
GET qualys:@qqq.qq.qualys.com HTTP/1.0\r\n\r\n

 

The screenshot below shows that a basic query with the crafted request to an internal website (see Figure 3, D) allows access to the page remotely (see Figure 3, E).

 


Figure 3

 

Upon receiving the request, Apache translates the URL by applying the rewrite rules. The "uri" extracted is "@qqq.qq.qualys.com" which gets appended, resulting in the URL

http://10.40.2.159@qqq.qq.qualys.com

The "uri" extracted in this case is everything following the first occurrence of the colon (:) in the request. This is treated as <username>@<host> giving access to the internal <host> if no authentication is required.

 

Patch

Apache fixed this issue in Version 2.2.22.

Workaround

 

Until a patch is applied, configuring the reverse proxy rules in the apache configuration file correctly will prevent this issue from occurring. For example, in the above case, if the reverse proxy rules (RewriteRule or ProxyPassMatch directives in httpd.conf) are configured as follows, the proof of concept will not work.

 

RewriteRule ^(.*) http://10.40.2.159/$1
ProxyPassMatch ^(.*) http://10.40.2.159/$1