Browse Source

[core] reject decoded url-path without leading '/'

buffer_simplify_path() no longer prepends '/' if '/' is missing.
Callers must check for leading '/' depending on use, such as in
concatenation with others paths, or direct use accessing filesystem

Note: lighttpd 1.4.50 provides the server.http-parseopts directive.
Recommended settings unless specific use requires looser settings:
  server.http-parseopts = (
    "header-strict"            => "enable",
    "host-strict"              => "enable",
    "host-normalize"           => "enable",
    "url-normalize"            => "enable",
    "url-normalize-unreserved" => "enable",
    "url-normalize-required"   => "enable",
    "url-ctrls-reject"         => "enable",
    "url-path-2f-decode"       => "enable",
    "url-path-dotseg-remove"   => "enable",
    "url-query-20-plus"        => "enable"
  )

x-ref:
  https://digi.ninja/blog/lighttpd_rewrite_bypass.php

As noted in the link above, mod_access should be preferred instead
of mod_rewrite for access controls to URLs.
personal/stbuehler/fix-fdevent
Glenn Strauss 3 years ago
parent
commit
e8e59396d3
  1. 20
      src/buffer.c
  2. 5
      src/mod_webdav.c
  3. 8
      src/response.c
  4. 27
      src/t/test_buffer.c
  5. 1
      src/t/test_burl.c

20
src/buffer.c

