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
This commit is contained in:
Yann Ylavic
2020-06-22 10:29:27 +00:00
parent b3f1c802ac
commit 4c79fd280d
5 changed files with 137 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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);
}

View File

@ -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
*/