Skip to content

Commit

Permalink
CSRF (#637)
Browse files Browse the repository at this point in the history
* inititial CSRF fix
* allow all private ip ranges
* Allow cross private range

---------

Co-authored-by: Benjamin Wilson <admin@opensourceminer.com>
  • Loading branch information
benjamin-wilson and Benjamin Wilson authored Jan 13, 2025
1 parent b8123e8 commit a04c00b
Showing 1 changed file with 179 additions and 10 deletions.
189 changes: 179 additions & 10 deletions main/http_server/http_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,100 @@ typedef struct rest_server_context

#define CHECK_FILE_EXTENSION(filename, ext) (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0)

static esp_err_t ip_in_private_range(uint32_t ip){
//Private IP ranges (little endian, 192.168.0.0 => 0.0.168.192)
//192.168.0.0
uint32_t sixteen_bit_block = 0b00000000000000001010100011000000;
uint32_t sixteen_bit_mask = 0b00000000000000001111111111111111;

if((ip & sixteen_bit_mask) == sixteen_bit_block){
return ESP_OK;
}
//172.16.0.0
uint32_t twenty_bit_block = 0b00000000000000000001000010101100;
uint32_t twenty_bit_mask = 0b00000000000000001111000011111111;
if((ip & twenty_bit_mask) == twenty_bit_block){
return ESP_OK;
}
//10.0.0.0
uint32_t twenty_four_bit_block = 0b00000000000000000000000000001010;
uint32_t twenty_four_bit_mask = 0b00000000000000000000000011111111;
if((ip & twenty_four_bit_mask) == twenty_four_bit_block){
return ESP_OK;
}

return ESP_FAIL;
}
static esp_err_t check_is_same_network(httpd_req_t * req){

int sockfd = httpd_req_to_sockfd(req);
char ipstr[INET6_ADDRSTRLEN];
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
socklen_t addr_size = sizeof(addr);

if (getpeername(sockfd, (struct sockaddr *)&addr, &addr_size) < 0) {
ESP_LOGE(TAG, "Error getting client IP");
return ESP_FAIL;
}


uint32_t request_ip_addr = addr.sin6_addr.un.u32_addr[3];

// // Convert to IPv6 string
// inet_ntop(AF_INET, &addr.sin6_addr, ipstr, sizeof(ipstr));

// Convert to IPv4 string
inet_ntop(AF_INET, &request_ip_addr, ipstr, sizeof(ipstr));



char origin[128];
char ip_str[16]; // Buffer to hold the extracted IP address string
uint32_t origin_ip_addr = 0;
// Attempt to get the Origin header
if (httpd_req_get_hdr_value_str(req, "Origin", origin, sizeof(origin)) == ESP_OK) {
ESP_LOGI("CORS", "Origin header: %s", origin);

// Find the start of the IP address in the Origin header
const char *prefix = "http://";
char *ip_start = strstr(origin, prefix);
if (ip_start) {
ip_start += strlen(prefix); // Move past "http://"

// Extract the IP address portion (up to the next '/')
char *ip_end = strchr(ip_start, '/');
size_t ip_len = ip_end ? (size_t)(ip_end - ip_start) : strlen(ip_start);
if (ip_len < sizeof(ip_str)) {
strncpy(ip_str, ip_start, ip_len);
ip_str[ip_len] = '\0'; // Null-terminate the string

// Convert the IP address string to uint32_t
origin_ip_addr = inet_addr(ip_str);
if (origin_ip_addr == INADDR_NONE) {
ESP_LOGW("CORS", "Invalid IP address: %s", ip_str);
} else {
ESP_LOGI("CORS", "Extracted IP address %lu", origin_ip_addr);
}
} else {
ESP_LOGW("CORS", "IP address string is too long");
}
}
}else {
// Origin is sent for CSRF sensitive requests, if there is no header it's not a concern.
origin_ip_addr = request_ip_addr;
}


if(ip_in_private_range(origin_ip_addr) == ESP_OK && ip_in_private_range(request_ip_addr) == ESP_OK){
return ESP_OK;
}

ESP_LOGI(TAG, "Client is NOT in the private ip ranges or same range as server.");
return ESP_FAIL;

}


esp_err_t init_fs(void)
{
esp_vfs_spiffs_conf_t conf = {.base_path = "", .partition_label = NULL, .max_files = 5, .format_if_mount_failed = false};
Expand Down Expand Up @@ -118,6 +212,7 @@ static esp_err_t set_content_type_from_file(httpd_req_t * req, const char * file

static esp_err_t set_cors_headers(httpd_req_t * req)
{

esp_err_t err;

err = httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
Expand All @@ -141,6 +236,10 @@ static esp_err_t set_cors_headers(httpd_req_t * req)
/* Recovery handler */
static esp_err_t rest_recovery_handler(httpd_req_t * req)
{
if(check_is_same_network(req) != ESP_OK){
return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized");
}

httpd_resp_send(req, recovery_page, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
Expand Down Expand Up @@ -209,6 +308,10 @@ static esp_err_t rest_common_get_handler(httpd_req_t * req)

static esp_err_t handle_options_request(httpd_req_t * req)
{
if(check_is_same_network(req) != ESP_OK){
return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized");
}

// Set CORS headers for OPTIONS request
if (set_cors_headers(req) != ESP_OK) {
httpd_resp_send_500(req);
Expand All @@ -223,6 +326,11 @@ static esp_err_t handle_options_request(httpd_req_t * req)

static esp_err_t PATCH_update_settings(httpd_req_t * req)
{

if(check_is_same_network(req) != ESP_OK){
return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized");
}

// Set CORS headers
if (set_cors_headers(req) != ESP_OK) {
httpd_resp_send_500(req);
Expand Down Expand Up @@ -321,6 +429,10 @@ static esp_err_t PATCH_update_settings(httpd_req_t * req)

static esp_err_t POST_restart(httpd_req_t * req)
{
if(check_is_same_network(req) != ESP_OK){
return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized");
}

// Set CORS headers
if (set_cors_headers(req) != ESP_OK) {
httpd_resp_send_500(req);
Expand All @@ -346,6 +458,10 @@ static esp_err_t POST_restart(httpd_req_t * req)
/* Simple handler for getting system handler */
static esp_err_t GET_system_info(httpd_req_t * req)
{
if(check_is_same_network(req) != ESP_OK){
return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized");
}

httpd_resp_set_type(req, "application/json");

// Set CORS headers
Expand Down Expand Up @@ -453,6 +569,10 @@ static esp_err_t GET_system_info(httpd_req_t * req)

esp_err_t POST_WWW_update(httpd_req_t * req)
{
if(check_is_same_network(req) != ESP_OK){
return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized");
}

wifi_mode_t mode;
esp_wifi_get_mode(&mode);
if (mode == WIFI_MODE_AP || mode == WIFI_MODE_APSTA)
Expand Down Expand Up @@ -507,6 +627,10 @@ esp_err_t POST_WWW_update(httpd_req_t * req)
*/
esp_err_t POST_OTA_update(httpd_req_t * req)
{
if(check_is_same_network(req) != ESP_OK){
return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized");
}

wifi_mode_t mode;
esp_wifi_get_mode(&mode);
if (mode == WIFI_MODE_AP || mode == WIFI_MODE_APSTA)
Expand Down Expand Up @@ -626,6 +750,10 @@ void send_log_to_websocket(char *message)
*/
esp_err_t echo_handler(httpd_req_t * req)
{
if(check_is_same_network(req) != ESP_OK){
return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized");
}

if (req->method == HTTP_GET) {
ESP_LOGI(TAG, "Handshake done, the new connection was opened");
fd = httpd_req_to_sockfd(req);
Expand Down Expand Up @@ -700,15 +828,23 @@ esp_err_t start_rest_server(void * pvParameters)
REST_CHECK(httpd_start(&server, &config) == ESP_OK, "Start server failed", err_start);

httpd_uri_t recovery_explicit_get_uri = {
.uri = "/recovery", .method = HTTP_GET, .handler = rest_recovery_handler, .user_ctx = rest_context};
.uri = "/recovery",
.method = HTTP_GET,
.handler = rest_recovery_handler,
.user_ctx = rest_context
};
httpd_register_uri_handler(server, &recovery_explicit_get_uri);

// Register theme API endpoints
ESP_ERROR_CHECK(register_theme_api_endpoints(server, rest_context));

/* URI handler for fetching system info */
httpd_uri_t system_info_get_uri = {
.uri = "/api/system/info", .method = HTTP_GET, .handler = GET_system_info, .user_ctx = rest_context};
.uri = "/api/system/info",
.method = HTTP_GET,
.handler = GET_system_info,
.user_ctx = rest_context
};
httpd_register_uri_handler(server, &system_info_get_uri);

httpd_uri_t swarm_options_uri = {
Expand All @@ -720,15 +856,26 @@ esp_err_t start_rest_server(void * pvParameters)
httpd_register_uri_handler(server, &swarm_options_uri);

httpd_uri_t system_restart_uri = {
.uri = "/api/system/restart", .method = HTTP_POST, .handler = POST_restart, .user_ctx = rest_context};
.uri = "/api/system/restart", .method = HTTP_POST,
.handler = POST_restart,
.user_ctx = rest_context
};
httpd_register_uri_handler(server, &system_restart_uri);

httpd_uri_t system_restart_options_uri = {
.uri = "/api/system/restart", .method = HTTP_OPTIONS, .handler = handle_options_request, .user_ctx = NULL};
.uri = "/api/system/restart",
.method = HTTP_OPTIONS,
.handler = handle_options_request,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &system_restart_options_uri);

httpd_uri_t update_system_settings_uri = {
.uri = "/api/system", .method = HTTP_PATCH, .handler = PATCH_update_settings, .user_ctx = rest_context};
.uri = "/api/system",
.method = HTTP_PATCH,
.handler = PATCH_update_settings,
.user_ctx = rest_context
};
httpd_register_uri_handler(server, &update_system_settings_uri);

httpd_uri_t system_options_uri = {
Expand All @@ -740,25 +887,47 @@ esp_err_t start_rest_server(void * pvParameters)
httpd_register_uri_handler(server, &system_options_uri);

httpd_uri_t update_post_ota_firmware = {
.uri = "/api/system/OTA", .method = HTTP_POST, .handler = POST_OTA_update, .user_ctx = NULL};
.uri = "/api/system/OTA",
.method = HTTP_POST,
.handler = POST_OTA_update,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &update_post_ota_firmware);

httpd_uri_t update_post_ota_www = {
.uri = "/api/system/OTAWWW", .method = HTTP_POST, .handler = POST_WWW_update, .user_ctx = NULL};
.uri = "/api/system/OTAWWW",
.method = HTTP_POST,
.handler = POST_WWW_update,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &update_post_ota_www);

httpd_uri_t ws = {.uri = "/api/ws", .method = HTTP_GET, .handler = echo_handler, .user_ctx = NULL, .is_websocket = true};
httpd_uri_t ws = {
.uri = "/api/ws",
.method = HTTP_GET,
.handler = echo_handler,
.user_ctx = NULL,
.is_websocket = true
};
httpd_register_uri_handler(server, &ws);

if (enter_recovery) {
/* Make default route serve Recovery */
httpd_uri_t recovery_implicit_get_uri = {
.uri = "/*", .method = HTTP_GET, .handler = rest_recovery_handler, .user_ctx = rest_context};
.uri = "/*", .method = HTTP_GET,
.handler = rest_recovery_handler,
.user_ctx = rest_context
};
httpd_register_uri_handler(server, &recovery_implicit_get_uri);

} else {
/* URI handler for getting web server files */
httpd_uri_t common_get_uri = {.uri = "/*", .method = HTTP_GET, .handler = rest_common_get_handler, .user_ctx = rest_context};
httpd_uri_t common_get_uri = {
.uri = "/*",
.method = HTTP_GET,
.handler = rest_common_get_handler,
.user_ctx = rest_context
};
httpd_register_uri_handler(server, &common_get_uri);
}

Expand Down

0 comments on commit a04c00b

Please sign in to comment.