diff options
author | Yann Ylavic <ylavic@apache.org> | 2020-06-22 12:29:27 +0200 |
---|---|---|
committer | Yann Ylavic <ylavic@apache.org> | 2020-06-22 12:29:27 +0200 |
commit | 4c79fd280dfa3eede5a6f3baebc7ef2e55b3eb6a (patch) | |
tree | b0a2d20a8bdbaef0d868fd2f3bc440ce1add089f | |
parent | Backported to 2.4.x (diff) | |
download | apache2-4c79fd280dfa3eede5a6f3baebc7ef2e55b3eb6a.tar.xz apache2-4c79fd280dfa3eede5a6f3baebc7ef2e55b3eb6a.zip |
Add ap_normalize_path() to replace ap_getparents() (with options).
include/httpd.h: Declare ap_normalize_path() and flags.
AP_NORMALIZE_ALLOW_RELATIVE:
Don't require that the path be absolute as per RFC 7230.
This is needed for lookup subrequests.
AP_NORMALIZE_NOT_ABOVE_ROOT:
Check that directory traversal ("..") don't go above root, or
initial directory with relative paths.
AP_NORMALIZE_DECODE_UNRESERVED:
Decode unreserved characters (like '.') first since they have
the same semantics encoded and decoded.
AP_NORMALIZE_MERGE_SLASHES:
Merge multiple slahes into a single one.
AP_NORMALIZE_DROP_PARAMETERS:
Ignore path parameters (";foo=bar"). Not used by httpd but since
ap_normalize_path() is taken from mod_jk's jk_servlet_normalize()
it can allow them to use the upstream version now.
server/util.c: Implement ap_normalize_path().
modules/dav/main/util.c: Replace call to ap_getparents() using
ap_normalize_path() with AP_NORMALIZE_DECODE_UNRESERVED flag since
the path comes from an obsolute URL (thus potentially %-encoded).
modules/generators/mod_autoindex.c: Replace call to ap_getparents() using
ap_normalize_path() with AP_NORMALIZE_ALLOW_RELATIVE and
AP_NORMALIZE_NOT_ABOVE_ROOT flags to be consistent with original code.
include/ap_mmn.h: MINOR bump for ap_normalize_path().
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1879074 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | include/ap_mmn.h | 3 | ||||
-rw-r--r-- | include/httpd.h | 15 | ||||
-rw-r--r-- | modules/dav/main/util.c | 7 | ||||
-rw-r--r-- | modules/generators/mod_autoindex.c | 5 | ||||
-rw-r--r-- | server/util.c | 112 |
5 files changed, 137 insertions, 5 deletions
diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 2a8ce4c2ca..15d2641d1a 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -632,6 +632,7 @@ * 20200420.1 (2.5.1-dev) Add ap_filter_adopt_brigade() * 20200420.2 (2.5.1-dev) Add ap_proxy_worker_can_upgrade() * 20200420.3 (2.5.1-dev) Add ap_parse_strict_length() + * 20200420.4 (2.5.1-dev) Add ap_normalize_path() */ #define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */ @@ -639,7 +640,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20200420 #endif -#define MODULE_MAGIC_NUMBER_MINOR 3 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 4 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/include/httpd.h b/include/httpd.h index 90293eb74c..ad38f2c927 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -1779,6 +1779,21 @@ AP_DECLARE(void) ap_no2slash(char *name) AP_DECLARE(void) ap_no2slash_ex(char *name, int is_fs_path) AP_FN_ATTR_NONNULL_ALL; +#define AP_NORMALIZE_ALLOW_RELATIVE (1u << 0) +#define AP_NORMALIZE_NOT_ABOVE_ROOT (1u << 1) +#define AP_NORMALIZE_DECODE_UNRESERVED (1u << 2) +#define AP_NORMALIZE_MERGE_SLASHES (1u << 3) +#define AP_NORMALIZE_DROP_PARAMETERS (1u << 4) + +/** + * Remove all ////, /./ and /xx/../ substrings from a path, and more + * depending on passed in flags. + * @param path The path to normalize + * @param flags bitmask of AP_NORMALIZE_* flags + * @return non-zero on success + */ +AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags) + AP_FN_ATTR_NONNULL((1)); /** * Remove all ./ and xx/../ substrings from a file name. Also remove diff --git a/modules/dav/main/util.c b/modules/dav/main/util.c index e21f626068..8cf3fe5234 100644 --- a/modules/dav/main/util.c +++ b/modules/dav/main/util.c @@ -664,7 +664,12 @@ static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih) /* note that parsed_uri.path is allocated; we can trash it */ /* clean up the URI a bit */ - ap_getparents(parsed_uri.path); + if (!ap_normalize_path(parsed_uri.path, + AP_NORMALIZE_DECODE_UNRESERVED)) { + return dav_new_error(r->pool, HTTP_BAD_REQUEST, + DAV_ERR_IF_TAGGED, rv, + "Invalid URI path tagged If-header."); + } /* the resources we will compare to have unencoded paths */ if (ap_unescape_url(parsed_uri.path) != OK) { diff --git a/modules/generators/mod_autoindex.c b/modules/generators/mod_autoindex.c index f977b88324..e43ba91dee 100644 --- a/modules/generators/mod_autoindex.c +++ b/modules/generators/mod_autoindex.c @@ -1266,8 +1266,9 @@ static struct ent *make_parent_entry(apr_int32_t autoindex_opts, if (!(p->name = ap_make_full_path(r->pool, r->uri, "../"))) { return (NULL); } - ap_getparents(p->name); - if (!*p->name) { + if (!ap_normalize_path(p->name, AP_NORMALIZE_ALLOW_RELATIVE | + AP_NORMALIZE_NOT_ABOVE_ROOT) + || p->name[0] == '\0') { return (NULL); } diff --git a/server/util.c b/server/util.c index 59e273e911..ba9551927b 100644 --- a/server/util.c +++ b/server/util.c @@ -77,7 +77,7 @@ #include "test_char.h" /* Win32/NetWare/OS2 need to check for both forward and back slashes - * in ap_getparents() and ap_escape_url. + * in ap_normalize_path() and ap_escape_url(). */ #ifdef CASE_BLIND_FILESYSTEM #define IS_SLASH(s) ((s == '/') || (s == '\\')) @@ -492,6 +492,116 @@ AP_DECLARE(apr_status_t) ap_pregsub_ex(apr_pool_t *p, char **result, return rc; } +/* Forward declare */ +static char x2c(const char *what); + +#define IS_SLASH_OR_NUL(s) (s == '\0' || IS_SLASH(s)) + +/* + * Inspired by mod_jk's jk_servlet_normalize(). + */ +AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags) +{ + int ret = 1; + apr_size_t l = 1, w = 1; + + if (!IS_SLASH(path[0])) { + /* Besides "OPTIONS *", a request-target should start with '/' + * per RFC 7230 section 5.3, so anything else is invalid. + */ + if (path[0] == '*' && path[1] == '\0') { + return 1; + } + /* However, AP_NORMALIZE_ALLOW_RELATIVE can be used to bypass + * this restriction (e.g. for subrequest file lookups). + */ + if (!(flags & AP_NORMALIZE_ALLOW_RELATIVE) || path[0] == '\0') { + return 0; + } + + l = w = 0; + } + + while (path[l] != '\0') { + /* RFC-3986 section 2.3: + * For consistency, percent-encoded octets in the ranges of + * ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), + * period (%2E), underscore (%5F), or tilde (%7E) should [...] + * be decoded to their corresponding unreserved characters by + * URI normalizers. + */ + if ((flags & AP_NORMALIZE_DECODE_UNRESERVED) + && path[l] == '%' && apr_isxdigit(path[l + 1]) + && apr_isxdigit(path[l + 2])) { + const char c = x2c(&path[l + 1]); + if (apr_isalnum(c) || (c && strchr("-._~", c))) { + /* Replace last char and fall through as the current + * read position */ + l += 2; + path[l] = c; + } + } + + if ((flags & AP_NORMALIZE_DROP_PARAMETERS) && path[l] == ';') { + do { + l++; + } while (!IS_SLASH_OR_NUL(path[l])); + continue; + } + + if (w == 0 || IS_SLASH(path[w - 1])) { + /* Collapse ///// sequences to / */ + if ((flags & AP_NORMALIZE_MERGE_SLASHES) && IS_SLASH(path[l])) { + do { + l++; + } while (IS_SLASH(path[l])); + continue; + } + + if (path[l] == '.') { + /* Remove /./ segments */ + if (IS_SLASH_OR_NUL(path[l + 1])) { + l++; + if (path[l]) { + l++; + } + continue; + } + + /* Remove /xx/../ segments */ + if (path[l + 1] == '.' && IS_SLASH_OR_NUL(path[l + 2])) { + /* Wind w back to remove the previous segment */ + if (w > 1) { + do { + w--; + } while (w && !IS_SLASH(path[w - 1])); + } + else { + /* Already at root, ignore and return a failure + * if asked to. + */ + if (flags & AP_NORMALIZE_NOT_ABOVE_ROOT) { + ret = 0; + } + } + + /* Move l forward to the next segment */ + l += 2; + if (path[l]) { + l++; + } + continue; + } + } + } + + path[w++] = path[l++]; + } + path[w] = '\0'; + + return ret; +} + /* * Parse .. so we don't compromise security */ |