]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
mod_rewrite: Better question mark tracking to avoid UnsafeAllow3F. PR 69197.
authorYann Ylavic <ylavic@apache.org>
Sat, 27 Jul 2024 13:35:53 +0000 (13:35 +0000)
committerYann Ylavic <ylavic@apache.org>
Sat, 27 Jul 2024 13:35:53 +0000 (13:35 +0000)
Track in do_expand() whether a '?' in the uri-path comes from a literal in
the substitution string or from an expansion (variable, lookup, ...).
In the former case it's safe to assume that it's the query-string separator
but for the other case it's not (could be a decoded %3f from r->uri).

This allows to avoid [UnsafeAllow3F] for most cases.

Merges r1919325 from trunk
Reviewed by: ylavic, covener, jorton
Github: closes #462

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1919545 13f79535-47bb-0310-9956-ffa450edef68

modules/mappers/mod_rewrite.c

index f1c22e3235b6d64a8b6c8241fa09abd3ca617137..53fb1e91ffb0c9a8252b16bab1ee2cc448a5c59f 100644 (file)
@@ -2376,9 +2376,16 @@ static APR_INLINE char *find_char_in_curlies(char *s, int c)
  * of an earlier expansion to include expansion specifiers that
  * are interpreted by a later expansion, producing results that
  * were not intended by the administrator.
+ *
+ * unsafe_qmark if not NULL will be set to 1 or 0 if a question mark
+ * is found respectively in a literal or in a lookup/expansion (whether
+ * it's the first or last qmark depends on [QSL]). Should be initialized
+ * to -1 and remains so if no qmark is found.
  */
-static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry)
+static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry,
+                       int *unsafe_qmark)
 {
+#define EXPAND_SPECIALS "\\$%"
     result_list *result, *current;
     result_list sresult[SMALL_EXPANSION];
     unsigned spc = 0;
@@ -2386,8 +2393,29 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry)
     char *p, *c;
     apr_pool_t *pool = ctx->r->pool;
 
-    span = strcspn(input, "\\$%");
     inputlen = strlen(input);
+    if (!unsafe_qmark) {
+        span = strcspn(input, EXPAND_SPECIALS);
+    }
+    else {
+        span = strcspn(input, EXPAND_SPECIALS "?");
+        if (input[span] == '?') {
+            /* this qmark is not from an expansion thus safe */
+            *unsafe_qmark = 0;
+
+            /* keep tracking only if interested in the last qmark */
+            if (entry && (entry->flags & RULEFLAG_QSLAST)) {
+                do {
+                    span++;
+                    span += strcspn(input + span, EXPAND_SPECIALS "?");
+                } while (input[span] == '?');
+            }
+            else {
+                unsafe_qmark = NULL;
+                span += strcspn(input + span, EXPAND_SPECIALS);
+            }
+        }
+    }
 
     /* fast exit */
     if (inputlen == span) {
@@ -2405,6 +2433,8 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry)
 
     /* loop for specials */
     do {
+        int expanded = 0;
+
         /* prepare next entry */
         if (current->len) {
             current->next = (spc < SMALL_EXPANSION)
@@ -2450,6 +2480,8 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry)
                 current->len = span;
                 current->string = p;
                 outlen += span;
+
+                expanded = 1;
                 p = endp + 1;
             }
 
@@ -2489,19 +2521,18 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry)
                     }
 
                     /* reuse of key variable as result */
-                    key = lookup_map(ctx->r, map, do_expand(key, ctx, entry));
-
+                    key = lookup_map(ctx->r, map, do_expand(key, ctx, entry, NULL));
                     if (!key && dflt && *dflt) {
-                        key = do_expand(dflt, ctx, entry);
+                        key = do_expand(dflt, ctx, entry, NULL);
                     }
-
-                    if (key) {
+                    if (key && *key) {
                         span = strlen(key);
                         current->len = span;
                         current->string = key;
                         outlen += span;
                     }
 
+                    expanded = 1;
                     p = endp + 1;
                 }
             }
@@ -2531,8 +2562,9 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry)
                     current->len = span;
                     current->string = bri->source + bri->regmatch[n].rm_so;
                 }
-
                 outlen += span;
+
+                expanded = 1;
             }
 
             p += 2;
@@ -2545,8 +2577,41 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry)
             ++outlen;
         }
 
