From 86c70e5abbe575171f51ac71f378ec7d53e30125 Mon Sep 17 00:00:00 2001 From: Sergey Lyubka Date: Sun, 31 Oct 2021 12:36:34 +0000 Subject: [PATCH] Fix #1391 - make remove_double_dots less aggressive --- mongoose.c | 15 +++++++++++++-- src/http.c | 15 +++++++++++++-- test/unit_test.c | 34 +++++++++++++++++++++++++++++++--- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/mongoose.c b/mongoose.c index 576948b0..66782008 100644 --- a/mongoose.c +++ b/mongoose.c @@ -1441,7 +1441,8 @@ static void remove_double_dots(char *s) { while (s[0] != '\0') { if (s[0] == '/' || s[0] == '\\') { s++; - } else if (s[0] == '.' && s[1] == '.') { + } else if (s[0] == '.' && s[1] == '.' && + (s[2] == '/' || s[2] == '\\')) { s += 2; } else { break; @@ -1471,11 +1472,21 @@ static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm, path[path_size - 1] = '\0'; // Double-check remove_double_dots(path); n = strlen(path); - LOG(LL_VERBOSE_DEBUG, ("%lu %s", c->id, path)); + LOG(LL_VERBOSE_DEBUG, + ("%lu %.*s -> %s", c->id, (int) hm->uri.len, hm->uri.ptr, path)); while (n > 0 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes flags = fs->stat(path, NULL, NULL); // Does it exist? if (flags == 0) { mg_http_reply(c, 404, "", "Not found\n"); // Does not exist, doh + } else if ((flags & MG_FS_DIR) && hm->uri.len && + hm->uri.ptr[hm->uri.len - 1] != '/') { + mg_printf(c, + "HTTP/1.1 301 Moved\r\n" + "Location: %.*s/\r\n" + "Content-Length: 0\r\n" + "\r\n", + (int) hm->uri.len, hm->uri.ptr); + flags = 0; } else if (flags & MG_FS_DIR) { if (((snprintf(path + n, path_size - n, "/index.html") > 0 && (tmp = fs->stat(path, NULL, NULL)) != 0) || diff --git a/src/http.c b/src/http.c index 891ba787..a58e214f 100644 --- a/src/http.c +++ b/src/http.c @@ -695,7 +695,8 @@ static void remove_double_dots(char *s) { while (s[0] != '\0') { if (s[0] == '/' || s[0] == '\\') { s++; - } else if (s[0] == '.' && s[1] == '.') { + } else if (s[0] == '.' && s[1] == '.' && + (s[2] == '/' || s[2] == '\\')) { s += 2; } else { break; @@ -725,11 +726,21 @@ static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm, path[path_size - 1] = '\0'; // Double-check remove_double_dots(path); n = strlen(path); - LOG(LL_VERBOSE_DEBUG, ("%lu %s", c->id, path)); + LOG(LL_VERBOSE_DEBUG, + ("%lu %.*s -> %s", c->id, (int) hm->uri.len, hm->uri.ptr, path)); while (n > 0 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes flags = fs->stat(path, NULL, NULL); // Does it exist? if (flags == 0) { mg_http_reply(c, 404, "", "Not found\n"); // Does not exist, doh + } else if ((flags & MG_FS_DIR) && hm->uri.len && + hm->uri.ptr[hm->uri.len - 1] != '/') { + mg_printf(c, + "HTTP/1.1 301 Moved\r\n" + "Location: %.*s/\r\n" + "Content-Length: 0\r\n" + "\r\n", + (int) hm->uri.len, hm->uri.ptr); + flags = 0; } else if (flags & MG_FS_DIR) { if (((snprintf(path + n, path_size - n, "/index.html") > 0 && (tmp = fs->stat(path, NULL, NULL)) != 0) || diff --git a/test/unit_test.c b/test/unit_test.c index da459cc2..3d7316c3 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -468,6 +468,15 @@ static int cmpbody(const char *buf, const char *str) { return mg_strcmp(hm.body, s); } +static bool cmpheader(const char *buf, const char *name, const char *value) { + struct mg_http_message hm; + struct mg_str *h; + size_t len = strlen(buf); + mg_http_parse(buf, len, &hm); + h = mg_http_get_header(&hm, name); + return h != NULL && mg_strcmp(*h, mg_str(value)) == 0; +} + static void wcb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_WS_OPEN) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; @@ -536,6 +545,22 @@ static void test_http_server(void) { ASSERT(fetch(&mgr, buf, url, "GET /київ.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "є\n") == 0); + ASSERT(fetch(&mgr, buf, url, "GET /../fuzz.c HTTP/1.0\n\n") == 404); + ASSERT(fetch(&mgr, buf, url, "GET /.%%2e/fuzz.c HTTP/1.0\n\n") == 404); + ASSERT(fetch(&mgr, buf, url, "GET /.%%2e%%2ffuzz.c HTTP/1.0\n\n") == 404); + ASSERT(fetch(&mgr, buf, url, "GET /..%%2f%%20fuzz.c HTTP/1.0\n\n") == 404); + ASSERT(fetch(&mgr, buf, url, "GET /..%%2ffuzz.c%%20 HTTP/1.0\n\n") == 404); + + ASSERT(fetch(&mgr, buf, url, "GET /dredir HTTP/1.0\n\n") == 301); + ASSERT(cmpheader(buf, "Location", "/dredir/")); + + ASSERT(fetch(&mgr, buf, url, "GET /dredir/ HTTP/1.0\n\n") == 200); + ASSERT(cmpbody(buf, "hi\n") == 0); + + ASSERT(fetch(&mgr, buf, url, "GET /..ddot HTTP/1.0\n\n") == 301); + ASSERT(fetch(&mgr, buf, url, "GET /..ddot/ HTTP/1.0\n\n") == 200); + ASSERT(cmpbody(buf, "hi\n") == 0); + { extern char *mg_http_etag(char *, size_t, size_t, time_t); char etag[100]; @@ -567,7 +592,8 @@ static void test_http_server(void) { "Content-Length: 4\r\n\r\nkuku") == 200); ASSERT(cmpbody(buf, "kuku") == 0); - ASSERT(fetch(&mgr, buf, url, "GET /ssi HTTP/1.1\r\n\r\n") == 200); + ASSERT(fetch(&mgr, buf, url, "GET /ssi HTTP/1.1\r\n\r\n") == 301); + ASSERT(fetch(&mgr, buf, url, "GET /ssi/ HTTP/1.1\r\n\r\n") == 200); ASSERT(cmpbody(buf, "this is index\n" "this is nested\n\n" @@ -1436,7 +1462,8 @@ static void test_packed(void) { // printf("--------\n%s\n", buf); // List nested dir - ASSERT(fetch(&mgr, buf, url, "GET /test HTTP/1.0\n\n") == 200); + ASSERT(fetch(&mgr, buf, url, "GET /test HTTP/1.0\n\n") == 301); + ASSERT(fetch(&mgr, buf, url, "GET /test/ HTTP/1.0\n\n") == 200); // printf("--------\n%s\n", buf); mg_mgr_free(&mgr); @@ -1577,7 +1604,8 @@ static void test_rewrites(void) { ASSERT(cmpbody(buf, "hello\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /foo/version.h HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, expected) == 0); - ASSERT(fetch(&mgr, buf, url, "GET /foo HTTP/1.0\n\n") == 200); + ASSERT(fetch(&mgr, buf, url, "GET /foo HTTP/1.0\n\n") == 301); + ASSERT(fetch(&mgr, buf, url, "GET /foo/ HTTP/1.0\n\n") == 200); // printf("-->[%s]\n", buf); // exit(0); mg_mgr_free(&mgr);