You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ni...@apache.org on 2007/10/27 01:07:23 UTC

svn commit: r588791 - in /httpd/httpd/trunk: CHANGES docs/manual/mod/mod_proxy.xml modules/proxy/mod_proxy.c modules/proxy/mod_proxy.h modules/proxy/mod_proxy_http.c

Author: niq
Date: Fri Oct 26 16:07:22 2007
New Revision: 588791

URL: http://svn.apache.org/viewvc?rev=588791&view=rev
Log:
mod_proxy: add "nocanon" keyword to ProxyPass, to suppress
URI-canonicalisation in a reverse proxy.
PR 41798

Modified:
    httpd/httpd/trunk/CHANGES
    httpd/httpd/trunk/docs/manual/mod/mod_proxy.xml
    httpd/httpd/trunk/modules/proxy/mod_proxy.c
    httpd/httpd/trunk/modules/proxy/mod_proxy.h
    httpd/httpd/trunk/modules/proxy/mod_proxy_http.c

Modified: httpd/httpd/trunk/CHANGES
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CHANGES?rev=588791&r1=588790&r2=588791&view=diff
==============================================================================
--- httpd/httpd/trunk/CHANGES [utf-8] (original)
+++ httpd/httpd/trunk/CHANGES [utf-8] Fri Oct 26 16:07:22 2007
@@ -2,11 +2,15 @@
 Changes with Apache 2.3.0
 [ When backported to 2.2.x, remove entry from this file ]
 
+  *) mod_proxy: add "nocanon" keyword to ProxyPass, to suppress
+     URI-canonicalisation in a reverse proxy.
+     PR 41798 [Nick Kew]
+
   *) core; scoreboard: ap_get_scoreboard_worker(sbh) now takes the sbh member
      from the connection rec, ap_get_scoreboard_worker(proc, thread) will now
      provide the unusual legacy lookup.  [William Rowe]
 
-  *) mod_proxy_http: Check but don't escape/unescape forward-proxied URLs
+  *) mod_proxy_http: Don't escape/unescape forward-proxied URLs
      PR 42592 [Nick Kew]
 
   *) mpm winnt: fix null pointer dereference
@@ -20,12 +24,6 @@
   *) mod_deflate: Don't leave a strong ETag in place while transforming
      the entity.
      PR 39727 [Nick Kew]
-
-  *) mod_proxy_http: Remove Warning headers with wrong date
-     PR 16138 [Nick Kew]
-
-  *) mod_proxy_http: Correctly parse all Connection headers in proxy.
-     PR 43509 [Nick Kew]
 
   *) HTTP protocol: Add "DefaultType none" option.
      PR 13986 and PR 16139 [Nick Kew]

Modified: httpd/httpd/trunk/docs/manual/mod/mod_proxy.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/manual/mod/mod_proxy.xml?rev=588791&r1=588790&r2=588791&view=diff
==============================================================================
--- httpd/httpd/trunk/docs/manual/mod/mod_proxy.xml (original)
+++ httpd/httpd/trunk/docs/manual/mod/mod_proxy.xml Fri Oct 26 16:07:22 2007
@@ -574,7 +574,7 @@
 <name>ProxyPass</name>
 <description>Maps remote servers into the local server URL-space</description>
 <syntax>ProxyPass [<var>path</var>] !|<var>url</var> [<var>key=value</var>
-	<var>[key=value</var> ...]]</syntax>
+	<var>[key=value</var> ...]] [nocanon]</syntax>
 <contextlist><context>server config</context><context>virtual host</context>
 <context>directory</context>
 </contextlist>
@@ -842,6 +842,13 @@
       &lt;/Proxy&gt;
     </example>
 
+    <p>Normally, mod_proxy will canonicalise ProxyPassed URLs.
+    But this may be incompatible with some backends, particularly those
+    that make use of <var>PATH_INFO</var>.  The optional <var>nocanon</var>
+    keyword suppresses this, and passes the URL path "raw" to the
+    backend.  Note that may affect the security of your backend, as it
+    removes the normal limited protection against URL-based attacks
+    provided by the proxy.</p>
 
     <p>When used inside a <directive type="section" module="core"
     >Location</directive> section, the first argument is omitted and the local

Modified: httpd/httpd/trunk/modules/proxy/mod_proxy.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/proxy/mod_proxy.c?rev=588791&r1=588790&r2=588791&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/proxy/mod_proxy.c (original)
+++ httpd/httpd/trunk/modules/proxy/mod_proxy.c Fri Oct 26 16:07:22 2007
@@ -228,12 +228,12 @@
                 else
                     worker->status &= ~PROXY_WORKER_HOT_STANDBY;
             }
-	    else if (*v == 'I' || *v == 'i') {
-	    	if (mode)
-		    worker->status |= PROXY_WORKER_IGNORE_ERRORS;
-		else
-		    worker->status &= ~PROXY_WORKER_IGNORE_ERRORS;
-	    }
+            else if (*v == 'I' || *v == 'i') {
+                if (mode)
+                    worker->status |= PROXY_WORKER_IGNORE_ERRORS;
+                else
+                    worker->status &= ~PROXY_WORKER_IGNORE_ERRORS;
+            }
             else {
                 return "Unknown status parameter option";
             }
@@ -509,7 +509,9 @@
     const char *fake;
     const char *real;
     ap_regmatch_t regm[AP_MAX_REG_MATCH];
+    ap_regmatch_t reg1[AP_MAX_REG_MATCH];
     char *found = NULL;
