Prerequisite topics and nuances for understanding CSRF
- Topic : Basics of cookies . Read more : https://clarifyforme.com/posts/5678598534987776/HTTP-Cookies
- Subtopic : same site attribute of cookies
- Nuance: The difference between cross site and cross origin is an important point. Note that the request from subdomain.example.com to example.com although is a cross origin request, yet, it is a same site request allowing cookies to be part of request headers and hence making CSRF attack from subdomains possible. Read more https://clarifyforme.com/posts/5177685533786112/what-is-the-difference-between-site-and-origin .
- domain attribute of cookies
- Nuance: A domain cannot create a cookie that goes to another domain. (hdfc.com cannot create a cookie that goes to icici.com) but subdomain can create cookies that go to domain. This can be used by subdomain to bypass double submit cookie approach and launch a CSRF attack.
- same site attribute of cookies, looks at the origin of the request, domain attribute determines the target of the request.
- Subtopic : same site attribute of cookies
- Topic : Cross Origin requests.
- Nuance : Browsers block cross origin script requests. This statement can make one assume that cross origin request cannot lead to CSRF attack as this will be blocked by the browser but this is not correct. As explained further in this article, a cross origin, same site request can lead to CSRF attack as cookies will go with request, execution can happen on the server and only the response is rejected by the browser owing to same site constraints.
What is a cross origin request
If a web application running under one domain tries to access resources in another domain then it is called a cross origin request.
- For the purposes of matching only domain, scheme and port are considered.
- Subdomain is considered a different domain.
- Let us say user types http://example.com/page1 on browser addressbar.
- The origin of any request (script/non script eg a click on link) from this tab will be (scheme=https, host=example.com , port=80)
- The following requests would be examples of cross origin requests
- If the user clicks on link or makes a script request to http://sudomain.example.com (doamin does not match ,subdomain is considered a different domain)
- If the user clicks on a link or makes a script request to http://anothersite.com (domain does not match)
- If the user clicks on a link or makes a script request to http://example.com:8080 (port is not matching)
- If the user clicks on a link or makes a script request to https://example.com (scheme is not matching)
- The following requests would be examples of same origin requests
- If the user clicks on a link like http://example.com/page2
- Read more https://clarifyforme.com/posts/5637618884673536/What-is-a-cross-origin-request
How an attacker can create a CSRF attack with HTML form against a POST end point.
- The user visits icicibank.com and authenticates, after which the server returns a session cookie in the response headers which is stored by the browser.
- While the user is logged in, he visits another site fraud.com (owned by a web attacker) on the same browser tab or another tab and clicks on any image/link. Note that the web attacker has his own malicious website to launch the attack.
- The users click leads to load of a HTML form and on page load, submit to the server because of the javascript as shown in the following example. Typically attack against post endpoint is via form as form is one of the HTML objects that can initiate a post request. Also, note that the hidden elements will not be visible to the user.
- This form submit will lead to a state change (fund transfer in this case ). The browser will add the session cookie (already set by icicibank.com) to the request headers since the destination of the request is icicibank.com. From icicibank perspective, since a valid session cookie is part of the request, this is a legitimate request, and based on the form parameters, actions will be performed, for example, fund transfer or deleting data.
- note that
- The origin of the request is https://fraud.com:80
- So the CSRF attack on post endpoint is happening from an attacker's application!
- The destination of the request is https://icicibank.com:80 as the action attribute has icicbank.com
- Now you may wonder, how did the attacker get this form? The attacker can get this form by logging in to icicbank.com from his machine and viewing the HTML contents of the form by view page source .
- CSRF attack is typically used by attackers to trigger some action/state change (eg deleting data). It cannot be used by an attacker to retrieve data as the response of the request does not go to the attacker. (The attack has happened from the browser of the genuine logged-in user, not the attacker's browser.
- The origin of the request is https://fraud.com:80
- Note that while this is a cross origin request, still it will not be blocked as cross origin script requests are blocked by browsers.
How an attacker can create CSRF attack with script against POST end point and attack mitigation
Steps for CSRF attack with script against post endpoint
- The attack is only possible from a subdomain. Cross-origin requests, which are cross-site also, will not go with cookies (if the same site attribute is lax and the request is a script request)
- Let's say the website which is the target of the CSRF attack is target.com
- The attacker controls the subdomain attacker.target.com.
- Any script on the attacker's website can also make an ajax post request to POST end point.
- For security reasons, the browser applies the same origin policy to scripts. ie cross-origin HTTP requests from scripts are blocked, therefore it would seem that a CSRF attack using script against post endpoint is not possible, but in actuality, cross-origin script requests will lead to at least one hit to the target server. A preflight request, the actual script request, or both. In case of a simple request, a preflight request will not be made,
- The actual ajax request will be made by the browser,
- The request will reach the server,
- The request can be executed on the server! (as the attacker's domain (attacker.target.com)is a subdomain of the attacked domain (target.com), the session cookie will be sent to the server as this would be an instance of a cross-origin, same site request). The value of the same-site attribute of the cookie does not matter in same-site requests.
- Only the response will be discarded by the browser if the cors header Access-Control-Allow-Origin is missing, meaning that a CSRF attack via ajax can succeed (in case of an attack from a subdomain) in spite of cross-origin protection provided by the browsers.
Attack mitigation: Here is what can be done to make a post endpoint ajax only and protect it against CSRF.
-
- If the server checks for a custom request header in the request and rejects the request if custom header is not present, then CSRF can be blocked. This is because the custom header in ajax request forces a preflight request before the actual request and the preflight request cannot do any state change.
- The server should execute a request conditionally if the X-Requested-With header is part of request headers
- (Custom headers can only be added by javascript) hence by checking for the presence of custom header, the server ensures that form posts will be rejected and this will be ajax only post endpoint.
- Now, If the request is made without X-Requested-With custom header, no preflight request will be made by the browser, but the request will not lead to execution on the server as X-Requested-With header is not present (The server is actively checking for the X-Requested-With header and not performing state changes if X-Requested-With header is absent)
- If the request is made with X-Requested-With custom header, the browser will make a preflight request. The preflight check from attacker's origin (even if it is a subdomain of attacked website target.com) will fail (as Access-Control-Allow-Origin will be absent or not match target.com
- Hence by checking for the presence of X-Requested-With custom header
- target.com server ensures that the post endpoint is an ajax only endpoint.
- target.com forces preflight before the actual request.
- by controlling the Access-Control-Allow-Origin header target.com controls if the actual cross-origin request can be made.
- It should be noted that a request from a subdomain is cross-origin, like any request from any domain (other than target.com) but it can be used for CSRF as these requests are still same-site requests, allowing cookies to be part of the request.
- If the post endpoint is for ajax /api calls this approach works, for form posts CSRF tokens can be used.
How an attacker can create CSRF attack against get end point.
- Once an attacker figures out that an http get can be used for some state change in the server, then the attacker can create a malicious url
<a href="https://www.icicibank.com/transferfunds.jsp?to_user_account=<hacker’s account>&amount=10000">click me for discounts</a>.
- The attacker distributes this malicious link via email/social media.
- It is important to note that, when we click on links throughout the internet we rarely check the source to see where the link is taking us. This link:<a href="https://www.icicibank.com/transferfunds.jsp?to_user_account=<hacker’s account>&amount=10000">click me for discounts</a>. would appear in the browser as “click here to get discounts.” Most users would not know a parameter was attached to the link as an identifier.
Should get requests change state and should get end points be protected against a CSRF attack?
- The HTTP GET request method is used to request a resource from the server. The GET request should only receive data.
- Note that browsers and load balancers can cache the response of GET requests. If the cached response is used, then the get request will not even reach the server. For this reason, GET request should not be used to change state on the server.
- browsers allow get requests which lead to top-level navigation to go with cookies (if same site attribute is lax, which is the default), hence get request’s making state change leads to increased risk of CSRF.
- If the GET end point is not changing state on the server, then it need not be protected against a CSRF attack. This is because the CSRF attack is executed from the browser of the genuine user. Not the browser of the attacker. Even if the GET request is able to fetch user data, the attacker cannot see it.
Preventing CSRF attacks via header verification.
Origin header and referer header (part of all requests) tell where a request originated from. Both these headers should be checked to ensure that the value of these headers is the same as trusted origins. This method can be used for CSRF checks for unauthenticated requests also. Typically both headers should be checked. Note that both these headers cannot be programmatically modified via javascript. While tools like burp proxy can intercept the request to change this header, in the case of CSRF, the attacker does NOT have the capability to intercept the request made from the actual user's browser. When verifying these headers against a target origin, the target origin could be configured on the server side, or determined based on host header value (or X-Forwarded-Host header). There are some more nuances that are covered in this article. https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#identifying-the-target-origin .
- The Origin header is sent in all cross-origin requests, but it’s also always sent for all POST, PUT, PATCH, and DELETE requests — even for same-origin POST, PUT, PATCH, and DELETE requests.
- The Referer HTTP request header contains an absolute or partial address of the page that makes the request. Note that the referer header may be absent in 1 to 2% of requests (blocked at the network layer because of privacy concerns).
Header verification can protect from CSRF attacks from subdomains also. Read more about nuances of referer header here https://seclab.stanford.edu/websec/csrf/csrf.pdf .
Same-Site Cookie Attribute to Prevent CSRF Attacks
Same-Site Cookie Attribute helps in controlling if cross-site HTTP requests(ajax / normal browser request) (eg request from b.com to a.com ) go with cookies. In other words with the help of Same-Site cookie attribute, developers can now control whether cookies are sent along with the request initiated by third-party websites. If the request is not cross site, the value of same site attribute does not matter. Read more https://clarifyforme.com/posts/5678598534987776/HTTP-Cookies
Note that Samesite attribute should be used as an additional layer of defense.
- It can be bypassed in some ways. Read https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-5.3.7.1
- Samesite cookies do not help in CSRF attacks from subdomain (as subdomain1.domain.com and subdomain2.domain.com are samesite ).There is a diffrence between cross site and cross origin. Read more https://clarifyforme.com/posts/5177685533786112/what-is-the-difference-between-site-and-origin
Using Custom request headers to prevent CSRF Attacks
- This is already covered in the section: How an attacker can create CSRF attack with ajax against POST end point.
Using CSRF tokens to prevent CSRF Attacks
How to ensure that a form can only be submitted from my own website? CSRF token can help here. A CSRF token is a random, low collision(odds of two identical tokens being generated by the algorithm are extremely rare) string. When the server is generating a form it wants to protect from CSRF, the server would generate the CSRF token, add it to the form as a hidden field and also store it in the session. (The CSRF token could be generated per session also) Now the form would look like this:
<form action="https://icicbank.com/debit" method="POST">
<input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9ajDlcn">
<input type="text" name="fromAccountId">
<input type="text" name="amount">
<input type="submit">
</form>
When the user submits the form, the server simply has to compare the value of the posted hidden field csrf-token with the CSRF token stored in the session object. Note that copying the static form from an authentic page to a different website would be useless because the value of the hidden field changes every time the form is generated (or per session). ie on submit of copied the html form, the copied hidden field value will not match the value in user session on the server. Note that generally protecting all post requests is important as through CSRF attack, the attacker typically triggers a state change, not read data.
Note that if you want to avoid storing the CSRF token in the session object (to keep the session object light weight?)
- Then signed and encrypted CSRF token can be used. The signed CSRF token should contain.
- unique user id to whom the token belongs. (copied form cannot have this info)
- timestamp(used for expiration)
- user's session identifier could be used as CSRF token , but this is not recommended as the CSRF token can sometimes be leaked as user sometimes can reveal the content of their webpages through screenshots (eg when seeking debugging support). Hence loss of CSRF token can mean loss of session which can be very dangerous.
- To solve the above problem HMAC of the session identifier is used as CSRF token. (this approach was used in Ruby On Rails),on the server side when this hidden field value is picked up, it can be decrypted and then compared with the user's session identifier.
Note that the CSRF token in the above discussion is sent downstream in a hidden form field.
Note that the CSRF token can be sent downstream in
- Request body.
- (hidden form fields)
- in this case, upstream the CSRF token would be sent as a request parameter.
- JSON response
- (hidden form fields)
- custom HTTP header.
- after the user navigates to the form
- ajax request is made to retrieve CSRF token. this request would be a cross-origin request from the attacker's application(even subdomain to domain request is cross-origin), hence attacker's app will not be able to retrieve/read the CSRF token. Hence the script in the attacker's app will not be able to retrieve the CSRF token.
- At the server, in case the CSRF token is not generated for the session, it will be generated and the CSRF token will be returned in the HTTP response header and the CSRF token will be stored in the session object.
- The script will pick up this response header and add a hidden field in the form.
- Server checks that the hidden field value matches the stored CSRF token in the session.
- This approach has a performance implication. The hidden field could have been part of the form when the form was sent.
- cookie
- User navigates to the form
- Server generates CSRF token, stores it against the user session and outputs it to a cookie. (cookie cannot be httponly, it should be readable by script). As the cookie is not httponly hence it is prone to xss attack.
- before submit the script will read cookie value and add a hidden field in the form.
- the cookie value cannot be read by attacker's domain / app.
- if the attacker's domain is a subdomain, even then, if the cookie is host only it cannot be read by a subdomain.
- server checks that the hidden field value matches the csrf token in session .
- note that in this case, extra ajax request was not der.
- hyperlinks (CSRF token should never be appended to hyperlinks)
CSRF token can be sent upstream in
- request body
- a hidden field in HTML form
- JSON payload (in case of script request)
- custom request header (in case of script request)
- It should be noted that CSRF tokens cannot go upstream via cookies. (otherwise, it has the same issue as a session cookie.)
Additionally CSRF token will protect from CSRF attack from subdomain also which samesite cookies will not help.
Double submit cookie
Double submit cookie is used when we want to keep the server-side code stateless. In this approach, the CSRF token is sent both in a cookie and form body in a hidden field. On form submit, the server will compare the cookie value with the hidden field value received as a request parameter. Note that both the values being compared on server have come from client in this approach. One is a hidden form field and other is a cookie. Note that if attacker.com is trying to attack example.com then the attacker can create a from with hidden field with value csrfrandomtok1, but he cannot create a cookie which will go to the domain example.com
Let us say the site being attacked is example.com. Now if
- The attacker's domain is subdomain of example.com then the attacker can create a cookie(via script/server side) which will go to example.com. (the cookie has to be set to example.com ).
- In this case attacker can choose any random value (say V1) and create cookie and hidden form field with value V1, via script and both will match on the server. To protect against this, the cookie value can be encrypted via a symmetric key(known to example.com server). The hidden field value is not encrypted. The serverside comparison will happen by comparing decrypted cookie value (decryption by symmetric key ) and the hidden field value. Now the attacker cannot create the cookie value from the token V1.
- The attacker's domain is not subdomain of example.com , then attacker cannot create a cookie which will go to example.com .
Angular JS mechanism for blocking CSRF
AngularJS provides a mechanism to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie (by default, XSRF-TOKEN) and sets it as an HTTP header (by default X-XSRF-TOKEN). Since only JavaScript that runs on your domain could read the cookie, your server can be assured that the XHR came from JavaScript running on your domain. Read more https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection
CSRF in login form
Even though CSRF is called a session-riding attack, ie an attack using an already established session, yet CSRF attack is possible against login form(before the user logs in). The difference is that
- In login CSRF the session of the attacker will be created and user data will be misused. In normal CSRF, session of the genuine user is created).
- In login CSRF, the session creation happens over cross site request. In normal CSRF the state-changing request is a cross site request.
Paypal and google had this vulnerability. In a login CSRF attack, the attacker forges a login request to an honest site using the attacker’s username and password at that site. If the attack succeeds then all operations done by the user on the browser will affect the attacker's account
- eg change in viewing history,
- Any credit card which the user attaches to the account will get attached to the attacker's account.
Here are the steps of login CSRF.
- The user receives a malicious link through social media /email
<a href="https://attacker.com/login.jsp">Click to get discount </a>
- The attacker clicking on this link can lead to the download of an HTML from attacker.com.
<form method="POST" action="http://example.com/login"> <input type="text" name="user" value="attacker_userid" /> <input type="password" name="pass" value="attacker_password" /> </form> <script> document.forms[0].submit(); </script>
- as soon as this form is downloaded, the javascript will trigger a form submit. Notice that this will lead to cross site post login request to example.com with the attacker's credentials!
- The attacker is now logged in from the browser of the genuine user.
- Now if the genuine user attaches any credit card in example.com portal it will get added to the attacker's account, not the genuine user's account.
Attack mitigation can be done using the following techniques
- Strict Referer validation is well-suited for preventing login CSRF because login requests are typically issued over HTTPS. Over HTTPS, strict Referer validation is feasible because only a tiny percentage (0.05–0.22%) of browsers suppress the header.
- for ajax/api post endpoint custom header technique already explained can be used.(note that the login request is a cross origin request)
- double submit cookie already explained in the post will also be effective. Note that if attacker.com is trying to attack example.com then the attacker can create a form with a hidden field with the value csrfrandomtok1, but he cannot create a cookie that will go to the domain example.com
Please note: Check StackOverflow questions like this where the wrong explanation has become a top answer https://stackoverflow.com/questions/15602473/is-csrf-protection-necessary-on-a-sign-up-form
User interaction based CSRF prevention
- OTP
- CAPTCHA
- Reauthentication via password
Note that these spoil user experience hence should be used where there are strong security implications.
Cross site websocket hijacking
during a websocket handshake to switch from http to ws the webscoket protocol doesn't prescribe any particular way that servers can authenticate clients. If cookie based mechanism is used then cross site website hijacking can happen. Because WebSockets are not restrained by the same-origin policy, an attacker can easily initiate a WebSocket request (i.e. the handshake/upgrade process) from a malicious webpage targeting the ws:// or wss:// endpoint URL of the attacked application. Due to the fact that this request is a regular HTTP(S) request, browsers send the cookies and HTTP-Authentication headers along, even cross-site. In the WebSocket scenario this attack can be extended from a write-only CSRF attack to a full read/write communication with a WebSocket service by physically establishing a new WebSocket connection with the service under the same authentication data as the victim. Note that Origin header is sent along the WebSocket handshake/upgrade request. This is like in a regular CORS request utilizing Cross-Origin Resource Sharing: If this was a regular HTTP(S) CORS request, the browser would not let the JavaScript on the malicious webpage see the response, when the server does not explicitly allow it (via a matching Access-Control-Allow-Origin response header). How ever this protection is not there for websocket. The developers should check the origin header and block the request themselves.
Test yourself
Question > Is CSRF protection required for GET requests?
Question > Can CSRF be used to read data?
Question > Is CSRF protection (say thorough CSRF token) required when SameSite is set to Lax(which is the default).
Question > If samesite cookie attribute is there, why do we need CSRF token?
Question > Is CSRF protection required in login page where no user session is established?
Question > A website target.com has not specified the same site attribute of the session cookie and hence it defaults to lax. It has a post end point which is not protected against CSRF . Can example.com launch CSRF attack via ajax? Can example.com launch CSRF attack via form post? Can subdomain.target.com launch CSRF attack via ajax? Can subdomain.target.com launch CSRF attack via form post.
Click on pitfalls icon on rhs of this post and go to gotchas section to see answers.