Harden HTTP path headers sessions and archives
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include <netdb.h>
|
||||
#include <execinfo.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <sys/file.h>
|
||||
#include "sys.h"
|
||||
#include "hash.h"
|
||||
@@ -182,6 +183,27 @@ String path_join(String base, String child)
|
||||
return(base + "/" + child);
|
||||
}
|
||||
|
||||
String path_real(String path)
|
||||
{
|
||||
char resolved[PATH_MAX];
|
||||
if(realpath(path.c_str(), resolved))
|
||||
return(String(resolved));
|
||||
return("");
|
||||
}
|
||||
|
||||
bool path_is_within(String path, String root)
|
||||
{
|
||||
String real_path = path_real(path);
|
||||
String real_root = path_real(root);
|
||||
if(real_path == "" || real_root == "")
|
||||
return(false);
|
||||
if(real_path == real_root)
|
||||
return(true);
|
||||
if(real_root[real_root.length() - 1] != '/')
|
||||
real_root += "/";
|
||||
return(str_starts_with(real_path, real_root));
|
||||
}
|
||||
|
||||
bool mkdir(String path)
|
||||
{
|
||||
shell_exec(String("mkdir -p ")+" "+shell_escape(path));
|
||||
@@ -877,6 +899,7 @@ StringMap make_server_settings()
|
||||
cfg["TRANSPORT_MAX_RESPONSE_BYTES"] = std::to_string(8 * 1024 * 1024);
|
||||
cfg["TRANSPORT_HTTP_REQUEST_TIMEOUT_SECONDS"] = "15";
|
||||
cfg["TRANSPORT_CONNECTION_IDLE_TIMEOUT_SECONDS"] = "120";
|
||||
cfg["HTTP_DOCUMENT_ROOT"] = "";
|
||||
cfg["CUSTOM_SERVER_MAX_SERVERS"] = "16";
|
||||
cfg["CUSTOM_SERVER_MIN_PORT"] = "1024";
|
||||
cfg["CUSTOM_SERVER_MAX_PORT"] = "65535";
|
||||
|
||||
@@ -9,6 +9,8 @@ String shell_escape(String raw);
|
||||
String basename(String fn);
|
||||
String dirname(String fn);
|
||||
String path_join(String base, String child);
|
||||
String path_real(String path);
|
||||
bool path_is_within(String path, String root);
|
||||
bool mkdir(String path);
|
||||
bool file_exists(String path);
|
||||
int file_open_locked(String file_name, int open_flags, int lock_type = LOCK_SH, int create_mode = 0644, f64 wait_timeout_seconds = 3.0, String purpose = "");
|
||||
|
||||
+63
-4
@@ -1,5 +1,8 @@
|
||||
#include "uri.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static String base64_encode(String raw)
|
||||
{
|
||||
static const char* chars =
|
||||
@@ -256,9 +259,33 @@ String encode_query(StringMap map)
|
||||
return(result);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
String http_header_value_clean(String value)
|
||||
{
|
||||
for(char& c : value)
|
||||
{
|
||||
if(c == '\r' || c == '\n')
|
||||
c = ' ';
|
||||
}
|
||||
return(value);
|
||||
}
|
||||
|
||||
String cookie_attribute_value_clean(String value)
|
||||
{
|
||||
for(char& c : value)
|
||||
{
|
||||
if(c == '\r' || c == '\n' || c == ';')
|
||||
c = ' ';
|
||||
}
|
||||
return(trim(value));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void redirect(String url, s32 code)
|
||||
{
|
||||
context->header["Location"] = url;
|
||||
context->header["Location"] = http_header_value_clean(url);
|
||||
context->set_status(code);
|
||||
}
|
||||
|
||||
@@ -499,9 +526,18 @@ void set_cookie(
|
||||
bool secure, bool http_only)
|
||||
{
|
||||
String cookie = "Set-Cookie: ";
|
||||
cookie.append(uri_encode(name) + "=" + uri_encode(value));
|
||||
cookie.append(uri_encode(cookie_attribute_value_clean(name)) + "=" + uri_encode(value));
|
||||
if(expires > 0)
|
||||
cookie.append(String("; Expires=") + time_format_utc("RFC1123", expires));
|
||||
if(path != "")
|
||||
cookie.append("; Path=" + cookie_attribute_value_clean(path));
|
||||
if(domain != "")
|
||||
cookie.append("; Domain=" + cookie_attribute_value_clean(domain));
|
||||
if(secure)
|
||||
cookie.append("; Secure");
|
||||
if(http_only)
|
||||
cookie.append("; HttpOnly");
|
||||
cookie.append("; SameSite=Lax");
|
||||
context->set_cookies.push_back(cookie);
|
||||
context->cookies[name] = value;
|
||||
}
|
||||
@@ -520,7 +556,30 @@ StringMap parse_cookies(String cookie_String)
|
||||
|
||||
String session_id_create()
|
||||
{
|
||||
return(to_hex(rand())+to_hex(rand())+to_hex(rand())+to_hex(rand()));
|
||||
unsigned char bytes[32];
|
||||
int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
|
||||
if(fd == -1)
|
||||
throw std::runtime_error("session_id_create(): could not open /dev/urandom");
|
||||
size_t offset = 0;
|
||||
while(offset < sizeof(bytes))
|
||||
{
|
||||
ssize_t count = read(fd, bytes + offset, sizeof(bytes) - offset);
|
||||
if(count <= 0)
|
||||
{
|
||||
close(fd);
|
||||
throw std::runtime_error("session_id_create(): could not read random bytes");
|
||||
}
|
||||
offset += (size_t)count;
|
||||
}
|
||||
close(fd);
|
||||
String result;
|
||||
static const char* hex = "0123456789abcdef";
|
||||
for(unsigned char b : bytes)
|
||||
{
|
||||
result.push_back(hex[b >> 4]);
|
||||
result.push_back(hex[b & 0x0f]);
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
bool is_valid_session_id(String session_id)
|
||||
@@ -602,7 +661,7 @@ String session_start(String session_name)
|
||||
context->session_name = "";
|
||||
|
||||
String session_id = context->cookies[session_name];
|
||||
if(!is_valid_session_id(session_id))
|
||||
if(!is_valid_session_id(session_id) || !file_exists(session_file_path(session_id)))
|
||||
session_id = "";
|
||||
|
||||
if(session_id.length() == 0)
|
||||
|
||||
+18
-2
@@ -122,6 +122,21 @@ String zip_read(String zip_file_name, String entry_name)
|
||||
if(!mz_zip_reader_init_file(&archive, zip_file_name.c_str(), 0))
|
||||
throw std::runtime_error(zip_error("zip_read", "could not open " + zip_file_name));
|
||||
|
||||
int file_index = mz_zip_reader_locate_file(&archive, entry_name.c_str(), NULL, 0);
|
||||
if(file_index < 0)
|
||||
{
|
||||
mz_zip_reader_end(&archive);
|
||||
throw std::runtime_error(zip_error("zip_read", "entry not found or not readable: " + entry_name));
|
||||
}
|
||||
mz_zip_archive_file_stat stat;
|
||||
std::memset(&stat, 0, sizeof(stat));
|
||||
if(!mz_zip_reader_file_stat(&archive, (mz_uint)file_index, &stat))
|
||||
{
|
||||
mz_zip_reader_end(&archive);
|
||||
throw std::runtime_error(zip_error("zip_read", "entry not found or not readable: " + entry_name));
|
||||
}
|
||||
archive_check_size("zip_read", "uncompressed entry", stat.m_uncomp_size, "ARCHIVE_MAX_OUTPUT_BYTES", 64 * 1024 * 1024);
|
||||
|
||||
size_t size = 0;
|
||||
void* data = mz_zip_reader_extract_file_to_heap(&archive, entry_name.c_str(), &size, 0);
|
||||
if(!data)
|
||||
@@ -129,7 +144,6 @@ String zip_read(String zip_file_name, String entry_name)
|
||||
mz_zip_reader_end(&archive);
|
||||
throw std::runtime_error(zip_error("zip_read", "entry not found or not readable: " + entry_name));
|
||||
}
|
||||
archive_check_size("zip_read", "uncompressed entry", size, "ARCHIVE_MAX_OUTPUT_BYTES", 64 * 1024 * 1024);
|
||||
String result((char*)data, size);
|
||||
mz_free(data);
|
||||
mz_zip_reader_end(&archive);
|
||||
@@ -305,9 +319,12 @@ String gz_compress(String src)
|
||||
|
||||
String gz_uncompress(String compressed)
|
||||
{
|
||||
archive_check_size("gz_uncompress", "input", compressed.size(), "ARCHIVE_MAX_INPUT_BYTES", 64 * 1024 * 1024);
|
||||
size_t deflate_offset = gz_deflate_offset(compressed);
|
||||
size_t footer_offset = compressed.size() - 8;
|
||||
size_t deflate_size = footer_offset - deflate_offset;
|
||||
u32 expected_size = gz_read_u32_le(compressed, footer_offset + 4);
|
||||
archive_check_size("gz_uncompress", "declared output", expected_size, "ARCHIVE_MAX_OUTPUT_BYTES", 64 * 1024 * 1024);
|
||||
size_t out_len = 0;
|
||||
void* out = tinfl_decompress_mem_to_heap(compressed.data() + deflate_offset, deflate_size, &out_len, 0);
|
||||
if(!out)
|
||||
@@ -318,7 +335,6 @@ String gz_uncompress(String compressed)
|
||||
mz_free(out);
|
||||
|
||||
u32 expected_crc = gz_read_u32_le(compressed, footer_offset);
|
||||
u32 expected_size = gz_read_u32_le(compressed, footer_offset + 4);
|
||||
u32 actual_crc = (u32)mz_crc32(MZ_CRC32_INIT, (const unsigned char*)result.data(), result.size());
|
||||
if(actual_crc != expected_crc)
|
||||
throw std::runtime_error("gz_uncompress(): crc check failed");
|
||||
|
||||
Reference in New Issue
Block a user