2

I'm trying to set up CSRF prevention by checking the Origin and Referer headers, and also blocking any action if neither are set.

However, if the URL is simply typed or pasted into the browser, such a check will cause the action to be blocked since the Origin and Referer headers won't be set.

I thought perhaps making sure HTTP_HOST is equal to SERVER_NAME, but seems that may weaken the check.

What is the best way to have a secure Origin / Referer check and yet let typed in URLs pass the check?

  • 1
    Why is it important to allow typed URLs? – Arminius May 30 '17 at 0:00
  • Fair point - not sure it's terribly important. As a developer/admin, I sometimes modify an addresses by typing in the change when it's easier and was surprised to see the errors. Seems a shame to lose the ability to type in an address, but perhaps that's the cost of security. – lindon May 30 '17 at 0:09
  • What would the typed URLs look like? If you're concerned about CSRF, they'd surely trigger sensitive state changes? – Arminius May 30 '17 at 0:16
  • 1
    The point being a URL shouldn't be able to trigger sensitive state changes with $_GET parameters anyway? – lindon May 30 '17 at 0:19
  • 2
    Got it, thanks for your help. I think in my case I need to be more precise in when I apply the check and throw errors and accept that typing in URLs that trigger sensitive state changes will fail the check. – lindon May 30 '17 at 0:29
6

If you follow the often cited best practice of not mutating data or other application state on HTTP Get requests, the referrer check on the Get request becomes a non-issue. You can just use the referrer check on all your HTTP Posts (or other methods if you're using them).

CSRF exploitation is essentially blind (barring massive CORS misconfigurations), so as long as Get requests only ever return data/JSON/HTML/etc. and don't change anything, you're fine not enforcing anti-CSRF on them. They're simply not sensitive to this kind of attack.

Posts (Puts, etc) are presumably only expected to come from a known app, therefore should have the correct referrer unless they're being stripped by a proxy or some other software or device "in the middle".

2

What is the best way to have a secure Origin / Referer check and yet let typed in URLs pass the check?

If you're letting typed-in URLs pass the check, you're inevitably creating a CSRF vulnerability (obviously assuming that the typed URLs don't use CSRF tokens and cause security-sensitive state changes).

That is, a malicious site can easily omit the referer and doesn't have to send an Origin header. This makes a cross-origin request indistinguishable from a typed URL. An easy way to strip the referrer from cross-origin requests is by adding a meta header:

<meta name="referrer" content="no-referrer">
<a href="https://yoursite.example/delete_my_account">Click me</a>

In that example, your browser can't tell if the user clicked the link or manually typed in the URL.

  • Commenting downvotes helps to clarify what the problem is and with improving the answer. – Arminius Jun 1 '17 at 19:58
-1

I think it is worth taking the time to ask why you are using an origin and referrer check to enable your CSRF validation.

The most secure way to do a CSRF check is by using cookies (well, the user's session, really) to store a token that must come back with the final request. Cookies make the entire process very simple and a lot less error prone (for your end user). The only time you should really have to do origin and referrer based CSRF validation is when storing the key in the user's session is not possible. However, if you are worried about the user typing URLs directly into the browser, then this means that your end user's are operating from within the browser, which means that you specifically have access to cookies/sessions. So I suspect the real answer to your question is to adjust your CSRF procedures to make use of cookies, not referrer/origin.

To answer your actual question though, the answer is that no, you can't do anything to allow user's to type the URL directly into the browser and still gain access to whatever resource/action your CSRF setup is protecting. Except by using cookies, there is no way to distinguish between a user typing a URL in directly, and a malicious CSRF attack.

Edited to add more details:

The CSRF token I am describing is called a Synchronizer token on this page. The idea is that you pre-calculate a secret, store it in the user's session (which is why cookies are involved), include it in the form that will be submitted (but you don't place it in an actual cookie), and then verify that the CSRF token came up with the form and matches what you stored in the session. The reason it works is that the action is not allowed without the token, and the token is not given until the user actually visits the form to execute the action. For additional security you also regenerate the token for each form submission. As a result, a malicious user can't silently complete an action in the "background" because all actions can only be completed if the user first visits the page that contains the form and then submits the actual form.

The page you referenced in OWASP suggests using a dual security paradigm to protect against CSRF attacks:

  1. Verify Origin and Referrer headers
  2. Use a CSRF token

Proper application of defense-in-depth dictates taking all necessary security measures, which is why they suggest using both methods to protect against CSRF. Sometimes though, reality requires a compromise, so it is worth mentioning all the pros and cons of either/both. Most importantly, when done properly, either method will protect you against all currently known CSRF attack vectors. Of course this is where defense in depth comes in, as having extra security (i.e. implementing both defense measures) potentially increases the chances of mitigating future attacks that have not yet been devised yet.

Despite this fact, every framework I have ever worked with uses only CSRF token protection out of the box (to the best of my knowledge). This includes Code Igniter, Laravel, Django, and Ruby on Rails. In the case of Django and Ruby on Rails I'm relying on their documentation to verify that fact (it is possible they left some details out), but for codeigniter and laravel I double checked the actual source code itself to make sure I wasn't mistaken. So in reality, it is common on the web to enable CSRF protection using only CSRF tokens. Whether or not that is a good idea for you depends on your comfort level of balancing security versus usability. However, it certainly isn't crazy to use only CSRF tokens to perform CSRF mitigation: many popular software systems do exactly that.

It occurs to me to ask one more question: why does your CSRF system prevent users from typing in an address? If a user simply types an address in their address bar to visit your page, that executes an HTTP/GET request, and GET requests shouldn't be subject to CSRF protection because the server should never take actions in response to a GET request. So in reality, there might also be an easier fix to your conundrum: make sure you never take actions in response to a GET request and then turn off CSRF protection on all GET requests. Then, user's will be able to type in URLs in a browser all they want.

  • Apologies for my ignorance, but is this the same a using a "secret cookie" as described in this OWASP article? – lindon Jun 2 '17 at 1:24
  • I edited my post and expanded the discussion on CSRF protection and CSRF tokens substantially – Conor Mancone Jun 2 '17 at 3:29
  • Thank you for the additional explanation - very helpful. Your last questions are good ones. The code I'm working with allows $_GET requests to change the state in some cases. I just need to differentiate and provide protection only for state-changing requests. That was my problem. Because I am protecting $_GET requests, using tokens is not a good idea so I am using the header check. I have seen some comments that checking the origin and referer headers will block too many users as these are not always sent by browsers. Was curious whether others see this as an issue? – lindon Jun 6 '17 at 0:07
  • From what I have read browser, support for referrer and origin checks is not 100% reliable. However, I have always relied on tokens, not header checks, so I can't speak from personal experience. You can still perform token authorization for get requests. The token will end up in your URL, but that won't actually hurt anything. It would be good to regenerate your token for each new request though. Using tokens with GET requests will make it impossible for them to type URLs in directly though, which is what you were originally trying to do. – Conor Mancone Jun 6 '17 at 12:00
  • Coming back to this with a fresh view: there is no way to both provide CSRF protection and allow users to type a protected URL into their browser and access the resource/execute the action. From the perspective of a CSRF attack, there is no way to distinguish an actual attack from the user typing in a URL directly: the two actions look exactly the same from the servers perspective. Doesn't matter if you use token-based or header-based security. Neither can accomplish what you want. Sorry. You'll have to ditch your security (bad idea) or adjust your application flow. – Conor Mancone Jun 6 '17 at 12:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.