From 40cb0aac7c509f99d27457538947f34baea1e99e Mon Sep 17 00:00:00 2001 From: Neradoc Date: Tue, 5 Jul 2022 17:18:22 +0200 Subject: [PATCH 1/4] CORS preflight: allow OPTIONS without authentication check for 127.0.0.1 without a port specified --- supervisor/shared/web_workflow/web_workflow.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index 81730c92d0..379b88fd18 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -356,6 +356,7 @@ static bool _origin_ok(const char *origin) { const char *http = "http://"; const char *local = ".local"; + // note: redirected requests send an Origin of "null" and will be caught by this if (memcmp(origin, http, strlen(http)) != 0) { return false; } @@ -374,8 +375,11 @@ static bool _origin_ok(const char *origin) { return true; } - const char *localhost = "127.0.0.1:"; - if (memcmp(origin + strlen(http), localhost, strlen(localhost)) == 0) { + // Port or no port + const char *localhost = "127.0.0.1"; + const int locallen = 9; + if (memcmp(origin + strlen(http), localhost, locallen) == 0 + && (localhost[locallen] == '\0' || localhost[locallen] == ':')) { return true; } @@ -909,7 +913,8 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { ESP_LOGE(TAG, "bad origin %s", request->origin); _reply_forbidden(socket, request); } else if (memcmp(request->path, "/fs/", 4) == 0) { - if (!request->authenticated) { + // OPTIONS is sent for CORS preflight, unauthenticated + if (!request->authenticated && strcmp(request->method, "OPTIONS") != 0) { if (_api_password[0] != '\0') { _reply_unauthorized(socket, request); } else { @@ -1030,7 +1035,10 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } } else if (memcmp(request->path, "/cp/", 4) == 0) { const char *path = request->path + 3; - if (strcmp(request->method, "GET") != 0) { + if (strcmp(request->method, "OPTIONS") == 0) { + // handle preflight requests to /cp/ + _reply_access_control(socket, request); + } else if (strcmp(request->method, "GET") != 0) { _reply_method_not_allowed(socket, request); } else if (strcmp(path, "/devices.json") == 0) { _reply_with_devices_json(socket, request); From 9a6c3884a73466d27a9909572c8d39ad9daec5f4 Mon Sep 17 00:00:00 2001 From: Neradoc Date: Sun, 24 Jul 2022 10:07:41 +0200 Subject: [PATCH 2/4] do options unauthenticated first, fix testing localhost --- supervisor/shared/web_workflow/web_workflow.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index 379b88fd18..eaa3e1800f 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -375,11 +375,10 @@ static bool _origin_ok(const char *origin) { return true; } - // Port or no port const char *localhost = "127.0.0.1"; - const int locallen = 9; - if (memcmp(origin + strlen(http), localhost, locallen) == 0 - && (localhost[locallen] == '\0' || localhost[locallen] == ':')) { + end = origin + strlen(http) + strlen(localhost); + if (memcmp(origin + strlen(http), localhost, strlen(localhost)) == 0 + && (end[0] == '\0' || end[0] == ':')) { return true; } @@ -913,8 +912,10 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { ESP_LOGE(TAG, "bad origin %s", request->origin); _reply_forbidden(socket, request); } else if (memcmp(request->path, "/fs/", 4) == 0) { - // OPTIONS is sent for CORS preflight, unauthenticated - if (!request->authenticated && strcmp(request->method, "OPTIONS") != 0) { + if (strcmp(request->method, "OPTIONS") == 0) { + // OPTIONS is sent for CORS preflight, unauthenticated + _reply_access_control(socket, request); + } else if (!request->authenticated) { if (_api_password[0] != '\0') { _reply_unauthorized(socket, request); } else { @@ -935,9 +936,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } // Delete is almost identical for files and directories so share the // implementation. - if (strcmp(request->method, "OPTIONS") == 0) { - _reply_access_control(socket, request); - } else if (strcmp(request->method, "DELETE") == 0) { + if (strcmp(request->method, "DELETE") == 0) { if (_usb_active()) { _reply_conflict(socket, request); return false; From 6575598ae6fed99727c455b175cd068d3b27945f Mon Sep 17 00:00:00 2001 From: Neradoc Date: Sun, 24 Jul 2022 12:28:17 +0200 Subject: [PATCH 3/4] HTTP headers and methods are not case sensitive had the issue where Firefox would send "authorization" in lower case --- supervisor/shared/web_workflow/web_workflow.c | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index eaa3e1800f..11f00c125f 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -912,7 +912,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { ESP_LOGE(TAG, "bad origin %s", request->origin); _reply_forbidden(socket, request); } else if (memcmp(request->path, "/fs/", 4) == 0) { - if (strcmp(request->method, "OPTIONS") == 0) { + if (strcasecmp(request->method, "OPTIONS") == 0) { // OPTIONS is sent for CORS preflight, unauthenticated _reply_access_control(socket, request); } else if (!request->authenticated) { @@ -936,7 +936,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } // Delete is almost identical for files and directories so share the // implementation. - if (strcmp(request->method, "DELETE") == 0) { + if (strcasecmp(request->method, "DELETE") == 0) { if (_usb_active()) { _reply_conflict(socket, request); return false; @@ -966,7 +966,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { return true; } } else if (directory) { - if (strcmp(request->method, "GET") == 0) { + if (strcasecmp(request->method, "GET") == 0) { FF_DIR dir; FRESULT res = f_opendir(fs, &dir, path); // Put the / back for replies. @@ -986,7 +986,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } f_closedir(&dir); - } else if (strcmp(request->method, "PUT") == 0) { + } else if (strcasecmp(request->method, "PUT") == 0) { if (_usb_active()) { _reply_conflict(socket, request); return false; @@ -1015,7 +1015,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } } } else { // Dealing with a file. - if (strcmp(request->method, "GET") == 0) { + if (strcasecmp(request->method, "GET") == 0) { FIL active_file; FRESULT result = f_open(fs, &active_file, path, FA_READ); @@ -1026,7 +1026,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } f_close(&active_file); - } else if (strcmp(request->method, "PUT") == 0) { + } else if (strcasecmp(request->method, "PUT") == 0) { _write_file_and_reply(socket, request, fs, path); return true; } @@ -1034,10 +1034,10 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } } else if (memcmp(request->path, "/cp/", 4) == 0) { const char *path = request->path + 3; - if (strcmp(request->method, "OPTIONS") == 0) { + if (strcasecmp(request->method, "OPTIONS") == 0) { // handle preflight requests to /cp/ _reply_access_control(socket, request); - } else if (strcmp(request->method, "GET") != 0) { + } else if (strcasecmp(request->method, "GET") != 0) { _reply_method_not_allowed(socket, request); } else if (strcmp(path, "/devices.json") == 0) { _reply_with_devices_json(socket, request); @@ -1058,7 +1058,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } else { _reply_missing(socket, request); } - } else if (strcmp(request->method, "GET") != 0) { + } else if (strcasecmp(request->method, "GET") != 0) { _reply_method_not_allowed(socket, request); } else { if (strcmp(request->path, "/") == 0) { @@ -1175,27 +1175,27 @@ static void _process_request(socketpool_socket_obj_t *socket, _request *request) request->header_value[request->offset - 1] = '\0'; request->offset = 0; request->state = STATE_HEADER_KEY; - if (strcmp(request->header_key, "Authorization") == 0) { + if (strcasecmp(request->header_key, "Authorization") == 0) { const char *prefix = "Basic "; request->authenticated = memcmp(request->header_value, prefix, strlen(prefix)) == 0 && strcmp(_api_password, request->header_value + strlen(prefix)) == 0; - } else if (strcmp(request->header_key, "Host") == 0) { + } else if (strcasecmp(request->header_key, "Host") == 0) { request->redirect = strcmp(request->header_value, "circuitpython.local") == 0; - } else if (strcmp(request->header_key, "Content-Length") == 0) { + } else if (strcasecmp(request->header_key, "Content-Length") == 0) { request->content_length = strtoul(request->header_value, NULL, 10); - } else if (strcmp(request->header_key, "Expect") == 0) { + } else if (strcasecmp(request->header_key, "Expect") == 0) { request->expect = strcmp(request->header_value, "100-continue") == 0; - } else if (strcmp(request->header_key, "Accept") == 0) { - request->json = strcmp(request->header_value, "application/json") == 0; - } else if (strcmp(request->header_key, "Origin") == 0) { + } else if (strcasecmp(request->header_key, "Accept") == 0) { + request->json = strcasecmp(request->header_value, "application/json") == 0; + } else if (strcasecmp(request->header_key, "Origin") == 0) { strcpy(request->origin, request->header_value); - } else if (strcmp(request->header_key, "X-Timestamp") == 0) { + } else if (strcasecmp(request->header_key, "X-Timestamp") == 0) { request->timestamp_ms = strtoull(request->header_value, NULL, 10); - } else if (strcmp(request->header_key, "Upgrade") == 0) { + } else if (strcasecmp(request->header_key, "Upgrade") == 0) { request->websocket = strcmp(request->header_value, "websocket") == 0; - } else if (strcmp(request->header_key, "Sec-WebSocket-Version") == 0) { + } else if (strcasecmp(request->header_key, "Sec-WebSocket-Version") == 0) { request->websocket_version = strtoul(request->header_value, NULL, 10); - } else if (strcmp(request->header_key, "Sec-WebSocket-Key") == 0 && + } else if (strcasecmp(request->header_key, "Sec-WebSocket-Key") == 0 && strlen(request->header_value) == 24) { strcpy(request->websocket_key, request->header_value); } From 09915ab0b987fc999666a354f3f0ace1ecd14064 Mon Sep 17 00:00:00 2001 From: Neradoc Date: Sun, 24 Jul 2022 15:15:10 +0200 Subject: [PATCH 4/4] compare all static ok hosts with port, add 127.0.0.1 and localhost to it use strncmp rather than memcmp, one of the strings coul be smaller than the other --- supervisor/shared/web_workflow/web_workflow.c | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index 11f00c125f..1fb8cdeff9 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -350,41 +350,40 @@ static bool _endswith(const char *str, const char *suffix) { return strcmp(str + (strlen(str) - strlen(suffix)), suffix) == 0; } -const char *ok_hosts[] = {"code.circuitpython.org"}; +const char *ok_hosts[] = { + "code.circuitpython.org", + "127.0.0.1", + "localhost", +}; static bool _origin_ok(const char *origin) { const char *http = "http://"; const char *local = ".local"; // note: redirected requests send an Origin of "null" and will be caught by this - if (memcmp(origin, http, strlen(http)) != 0) { + if (strncmp(origin, http, strlen(http)) != 0) { return false; } // These are prefix checks up to : so that any port works. const char *hostname = common_hal_mdns_server_get_hostname(&mdns); const char *end = origin + strlen(http) + strlen(hostname) + strlen(local); - if (memcmp(origin + strlen(http), hostname, strlen(hostname)) == 0 && - memcmp(origin + strlen(http) + strlen(hostname), local, strlen(local)) == 0 && + if (strncmp(origin + strlen(http), hostname, strlen(hostname)) == 0 && + strncmp(origin + strlen(http) + strlen(hostname), local, strlen(local)) == 0 && (end[0] == '\0' || end[0] == ':')) { return true; } end = origin + strlen(http) + strlen(_our_ip_encoded); - if (memcmp(origin + strlen(http), _our_ip_encoded, strlen(_our_ip_encoded)) == 0 && + if (strncmp(origin + strlen(http), _our_ip_encoded, strlen(_our_ip_encoded)) == 0 && (end[0] == '\0' || end[0] == ':')) { return true; } - const char *localhost = "127.0.0.1"; - end = origin + strlen(http) + strlen(localhost); - if (memcmp(origin + strlen(http), localhost, strlen(localhost)) == 0 - && (end[0] == '\0' || end[0] == ':')) { - return true; - } - for (size_t i = 0; i < MP_ARRAY_SIZE(ok_hosts); i++) { - // This checks exactly. - if (strcmp(origin + strlen(http), ok_hosts[i]) == 0) { + // Allows any port + end = origin + strlen(http) + strlen(ok_hosts[i]); + if (strncmp(origin + strlen(http), ok_hosts[i], strlen(ok_hosts[i])) == 0 + && (end[0] == '\0' || end[0] == ':')) { return true; } } @@ -911,7 +910,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } else if (strlen(request->origin) > 0 && !_origin_ok(request->origin)) { ESP_LOGE(TAG, "bad origin %s", request->origin); _reply_forbidden(socket, request); - } else if (memcmp(request->path, "/fs/", 4) == 0) { + } else if (strncmp(request->path, "/fs/", 4) == 0) { if (strcasecmp(request->method, "OPTIONS") == 0) { // OPTIONS is sent for CORS preflight, unauthenticated _reply_access_control(socket, request); @@ -1032,7 +1031,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } } } - } else if (memcmp(request->path, "/cp/", 4) == 0) { + } else if (strncmp(request->path, "/cp/", 4) == 0) { const char *path = request->path + 3; if (strcasecmp(request->method, "OPTIONS") == 0) { // handle preflight requests to /cp/ @@ -1177,7 +1176,7 @@ static void _process_request(socketpool_socket_obj_t *socket, _request *request) request->state = STATE_HEADER_KEY; if (strcasecmp(request->header_key, "Authorization") == 0) { const char *prefix = "Basic "; - request->authenticated = memcmp(request->header_value, prefix, strlen(prefix)) == 0 && + request->authenticated = strncmp(request->header_value, prefix, strlen(prefix)) == 0 && strcmp(_api_password, request->header_value + strlen(prefix)) == 0; } else if (strcasecmp(request->header_key, "Host") == 0) { request->redirect = strcmp(request->header_value, "circuitpython.local") == 0;