add native csrf helpers
This commit is contained in:
parent
125a24e2f9
commit
b8c2efe976
@ -40,7 +40,9 @@ PUBLIC_APIS = [
|
||||
("crypto_equal", True, "public"), ("gen_noise32", True, "public"), ("gen_noise64", True, "public"),
|
||||
("gen_noise01", True, "public"), ("gen_int", True, "public"), ("gen_float", True, "public"),
|
||||
("draw_int", True, "public"), ("draw_float", True, "public"),
|
||||
("encode_query", True, "public"), ("request_script_url", True, "public"),
|
||||
("encode_query", True, "public"), ("csrf_token", True, "public"),
|
||||
("csrf_valid", True, "public"), ("csrf_rotate", True, "public"),
|
||||
("request_script_url", True, "public"),
|
||||
("request_base_url", True, "public"), ("request_route_from_raw_path", True, "public"),
|
||||
("cli_arg", True, "public"), ("unit_compile", True, "public"),
|
||||
("cleanup_sqlite_connections", False, "internal"), ("cleanup_mysql_connections", False, "internal"),
|
||||
|
||||
21
site/doc/pages/csrf_rotate.txt
Normal file
21
site/doc/pages/csrf_rotate.txt
Normal file
@ -0,0 +1,21 @@
|
||||
:sig
|
||||
void csrf_rotate(String session_name = "uce-session", String token_name = "csrf_token")
|
||||
|
||||
:params
|
||||
session_name : session/cookie name used by `csrf_token()`
|
||||
token_name : form/action token namespace to clear
|
||||
|
||||
:content
|
||||
Starts the named session if needed and clears the stored CSRF token for `token_name`. The next `csrf_token()` call creates a new token. Rotate after sensitive successful mutations when replaying the same submitted form should fail.
|
||||
|
||||
:example
|
||||
String old_token = csrf_token();
|
||||
csrf_rotate();
|
||||
String new_token = csrf_token();
|
||||
print(old_token != new_token ? "rotated\n" : "unchanged\n");
|
||||
|
||||
:see
|
||||
>http
|
||||
csrf_token
|
||||
csrf_valid
|
||||
session_start
|
||||
26
site/doc/pages/csrf_token.txt
Normal file
26
site/doc/pages/csrf_token.txt
Normal file
@ -0,0 +1,26 @@
|
||||
:sig
|
||||
String csrf_token(String session_name = "uce-session", String token_name = "csrf_token")
|
||||
|
||||
:params
|
||||
session_name : session/cookie name to store the token under
|
||||
token_name : form/action token namespace inside the session
|
||||
return value : printable CSRF token for the active session
|
||||
|
||||
:content
|
||||
Starts the named session if needed and returns a stable per-session CSRF token for `token_name`. Use it in hidden fields on forms that mutate server state, then verify the submitted value with `csrf_valid()` before applying the change.
|
||||
|
||||
Tokens are generated from cryptographic randomness and stored in `context.session` under an internal key. Use a different `token_name` when independent forms should have independent tokens.
|
||||
|
||||
:example
|
||||
String token = csrf_token("uce-doc-session");
|
||||
?><form method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= token ?>">
|
||||
<button type="submit">Save</button>
|
||||
</form><?
|
||||
|
||||
:see
|
||||
>http
|
||||
csrf_valid
|
||||
csrf_rotate
|
||||
session_start
|
||||
random_bytes
|
||||
25
site/doc/pages/csrf_valid.txt
Normal file
25
site/doc/pages/csrf_valid.txt
Normal file
@ -0,0 +1,25 @@
|
||||
:sig
|
||||
bool csrf_valid(String submitted_token, String session_name = "uce-session", String token_name = "csrf_token")
|
||||
|
||||
:params
|
||||
submitted_token : value received from the request, usually `context.post["csrf_token"]`
|
||||
session_name : session/cookie name used by `csrf_token()`
|
||||
token_name : form/action token namespace used by `csrf_token()`
|
||||
return value : `true` when the submitted token matches the session token
|
||||
|
||||
:content
|
||||
Starts the named session if needed and checks `submitted_token` against the stored CSRF token using constant-time comparison. It does not create a token when none exists, so a request without a previously rendered form token fails closed.
|
||||
|
||||
Use this before mutating state from POST forms or other browser-submitted requests.
|
||||
|
||||
:example
|
||||
String token = csrf_token("uce-doc-session");
|
||||
print(csrf_valid(token, "uce-doc-session") ? "valid\n" : "invalid\n");
|
||||
print(csrf_valid("bad-token", "uce-doc-session") ? "bad valid\n" : "bad invalid\n");
|
||||
|
||||
:see
|
||||
>http
|
||||
csrf_token
|
||||
csrf_rotate
|
||||
crypto_equal
|
||||
session_start
|
||||
@ -34,6 +34,22 @@ RENDER(Request& context)
|
||||
String malformed_percent = uri_decode("% %A %GG ok%20done");
|
||||
URI empty_uri = parse_uri("");
|
||||
String generated_session = session_id_create();
|
||||
String csrf = "";
|
||||
String csrf_rotated = "";
|
||||
bool csrf_ok = false;
|
||||
bool csrf_bad = false;
|
||||
bool csrf_old_after_rotate = false;
|
||||
bool csrf_new_after_rotate = false;
|
||||
if(action != "destroy")
|
||||
{
|
||||
csrf = csrf_token("uce-site-tests", "http_form");
|
||||
csrf_ok = csrf_valid(csrf, "uce-site-tests", "http_form");
|
||||
csrf_bad = csrf_valid("not-" + csrf, "uce-site-tests", "http_form");
|
||||
csrf_rotate("uce-site-tests", "http_form");
|
||||
csrf_old_after_rotate = csrf_valid(csrf, "uce-site-tests", "http_form");
|
||||
csrf_rotated = csrf_token("uce-site-tests", "http_form");
|
||||
csrf_new_after_rotate = csrf_rotated != "" && csrf_rotated != csrf && csrf_valid(csrf_rotated, "uce-site-tests", "http_form");
|
||||
}
|
||||
String query_dump = var_dump(query);
|
||||
String set_cookie_dump = var_dump(context.set_cookies);
|
||||
String header_dump = var_dump(context.header);
|
||||
@ -55,6 +71,8 @@ RENDER(Request& context)
|
||||
check("response header mutation", context.header["X-Site-Tests"] == "http-suite", header_dump);
|
||||
check("session_start()", session_id != "", "session_id=" + session_id);
|
||||
check("session_id_create()", generated_session.length() >= 16, generated_session);
|
||||
if(action != "destroy")
|
||||
check("csrf_token() / csrf_valid() / csrf_rotate()", csrf.length() >= 32 && csrf_ok && !csrf_bad && !csrf_old_after_rotate && csrf_new_after_rotate, "csrf=" + csrf + " rotated=" + csrf_rotated);
|
||||
|
||||
if(action == "destroy")
|
||||
check("session_destroy()", session_dump.find("suite") == String::npos && context.session_id == "" && context.session_name == "" && !file_exists(session_path), session_dump + " path=" + session_path);
|
||||
|
||||
@ -877,6 +877,39 @@ void session_destroy(String session_name)
|
||||
}
|
||||
}
|
||||
|
||||
String csrf_session_key(String token_name)
|
||||
{
|
||||
token_name = first(trim(token_name), "csrf_token");
|
||||
return("_csrf_" + token_name);
|
||||
}
|
||||
|
||||
String csrf_token(String session_name, String token_name)
|
||||
{
|
||||
session_start(session_name);
|
||||
String key = csrf_session_key(token_name);
|
||||
if(context->session[key] == "")
|
||||
{
|
||||
String random = random_bytes(32);
|
||||
if(random.size() != 32)
|
||||
random = session_id_create();
|
||||
context->session[key] = sha256_hex(random);
|
||||
}
|
||||
return(context->session[key]);
|
||||
}
|
||||
|
||||
bool csrf_valid(String submitted_token, String session_name, String token_name)
|
||||
{
|
||||
session_start(session_name);
|
||||
String expected = context->session[csrf_session_key(token_name)];
|
||||
return(expected != "" && submitted_token != "" && crypto_equal(submitted_token, expected));
|
||||
}
|
||||
|
||||
void csrf_rotate(String session_name, String token_name)
|
||||
{
|
||||
session_start(session_name);
|
||||
context->session.erase(csrf_session_key(token_name));
|
||||
}
|
||||
|
||||
String ws_make_accept_key(String client_key)
|
||||
{
|
||||
return(base64_encode(gen_sha1(
|
||||
|
||||
@ -33,6 +33,9 @@ StringMap load_session_data(String session_id);
|
||||
void save_session_data(String session_id, StringMap data);
|
||||
String session_start(String session_name = "uce-session");
|
||||
void session_destroy(String session_name = "uce-session");
|
||||
String csrf_token(String session_name = "uce-session", String token_name = "csrf_token");
|
||||
bool csrf_valid(String submitted_token, String session_name = "uce-session", String token_name = "csrf_token");
|
||||
void csrf_rotate(String session_name = "uce-session", String token_name = "csrf_token");
|
||||
String ws_make_accept_key(String client_key);
|
||||
bool ws_is_valid_client_key(String client_key);
|
||||
String ws_encode_frame(String payload, u8 opcode = 0x1, bool is_final_fragment = true);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user