+        if (unsafe_qmark && expanded && current->len
+            && memchr(current->string, '?', current->len)) {
+            /* this qmark is from an expansion thus unsafe */
+            *unsafe_qmark = 1;
+
+            /* keep tracking only if interested in the last qmark */
+            if (!entry || !(entry->flags & RULEFLAG_QSLAST)) {
+                unsafe_qmark = NULL;
+            }
+        }
+
         /* check the remainder */
-        if (*p && (span = strcspn(p, "\\$%")) > 0) {
+        if (!unsafe_qmark) {
+            span = strcspn(p, EXPAND_SPECIALS);
+        }
+        else {
+            span = strcspn(p, EXPAND_SPECIALS "?");
+            if (p[span] == '?') {
+                /* this qmark is not from an expansion thus safe */
+                *unsafe_qmark = 0;
+
+                /* keep tracking only if interested in the last qmark */
+                if (entry && (entry->flags & RULEFLAG_QSLAST)) {
+                    do {
+                        span++;
+                        span += strcspn(p + span, EXPAND_SPECIALS "?");
+                    } while (p[span] == '?');
+                }
+                else {
+                    unsafe_qmark = NULL;
+                    span += strcspn(p + span, EXPAND_SPECIALS);
+                }
+            }
+        }
+        if (span > 0) {
             if (current->len) {
                 current->next = (spc < SMALL_EXPANSION)
                                 ? &(sresult[spc++])
@@ -2591,7 +2656,7 @@ static void do_expand_env(data_item *env, rewrite_ctx *ctx)
     char *name, *val;
 
     while (env) {
-        name = do_expand(env->data, ctx, NULL);
+        name = do_expand(env->data, ctx, NULL, NULL);
         if (*name == '!') {
             name++;
             apr_table_unset(ctx->r->subprocess_env, name);
@@ -2725,7 +2790,7 @@ static void add_cookie(request_rec *r, char *s)
 static void do_expand_cookie(data_item *cookie, rewrite_ctx *ctx)
 {
     while (cookie) {
-        add_cookie(ctx->r, do_expand(cookie->data, ctx, NULL));
+        add_cookie(ctx->r, do_expand(cookie->data, ctx, NULL, NULL));
         cookie = cookie->next;
     }
 
@@ -4014,7 +4079,7 @@ static int apply_rewrite_cond(rewritecond_entry *p, rewrite_ctx *ctx)
     int basis;
 
     if (p->ptype != CONDPAT_AP_EXPR)
-        input = do_expand(p->input, ctx, NULL);
+        input = do_expand(p->input, ctx, NULL, NULL);
 
     switch (p->ptype) {
     case CONDPAT_FILE_EXISTS:
@@ -4178,7 +4243,7 @@ static APR_INLINE void force_type_handler(rewriterule_entry *p,
     char *expanded;
 
     if (p->forced_mimetype) {
-        expanded = do_expand(p->forced_mimetype, ctx, p);
+        expanded = do_expand(p->forced_mimetype, ctx, p, NULL);
 
         if (*expanded) {
             ap_str_tolower(expanded);
@@ -4192,7 +4257,7 @@ static APR_INLINE void force_type_handler(rewriterule_entry *p,
     }
 
     if (p->forced_handler) {
-        expanded = do_expand(p->forced_handler, ctx, p);
+        expanded = do_expand(p->forced_handler, ctx, p, NULL);
 
         if (*expanded) {
             ap_str_tolower(expanded);
@@ -4329,12 +4394,18 @@ static rule_return_type apply_rewrite_rule(rewriterule_entry *p,
 
     /* expand the result */
     if (!(p->flags & RULEFLAG_NOSUB)) {
-        newuri = do_expand(p->output, ctx, p);
+        int unsafe_qmark = -1;
+
+        if (p->flags & RULEFLAG_UNSAFE_ALLOW3F) {
+            newuri = do_expand(p->output, ctx, p, NULL);
+        }
+        else {
+            newuri = do_expand(p->output, ctx, p, &unsafe_qmark);
+        }
         rewritelog((r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri,
                     newuri));
-        if (!(p->flags & RULEFLAG_UNSAFE_ALLOW3F) &&
-            ap_strcasestr(r->unparsed_uri, "%3f") &&
-            ap_strchr_c(newuri, '?')) {
+
+        if (unsafe_qmark > 0) {
             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10508)
                     "Unsafe URL with %%3f URL rewritten without "
                     "UnsafeAllow3F");