-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature]: Implement CSRF token validation on oauth2-proxy #2573
Comments
Introduce field CSRFToken in session state struct. Generate a nonce and polulate this field when session is created. Signed-off-by: Athina Plaskasoviti <[email protected]>
Signed-off-by: Athina Plaskasoviti <[email protected]>
Expose /oauth2/csrftoken endpoint to fetch the CSRF token of the current session. Signed-off-by: Athina Plaskasoviti <[email protected]>
Signed-off-by: Athina Plaskasoviti <[email protected]>
Signed-off-by: Athina Plaskasoviti <[email protected]>
Signed-off-by: Athina Plaskasoviti <[email protected]>
Signed-off-by: Athina Plaskasoviti <[email protected]>
Add header X-CSRF-Token to every request if session has a CSRF token. Signed-off-by: Athina Plaskasoviti <[email protected]>
Signed-off-by: Athina Plaskasoviti <[email protected]>
Signed-off-by: Athina Plaskasoviti <[email protected]>
Context and CSRF implementationCSRF Token and Session StateSession in
There is no explicit session ID in either case, however there is the Using the
So is seems that the Therefore we introduced a new Apart from the above, For this purpose, we exposed the Returning the CSRF token to the client applicationOnce the CSRF token is generated and saved along with the session, it is necessary for the token to reach the client application. The ways to return a CSRF token to the client application are:
Note that the above can be used safely to return a CSRF token to the client, given that the proxy will expect client tokens in a custom header. Token value cannot be exploited e.g. by Below I will describe the implementation for each. Return as cookieTo return the CSRF token as cookie, we need to first construct the cookie. oauth2-proxy has the following Cookie struct: // Cookie contains configuration options relating to Cookie configuration
type Cookie struct {
Name string `flag:"cookie-name" cfg:"cookie_name"`
Secret string `flag:"cookie-secret" cfg:"cookie_secret"`
Domains []string `flag:"cookie-domain" cfg:"cookie_domains"`
Path string `flag:"cookie-path" cfg:"cookie_path"`
Expire time.Duration `flag:"cookie-expire" cfg:"cookie_expire"`
Refresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh"`
Secure bool `flag:"cookie-secure" cfg:"cookie_secure"`
HTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"`
SameSite string `flag:"cookie-samesite" cfg:"cookie_samesite"`
CSRFPerRequest bool `flag:"cookie-csrf-per-request" cfg:"cookie_csrf_per_request"`
CSRFExpire time.Duration `flag:"cookie-csrf-expire" cfg:"cookie_csrf_expire"`
} which is used to hold the necessary options to construct the session cookie using function This is the CSRF token struct type CSRFToken struct {
CSRFToken bool `flag:"csrftoken" cfg:"csrftoken"`
CookieName string `flag:"csrftoken-cookie-name" cfg:"csrftoken_cookie_name"`
CookieDomains []string `flag:"csrftoken-cookie-domain" cfg:"csrftoken_cookie_domain"`
CookiePath string `flag:"csrftoken-cookie-path" cfg:"csrftoken_cookie_path"`
CookieExpire time.Duration `flag:"csrftoken-cookie-expire" cfg:"csrftoken_cookie_expire"`
CookieSecure bool `flag:"csrftoken-cookie-secure" cfg:"csrftoken_cookie_secure"`
CookieHTTPOnly bool `flag:"csrftoken-cookie-httponly" cfg:"csrftoken_cookie_httponly"`
CookieSameSite string `flag:"csrftoken-cookie-samesite" cfg:"csrftoken_cookie_samesite"`
} We have also implemented a
Return as headerTo return the CSRF token as a custom header, we introduced the:
that injects the header with the CSRF token value from Return as JSON responseTo return the CSRF token as a JSON response, we exposed the The purpose of this endpoint is to use right after the finish of the OAuth2 cycle, before making any requests to the upstream server. A frontend application can place an Ajax request to fetch the CSRF token and then include it in subsequent requests to the server. CSRF validationThe validation of CSRF tokens is necessary if all of the following apply:
Focusing on the last item, CSRF vulnerabilities happen in client applications (UI) and don't concern programmatic client requests, that authenticate with bearer tokens via authorization headers. This information is necessary when deciding to apply CSRF validation and is currently not implemented in Authentication in The above summarize the necessity of the following newly introduced argument:
Furthermore,
Skip validationIn many setups, is it necessary to skip validation for some routes. To allow this functionality, we exposed the:
This will construct a list of method/path combinations that requests should be allowed without CSRF validation. Note that we can completely disable CSRF validation, but not CSRF token generation, by including all paths in this argument. |
Introduce field CSRFToken in the session state struct. The CSRF token will be a random string (nonce) that is generated and populates the CSRFToken field when a session is created. Furthermore, introduce flag --csrftoken (default: false) to control CSRF token generation in a session. Co-authored-by: Athina Plaskasoviti <[email protected]> Co-authored-by: Konstantinos Lolos <[email protected]>
Expose GET /oauth2/csrftoken endpoint to fetch the CSRF token of the current session. If CSRF token generation is disabled (which is the default behavior currently), this endpoint will respond with 404 NotFound. The endpoint needs authentication, i.e. expects a valid session to exist. Co-authored-by: Athina Plaskasoviti <[email protected]> Co-authored-by: Konstantinos Lolos <[email protected]>
Introduce helpers that will wrap the CSRF token in a cookie. Set the cookie along with the session cookie, when the callback function is called. Expose the following flags to configure CSRF token cookie attributes: * --csrftoken-cookie-name: the name of the CSRF token cookie (default: _oauth2_proxy_csrftoken). If set to an empty string, the functionality is disabled. * --csrftoken-cookie-domain: the domain(s) of the cookie (optional, default "") * --csrftoken-cookie-path: the path of the cookie (optional, default: /) * --csrftoken-cookie-expire: expiration of the cookie (default: 168 hours) * --csrftoken-cookie-secure: set secure (HTTPS) cookie (default: true) * --csrftoken-cookie-httponly: set HTTPOnly for cookie (default: false) * --csrftoken-cookie-samesite: set SameSite for cookie (default: strict) Note that a CSRF cookie should be secure but not HttpOnly, since clients are expected to retrieve the value of the token to send in subsequent requests. Set SameSite to strict by default. Co-authored-by: Athina Plaskasoviti <[email protected]> Co-authored-by: Konstantinos Lolos <[email protected]>
Expose flag --csrftoken-response-header (string) to allow returning the session CSRF token as a custom header. Default header name is X-CSRF-Token, which the proxy adds to every request if the session has a CSRF token. If --csrftoken-response-header is set to an empty string, the functionality is disabled. Co-authored-by: Athina Plaskasoviti <[email protected]> Co-authored-by: Konstantinos Lolos <[email protected]>
Introduce field AuthMethod in request scope. Allow middlewares to store the authentication method in AuthMethod field of the RequestScope. It is necessary for the proxy to be aware if authentication was achieved through a cookie or a header, as only requests that are authenticated via cookie need CSRF protection, while programmatic client requests that use an authorization header do not need validation of CSRF tokens. Co-authored-by: Athina Plaskasoviti <[email protected]> Co-authored-by: Konstantinos Lolos <[email protected]>
Implement CSRF token validation on oauth2-proxy and expose flag --csrftoken-header (string) to allow setting the name of the CSRF token header expected in the client requests. The proxy implements the Synchonizer Token Pattern for CSRF protection: * reads the CSRF token from the header defined by --csrftoken-header * compares it with the token in the current session (session cookie or Redis). If the tokens are the same, request is authenticated, else denied. CSRF token validation is applied when all of the following hold: * when --csrftoken=true * in requests with unsafe methods, i.e. requests that the client does not request, and does not expect, any state change on the origin server as a result of applying a safe method to a target resource, according to RFC 7231. * in requests that are authenticated via cookie Co-authored-by: Athina Plaskasoviti <[email protected]> Co-authored-by: Konstantinos Lolos <[email protected]>
) Introduce flag --skip-csrftoken-route which accepts a string or a list of string METHOD=PATH combinations, to exclude from CSRF validation in the proxy. To completely disable CSRF token validation, set --skip-csrftoken-route="*". Co-authored-by: Athina Plaskasoviti <[email protected]> Co-authored-by: Konstantinos Lolos <[email protected]>
Extend the Overview page to include usage of arguments: * --csrftoken * --csrftoken-cookie-name * --csrftoken-cookie-path * --csrftoken-cookie-domain * --csrftoken-cookie-expire * --csrftoken-cookie-secure * --csrftoken-cookie-httponly * --csrftoken-cookie-samesite * --csrftoken-header * --csrftoken-response-header * --skip-csrftoken-route that were introduced in previous commits. Co-authored-by: Athina Plaskasoviti <[email protected]> Co-authored-by: Konstantinos Lolos <[email protected]>
Pending itemsThe above summarize the operation of the CSRF implementation on the proxy. However, since there are cases that
to the upstream. This information is necessary if the upstream server implements CSRF and doesn't need We have already started working on the above and will add the commits to the PR as soon as they're ready. |
Modify the injector interface to handle request scope instead of session state. This makes the interface more flexible to retrieving values for header injection. The session state can be obtained from the request scope. Co-authored-by: Athina Plaskasoviti <[email protected]> Co-authored-by: Konstantinos Lolos <[email protected]>
Allow forwarding the authentication method from the request scope to upstream. This is necessary in case the CSRF token validation is implemented on the upstream, and not necessary to enable on the proxy, so upstream can decide when to apply or skip validation, since only requests that are authenticated by cookie should be subjected to CSRF protection. Co-authored-by: Athina Plaskasoviti <[email protected]> Co-authored-by: Konstantinos Lolos <[email protected]>
…2-proxy#2573) Remove the CSRF token header from the request if oauth2-proxy has been configured to run with --skip-auth-strip-headers=true Co-authored-by: Athina Plaskasoviti <[email protected]> Co-authored-by: Konstantinos Lolos <[email protected]>
Authentication method headerIf CSRF validation is implemented on the upstream, there is no need to enable the feature in the proxy. However, not all requests are subject to CSRF checks - as we mentioned in previous comments:
Since we have added Currently the headers middleware that handles adding headers to requests forwarded to upstreams can get header values either from the session ( Furthermore, it is necessary for the header injector interface to have access to the scope. Currently the injector uses the Remove client CSRF token header from request if
|
Signed-off-by: Athina Plaskasoviti <[email protected]>
Signed-off-by: Athina Plaskasoviti <[email protected]>
Motivation
Hello all!
I am opening this issue to discuss implementing CSRF token validation on the oauth2-proxy side.
Our team is looking to enhance the security of our applications and extending oauth2-proxy seems like the best way forward for the following reasons:
We understand that currently CSRF protection is missing from oauth2-proxy:
SameSite
cookie attribute, however this is not enough to prevent cross-origin attacks, as explained both in this issue Cross-Origin Request Forgery #2081 and this interesting CVE writeup https://jub0bs.com/posts/2022-02-08-cve-2022-21703-writeup/.I will elaborate on the implementation plan and reasoning for our proposals below. We are really looking forward to your feedback and suggestions.
Possible solution
Going through the OWASP documentation on CSRF, the most prominent options for CSRF token mechanism are the following:
From the aforementioned options, we believe that the Synchronizer Token Pattern is the best pick for implementing CSRF protection in oauth2-proxy. Oauth2-proxy can operate either standalone or using a Redis instance, but in both cases the state is retained (either in the DB or in the cookie), forming a stateful approach.
What are our options to return the CSRF token?
A server can return a CSRF token to the client:
Return as cookie
Returning the CSRF token as a cookie should be an option for our implementation.
Return as form field
Returning the CSRF token in a form field is not a viable option for oauth2-proxy, as it is unaware of the HTML being served.
Return as custom header
Returning CSRF token as a custom header should also be one the options in our implementation.
Return as JSON
Finally, we could expose a dedicated endpoint to oauth2-proxy to return CSRF token. Clients will be able to hit that endpoint with a GET request immediately after a successful login and session establishment. The endpoint must require authentication.
Based on the oauth2-proxy documentation, this endpoint could live under the /oauth2 prefix, for example /oauth2/csrftoken.
What should the token be?
For the Synchronizer Token Pattern, a cryptographically strong random string is suggested in OWASP docs. Other suggestions include a session dependent value signed by a strong algorithm (HMAC is preferred), for example the session ID, which is unique and lasts as long as the session is active.
How should oauth2-proxy get the token from client requests?
Oauth2-proxy should expect incoming requests to have a custom header with the CSRF token. If a request is missing the header, or header value is invalid, CSRF validation shall fail.
Should oauth2-proxy send tokens to upstream?
oauth2-proxy is an intermediate between the client and the backend and in many cases, it is the application backend that responds to the client. So, in this case oauth2-proxy should also be able to forward CSRF tokens to the application.
This would be useful in case the upstream application wants to include the CSRF token in a response (e.g., an HTML form), or even perform the validation on its own.
Oauth2-proxy arguments for CSRF configuration
Following are our initial suggestions on oauth2-proxy arguments that will configure the CSRF feature operation.
Note that for the following suggestions we used the
--csrftoken
prefix, to avoid conflicts and confusion around the--cookie-csrf-*
CLI arguments that handle login CSRF.Also, to avoid confusion, mentions to actual CSRF token refer to the tokens generated by oauth2-proxy, while client CSRF token refer to the tokens fetched from the client requests.
--csrftoken
boolEnable CSRF token generation and validation mechanism in oauth2-proxy.
Default: false
--csrftoken-cookie-name
stringThe name of the CSRF cookie. If set to empty string, no CSRF cookie will be set by oauth2-proxy.
Default: _oauth2_proxy_csrftoken
--csrftoken-cookie-httponly
boolEnable HttpOnly for CSRF cookie.
Default: false
We can probably omit this setting since a CSRF cookie should be
HttpOnly=false
by default, so exposing a setting might be unnecessary for the time being.--csrftoken-cookie-samesite
”strict”/”lax”/”none”/””Enable SameSite for CSRF cookie.
Default: strict
--csrftoken-cookie-secure
boolEnable Secure for CSRF cookie.
Default: true
--csrftoken-header
stringThe name of the header for holding the CSRF token sent from the client.
Default: X-CSRF-Token
--pass-csrftoken-header
boolPass CSRF token header to upstream. This option passes the header declared in --csrftoken-header option.
--csrftoken-response-header
stringThe name of the actual CSRF token header to return to client. If set to empty string, no CSRF token header will be set by oauth2-proxy.
Default: X-CSRF-Token
--csrftoken-upstream-header
stringThe name of the actual CSRF token header forwarded to upstream.
Default: OAuth2-Proxy-CSRF-Token
--skip-csrftoken-route
string | listBypass CSRF token validation for requests that match the method & path.
Format: method=path_regex OR method!=path_regex. For all methods: path_regex OR !=path_regex.
Provider
None
The text was updated successfully, but these errors were encountered: