diff options
author | 2014-01-16 19:39:17 (JST) | |
---|---|---|
committer | 2014-01-16 20:13:39 (JST) | |
commit | b826537cb4aa2358027ffcb1dd6a87274734e962 (patch) | |
tree | 7c749c66d868cb996828d2b65a4bede58b5ebd62 | |
parent | d6e9200cc35411f3f27426b608bcfdef9348e6d3 (diff) | |
download | cgit-b826537cb4aa2358027ffcb1dd6a87274734e962.zip cgit-b826537cb4aa2358027ffcb1dd6a87274734e962.tar.gz |
authentication: use hidden form instead of referer
This also gives us some CSRF protection. Note that we make use of the
hmac to protect the redirect value.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r-- | cgit.c | 22 | ||||
-rw-r--r-- | cgitrc.5.txt | 3 | ||||
-rw-r--r-- | filters/simple-authentication.lua | 200 |
3 files changed, 131 insertions, 94 deletions
@@ -614,22 +614,19 @@ static inline void open_auth_filter(struct cgit_context *ctx, const char *functi | |||
614 | ctx->qry.url ? ctx->qry.url : ""); | 614 | ctx->qry.url ? ctx->qry.url : ""); |
615 | } | 615 | } |
616 | 616 | ||
617 | /* We intentionally keep this rather small, instead of looping and | ||
618 | * feeding it to the filter a couple bytes at a time. This way, the | ||
619 | * filter itself does not need to handle any denial of service or | ||
620 | * buffer bloat issues. If this winds up being too small, people | ||
621 | * will complain on the mailing list, and we'll increase it as needed. */ | ||
617 | #define MAX_AUTHENTICATION_POST_BYTES 4096 | 622 | #define MAX_AUTHENTICATION_POST_BYTES 4096 |
623 | /* The filter is expected to spit out "Status: " and all headers. */ | ||
618 | static inline void authenticate_post(struct cgit_context *ctx) | 624 | static inline void authenticate_post(struct cgit_context *ctx) |
619 | { | 625 | { |
620 | if (ctx->env.http_referer && strlen(ctx->env.http_referer) > 0) { | ||
621 | html("Status: 302 Redirect\n"); | ||
622 | html("Cache-Control: no-cache, no-store\n"); | ||
623 | htmlf("Location: %s\n", ctx->env.http_referer); | ||
624 | } else { | ||
625 | html("Status: 501 Missing Referer\n"); | ||
626 | html("Cache-Control: no-cache, no-store\n\n"); | ||
627 | exit(0); | ||
628 | } | ||
629 | |||
630 | open_auth_filter(ctx, "authenticate-post"); | ||
631 | char buffer[MAX_AUTHENTICATION_POST_BYTES]; | 626 | char buffer[MAX_AUTHENTICATION_POST_BYTES]; |
632 | int len; | 627 | int len; |
628 | |||
629 | open_auth_filter(ctx, "authenticate-post"); | ||
633 | len = ctx->env.content_length; | 630 | len = ctx->env.content_length; |
634 | if (len > MAX_AUTHENTICATION_POST_BYTES) | 631 | if (len > MAX_AUTHENTICATION_POST_BYTES) |
635 | len = MAX_AUTHENTICATION_POST_BYTES; | 632 | len = MAX_AUTHENTICATION_POST_BYTES; |
@@ -637,10 +634,7 @@ static inline void authenticate_post(struct cgit_context *ctx) | |||
637 | die_errno("Could not read POST from stdin"); | 634 | die_errno("Could not read POST from stdin"); |
638 | if (write(STDOUT_FILENO, buffer, len) < 0) | 635 | if (write(STDOUT_FILENO, buffer, len) < 0) |
639 | die_errno("Could not write POST to stdout"); | 636 | die_errno("Could not write POST to stdout"); |
640 | /* The filter may now spit out a Set-Cookie: ... */ | ||
641 | cgit_close_filter(ctx->cfg.auth_filter); | 637 | cgit_close_filter(ctx->cfg.auth_filter); |
642 | |||
643 | html("\n"); | ||
644 | exit(0); | 638 | exit(0); |
645 | } | 639 | } |
646 | 640 | ||
diff --git a/cgitrc.5.txt b/cgitrc.5.txt index c45dbd3..682d8bb 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt | |||
@@ -662,7 +662,8 @@ auth filter:: | |||
662 | the http cookie and return a 0 if it is invalid or 1 if it is invalid, | 662 | the http cookie and return a 0 if it is invalid or 1 if it is invalid, |
663 | in the exit code / close function. If the filter action is | 663 | in the exit code / close function. If the filter action is |
664 | "authenticate-post", this filter receives POST'd parameters on | 664 | "authenticate-post", this filter receives POST'd parameters on |
665 | standard input, and should write to output one or more "Set-Cookie" | 665 | standard input, and should write a complete CGI request, preferably |
666 | with a 302 redirect, and write to output one or more "Set-Cookie" | ||
666 | HTTP headers, each followed by a newline. | 667 | HTTP headers, each followed by a newline. |
667 | 668 | ||
668 | Please see `filters/simple-authentication.lua` for a clear example | 669 | Please see `filters/simple-authentication.lua` for a clear example |
diff --git a/filters/simple-authentication.lua b/filters/simple-authentication.lua index 4cd4983..5935d08 100644 --- a/filters/simple-authentication.lua +++ b/filters/simple-authentication.lua | |||
@@ -33,15 +33,28 @@ local secret = "BE SURE TO CUSTOMIZE THIS STRING TO SOMETHING BIG AND RANDOM" | |||
33 | -- | 33 | -- |
34 | -- | 34 | -- |
35 | 35 | ||
36 | -- Sets HTTP cookie headers based on post | 36 | -- Sets HTTP cookie headers based on post and sets up redirection. |
37 | function authenticate_post() | 37 | function authenticate_post() |
38 | local password = users[post["username"]] | 38 | local password = users[post["username"]] |
39 | -- TODO: Implement time invariant string comparison function to mitigate against timing attack. | 39 | local redirect = validate_value(post["redirect"]) |
40 | |||
41 | if redirect == nil then | ||
42 | not_found() | ||
43 | return 0 | ||
44 | end | ||
45 | |||
46 | redirect_to(redirect) | ||
47 | |||
48 | -- TODO: Implement time invariant string comparison function to mitigate timing attack. | ||
40 | if password == nil or password ~= post["password"] then | 49 | if password == nil or password ~= post["password"] then |
41 | construct_cookie("", "cgitauth") | 50 | set_cookie("cgitauth", "") |
42 | else | 51 | else |
43 | construct_cookie(post["username"], "cgitauth") | 52 | -- One week expiration time |
53 | local username = secure_value(post["username"], os.time() + 604800) | ||
54 | set_cookie("cgitauth", username) | ||
44 | end | 55 | end |
56 | |||
57 | html("\n") | ||
45 | return 0 | 58 | return 0 |
46 | end | 59 | end |
47 | 60 | ||
@@ -54,8 +67,8 @@ function authenticate_cookie() | |||
54 | return 1 | 67 | return 1 |
55 | end | 68 | end |
56 | 69 | ||
57 | local username = validate_cookie(get_cookie(http["cookie"], "cgitauth")) | 70 | local username = validate_value(get_cookie(http["cookie"], "cgitauth")) |
58 | if username == nil or not accepted_users[username] then | 71 | if username == nil or not accepted_users[username:lower()] then |
59 | return 0 | 72 | return 0 |
60 | else | 73 | else |
61 | return 1 | 74 | return 1 |
@@ -68,6 +81,9 @@ function body() | |||
68 | html("<form method='post' action='") | 81 | html("<form method='post' action='") |
69 | html_attr(cgit["login"]) | 82 | html_attr(cgit["login"]) |
70 | html("'>") | 83 | html("'>") |
84 | html("<input type='hidden' name='redirect' value='") | ||
85 | html_attr(secure_value(cgit["url"], 0)) | ||
86 | html("' />") | ||
71 | html("<table>") | 87 | html("<table>") |
72 | html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>") | 88 | html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>") |
73 | html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>") | 89 | html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>") |
@@ -78,81 +94,10 @@ function body() | |||
78 | end | 94 | end |
79 | 95 | ||
80 | 96 | ||
81 | -- | ||
82 | -- | ||
83 | -- Cookie construction and validation helpers. | ||
84 | -- | ||
85 | -- | ||
86 | |||
87 | local crypto = require("crypto") | ||
88 | |||
89 | -- Returns username of cookie if cookie is valid. Otherwise returns nil. | ||
90 | function validate_cookie(cookie) | ||
91 | local i = 0 | ||
92 | local username = "" | ||
93 | local expiration = 0 | ||
94 | local salt = "" | ||
95 | local hmac = "" | ||
96 | |||
97 | if cookie:len() < 3 or cookie:sub(1, 1) == "|" then | ||
98 | return nil | ||
99 | end | ||
100 | |||
101 | for component in string.gmatch(cookie, "[^|]+") do | ||
102 | if i == 0 then | ||