+    int mismatch = 0;
 
     if (r->proxyreq) {
         /* someone has already set up the proxy, it was possibly ourselves
@@ -524,6 +526,8 @@
      */
 
     for (i = 0; i < conf->aliases->nelts; i++) {
+	unsigned int nocanon = ent[i].flags & PROXYPASS_NOCANON;
+        const char *use_uri = nocanon ? r->unparsed_uri : r->uri;
         if (dconf->interpolate_env == 1) {
             fake = proxy_interpolate(r, ent[i].fake);
             real = proxy_interpolate(r, ent[i].real);
@@ -537,8 +541,14 @@
                 if ((real[0] == '!') && (real[1] == '\0')) {
                     return DECLINED;
                 }
-                found = ap_pregsub(r->pool, real, r->uri, AP_MAX_REG_MATCH,
-                                   regm);
+                /* test that we haven't reduced the URI */
+                if (nocanon && ap_regexec(ent[i].regex, r->unparsed_uri,
+                                          AP_MAX_REG_MATCH, reg1, 0)) {
+                    mismatch = 1;
+		    use_uri = r->uri;
+                }
+                found = ap_pregsub(r->pool, real, use_uri, AP_MAX_REG_MATCH,
+                                   (use_uri == r->uri) ? regm : reg1);
                 /* Note: The strcmp() below catches cases where there
                  * was no regex substitution. This is so cases like:
                  *
@@ -556,8 +566,8 @@
                     found = apr_pstrcat(r->pool, "proxy:", found, NULL);
                 }
                 else {
-                    found = apr_pstrcat(r->pool, "proxy:", real, r->uri,
-                                        NULL);
+                    found = apr_pstrcat(r->pool, "proxy:", real,
+                                        use_uri, NULL);
                 }
             }
         }
@@ -568,16 +578,31 @@
                 if ((real[0] == '!') && (real[1] == '\0')) {
                     return DECLINED;
                 }
-
+                if (nocanon
+                    && len != alias_match(r->unparsed_uri, ent[i].fake)) {
+                    mismatch = 1;
+		    use_uri = r->uri;
+                }
                 found = apr_pstrcat(r->pool, "proxy:", real,
-                                    r->uri + len, NULL);
-
+                                    use_uri + len, NULL);
             }
         }
+        if (mismatch) {
+            /* We made a reducing transformation, so we can't safely use
+             * unparsed_uri.  Safe fallback is to ignore nocanon.
+             */
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "Unescaped URL path matched ProxyPass; ignoring unsafe nocanon");
+        }
+
         if (found) {
             r->filename = found;
             r->handler = "proxy-server";
             r->proxyreq = PROXYREQ_REVERSE;
+            if (nocanon && !mismatch) {
+                /* mod_proxy_http needs to be told.  Different module. */
+                apr_table_setn(r->notes, "proxy-nocanon", "1");
+            }
             return OK;
         }
     }
@@ -1185,6 +1210,7 @@
     const apr_table_entry_t *elts;
     int i;
     int use_regex = is_regex;
+    unsigned int flags = 0;
 
     while (*arg) {
         word = ap_getword_conf(cmd->pool, &arg);
@@ -1198,8 +1224,12 @@
             }
             f = word;
         }
-        else if (!r)
+        else if (!r) {
             r = word;
+        }
+        else if (!strcasecmp(word,"nocanon")) {
+            flags |= PROXYPASS_NOCANON;
+        }
         else {
             char *val = strchr(word, '=');
             if (!val) {
@@ -1230,6 +1260,7 @@
     new = apr_array_push(conf->aliases);
     new->fake = apr_pstrdup(cmd->pool, f);
     new->real = apr_pstrdup(cmd->pool, r);
+    new->flags = flags;
     if (use_regex) {
         new->regex = ap_pregcomp(cmd->pool, f, AP_REG_EXTENDED);
         if (new->regex == NULL)

Modified: httpd/httpd/trunk/modules/proxy/mod_proxy.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/proxy/mod_proxy.h?rev=588791&r1=588790&r2=588791&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/proxy/mod_proxy.h (original)
+++ httpd/httpd/trunk/modules/proxy/mod_proxy.h Fri Oct 26 16:07:22 2007
@@ -109,10 +109,12 @@
     int use_regex;          /* simple boolean. True if we have a regex pattern */
 };
 
+#define PROXYPASS_NOCANON 0x01
 struct proxy_alias {
     const char  *real;
     const char  *fake;
     ap_regex_t  *regex;
+    unsigned int flags;
 };
 
 struct dirconn_entry {

Modified: httpd/httpd/trunk/modules/proxy/mod_proxy_http.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/proxy/mod_proxy_http.c?rev=588791&r1=588790&r2=588791&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/proxy/mod_proxy_http.c (original)
+++ httpd/httpd/trunk/modules/proxy/mod_proxy_http.c Fri Oct 26 16:07:22 2007
@@ -82,13 +82,19 @@
 
     /* process path */
     /* In a reverse proxy, our URL has been processed, so canonicalise
-     * In a forward proxy, we have and MUST NOT MANGLE the original,
-     * so just check it for disallowed chars.
+     * unless proxy-nocanon is set to say it's raw
+     * In a forward proxy, we have and MUST NOT MANGLE the original.
      */
     switch (r->proxyreq) {
     default: /* wtf are we doing here? */
     case PROXYREQ_REVERSE:
-        path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, r->proxyreq);
+        if (apr_table_get(r->notes, "proxy-nocanon")) {
+            path = url;   /* this is the raw path */
+        }
+        else {
+            path = ap_proxy_canonenc(r->pool, url, strlen(url),
+                                     enc_path, 0, r->proxyreq);
+        }
         break;
     case PROXYREQ_PROXY:
         path = url;