@ -806,13 +806,6 @@ void buffer_path_simplify(buffer *dest, buffer *src)
force_assert('\0' == src->ptr[src->used-1]);
/* might need one character more for the '/' prefix */
if (src == dest) {
buffer_string_prepare_append(dest, 1);
} else {
buffer_string_prepare_copy(dest, buffer_string_length(src) + 1);
}
#if defined(__WIN32) || defined(__CYGWIN__)
/* cygwin is treating \ and / the same, so we have to that too */
{
@ -832,14 +825,15 @@ void buffer_path_simplify(buffer *dest, buffer *src)
while (*walk == ' ') {
walk++;
}
if (*walk == '.') {
if (walk[1] == '/' || walk[1] == '\0')
++walk;
else if (walk[1] == '.' && (walk[2] == '/' || walk[2] == '\0'))
walk+=2;
}
pre1 = 0;
c = *(walk++);
/* prefix with '/' if not already present */
if (c != '/') {
pre1 = '/';
*(out++) = '/';
}
while (c != '\0') {
/* assert((src != dest || out <= walk) && slash <= out); */
@ -859,7 +853,7 @@ void buffer_path_simplify(buffer *dest, buffer *src)
if (c == '/' || c == '\0') {
const size_t toklen = out - slash;
if (toklen == 3 && pre2 == '.' && pre1 == '.') {
if (toklen == 3 && pre2 == '.' && pre1 == '.' && *slash == '/') {
/* "/../" or ("/.." at end of string) */
out = slash;
/* if there is something before "/..", there is at least one

5
src/mod_webdav.c

@ -1998,6 +1998,11 @@ static handler_t mod_webdav_copymove(server *srv, connection *con, plugin_data *
buffer_urldecode_path(p->uri.path);
buffer_path_simplify(p->uri.path, p->uri.path);
if (buffer_string_is_empty(p->uri.path) || p->uri.path->ptr[0] != '/') {
con->http_status = 400;
return HANDLER_FINISHED;
}
/* we now have a URI which is clean. transform it into a physical path */
buffer_copy_buffer(p->physical.doc_root, con->physical.doc_root);
buffer_copy_buffer(p->physical.rel_path, p->uri.path);

8
src/response.c

@ -396,6 +396,14 @@ handler_t http_response_prepare(server *srv, connection *con) {
buffer_copy_buffer(con->uri.path, con->uri.path_raw);
buffer_urldecode_path(con->uri.path);
buffer_path_simplify(con->uri.path, con->uri.path);
if (buffer_string_is_empty(con->uri.path) || con->uri.path->ptr[0] != '/') {
log_error_write(srv, __FILE__, __LINE__, "sbs",
"uri-path does not begin with '/':", con->uri.path, "-> 400");
con->keep_alive = 0;
con->http_status = 400;
con->file_finished = 1;
return HANDLER_FINISHED;
}
}
con->conditional_is_valid[COMP_SERVER_SOCKET] = 1; /* SERVERsocket */

27
src/t/test_buffer.c

@ -23,15 +23,6 @@ static void run_buffer_path_simplify(buffer *psrc, buffer *pdest, const char *in
fflush(stderr);
abort();
} else {
#if 0
fprintf(stdout,
"%s.%d: buffer_path_simplify('%s') succeeded: got '%s'\n",
__FILE__,
__LINE__,
in,
out);
#endif
if (psrc != pdest) buffer_copy_buffer(psrc, pdest);
buffer_path_simplify(pdest, psrc);
@ -40,7 +31,7 @@ static void run_buffer_path_simplify(buffer *psrc, buffer *pdest, const char *in
"%s.%d: buffer_path_simplify('%s') failed - not idempotent: expected '%s', got '%s'\n",
__FILE__,
__LINE__,
out,
in,
out,
pdest->ptr ? pdest->ptr : "");
fflush(stderr);
@ -51,21 +42,25 @@ static void run_buffer_path_simplify(buffer *psrc, buffer *pdest, const char *in
static void test_buffer_path_simplify_with(buffer *psrc, buffer *pdest) {
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN(""), CONST_STR_LEN(""));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN(" "), CONST_STR_LEN("/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("/"), CONST_STR_LEN("/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("//"), CONST_STR_LEN("/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc"), CONST_STR_LEN("/abc"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc//"), CONST_STR_LEN("/abc/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc/./xyz"), CONST_STR_LEN("/abc/xyz"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc/.//xyz"), CONST_STR_LEN("/abc/xyz"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc"), CONST_STR_LEN("abc"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc//"), CONST_STR_LEN("abc/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc/./xyz"), CONST_STR_LEN("abc/xyz"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc/.//xyz"), CONST_STR_LEN("abc/xyz"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc/../xyz"), CONST_STR_LEN("/xyz"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("/abc/./xyz"), CONST_STR_LEN("/abc/xyz"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("/abc//./xyz"), CONST_STR_LEN("/abc/xyz"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("/abc/../xyz"), CONST_STR_LEN("/xyz"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc/../xyz/."), CONST_STR_LEN("/xyz/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("/abc/../xyz/."), CONST_STR_LEN("/xyz/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc/./xyz/.."), CONST_STR_LEN("/abc/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("abc/./xyz/.."), CONST_STR_LEN("abc/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("/abc/./xyz/.."), CONST_STR_LEN("/abc/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("."), CONST_STR_LEN(""));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN(".."), CONST_STR_LEN(""));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("..."), CONST_STR_LEN("..."));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("...."), CONST_STR_LEN("...."));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN(".../"), CONST_STR_LEN(".../"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("./xyz/.."), CONST_STR_LEN("/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN(".//xyz/.."), CONST_STR_LEN("/"));
run_buffer_path_simplify(psrc, pdest, CONST_STR_LEN("/./xyz/.."), CONST_STR_LEN("/"));

1
src/t/test_burl.c

@ -31,6 +31,7 @@ static void test_burl_normalize (void) {
int flags;
flags = HTTP_PARSEOPT_URL_NORMALIZE_UNRESERVED;
run_burl_normalize(psrc, ptmp, flags, __LINE__, CONST_STR_LEN("no-slash"), CONST_STR_LEN("no-slash"));
run_burl_normalize(psrc, ptmp, flags, __LINE__, CONST_STR_LEN("/"), CONST_STR_LEN("/"));
run_burl_normalize(psrc, ptmp, flags, __LINE__, CONST_STR_LEN("/abc"), CONST_STR_LEN("/abc"));
run_burl_normalize(psrc, ptmp, flags, __LINE__, CONST_STR_LEN("/abc/"), CONST_STR_LEN("/abc/"));

Loading…
Cancel
Save