From 785b0379d6b2e4eac3ad3127cb6f63833316d170 Mon Sep 17 00:00:00 2001 From: Nick Kew Date: Fri, 26 Oct 2007 23:07:22 +0000 Subject: [PATCH] mod_proxy: add "nocanon" keyword to ProxyPass, to suppress URI-canonicalisation in a reverse proxy. PR 41798 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@588791 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 12 +++---- docs/manual/mod/mod_proxy.xml | 9 +++++- modules/proxy/mod_proxy.c | 59 ++++++++++++++++++++++++++-------- modules/proxy/mod_proxy.h | 2 ++ modules/proxy/mod_proxy_http.c | 12 +++++-- 5 files changed, 69 insertions(+), 25 deletions(-) diff --git a/CHANGES b/CHANGES index 45fb51de6d..ff41efe51f 100644 --- a/CHANGES +++ b/CHANGES @@ -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 @@ -21,12 +25,6 @@ Changes with Apache 2.3.0 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] diff --git a/docs/manual/mod/mod_proxy.xml b/docs/manual/mod/mod_proxy.xml index 5e5db191dd..bbe6740d75 100644 --- a/docs/manual/mod/mod_proxy.xml +++ b/docs/manual/mod/mod_proxy.xml @@ -574,7 +574,7 @@ expressions ProxyPass Maps remote servers into the local server URL-space ProxyPass [path] !|url [key=value - [key=value ...]] + [key=value ...]] [nocanon] server configvirtual host directory @@ -842,6 +842,13 @@ expressions </Proxy> +

Normally, mod_proxy will canonicalise ProxyPassed URLs. + But this may be incompatible with some backends, particularly those + that make use of PATH_INFO. The optional nocanon + 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.

When used inside a Location section, the first argument is omitted and the local diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index 31b5d8540d..5862354504 100644 --- a/modules/proxy/mod_proxy.c +++ b/modules/proxy/mod_proxy.c @@ -228,12 +228,12 @@ static const char *set_worker_param(apr_pool_t *p, 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 @@ static int proxy_trans(request_rec *r) 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 @@ static int proxy_trans(request_rec *r) */ 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 @@ static int proxy_trans(request_rec *r) 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 @@ static int proxy_trans(request_rec *r) 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 @@ static int proxy_trans(request_rec *r) 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 @@ static const char * 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 @@ static const char * } 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 @@ static const char * 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) diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index de73723a1b..8659602268 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -109,10 +109,12 @@ struct proxy_remote { 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 { diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c index 82c2035cfd..762b2a8a1e 100644 --- a/modules/proxy/mod_proxy_http.c +++ b/modules/proxy/mod_proxy_http.c @@ -82,13 +82,19 @@ static int proxy_http_canon(request_rec *r, char *url) /* 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; -- 2.40.0