可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm putting together a site that will make itself available for user input. I was wondering if writing a function like:
if(getenv("HTTP_REFERER") != 'http://www.myURL.com/submitArea'){
die('don\'t be an jerk, ruin your own site');
}else{
// continue with form processing
}
is enough to prevent cross site form submissions.
EDIT: And if not, what is the best practice for preventing forms from being submitted from other hosts?
回答1:
Actually yes, according to the OWASP CSRF Prevention Cheat Sheet in most cases checking the referer is enough to patch a CSRF vulnerability. Although it is trivial to spoof the referer on your OWN BROWSER it is impossible to spoof it on another browser (via CSRF) because it breaks the rules.
In fact checking the referer is very common to see on embedded network hardware where Memory is scarce. Motorola does this for their Surfboard Cable Modems. I know this first hand, because I hacked them with csrf and then they patched it using a referer check. This vulnerability received a severity metric of 13.5 and according to the Department of Homeland Security this is the most dangerous CSRF vulnerability ever discovered and in the top 1,000 most dangerous software flaws of all time.
回答2:
Nope - HTTP_REFERER
can be freely spoofed on client side and is not a reliable indicator of where a request came from.
Update: I misread the part about cross site forgery: For this, checking the referer is a valid security measure, because CSRF rely on manipulated links pointing to protected pages (that the attacked user has privileges on). User @Rook is correct.
The only exception is if the attack can happen from within the web application that is being attacked, e.g. by injecting malicious JavaScript code. In that case, a referer check is useless because the attack is coming from a "safe" URL, but so is arguably a solution based on a session or one-time token, because the token is in reach of the malicious JavaScript and can be easily retrieved.
However, using a one-time token is highly preferable to protect against this kind of attacks because HTTP_REFERER
is stripped out by some proxies.
回答3:
Using a SESSION will most likely be the better route to prevent cross site form submissions.
回答4:
Whilst it is impossible to spoof a Referer
in another user's browser, it is easy to spoof a lack-of-referrer (eg. using a meta-refresh), in addition to some user-agents not sending a Referer at all.
So either you allow missing-referrer and have non-watertight XSRF protection, or you require a referrer that matches your site, in which case you take a big hit to accessibility. That hit might be acceptable if the only person using the script is you, and you know you'll always be using a browser/firewall/proxy/etc combination that passes referrers through reliably. But for anything you expect other people to use, it's generally not a good idea.
Referer
is quite a weak anti-XSRF mechanism. Much better to use a per-user/event token issued by the server that must come back to the server to validate the submission.
$query = "SELECT * FROM users
WHERE name
= '$name'";
Potential SQL injection vulnerability. Please use mysql_real_escape_string
or parameterised queries.
input.setAttribute('name', 'add_bar');
input.setAttribute('value', '');
Don't use setAttribute
on HTML attributes. There are bugs in IE that stop it working in some cases, and there are some attributes that don't do what you think. For example, setting the value
attribute is not the same as setting the value
property. The property holds the current value of the form field; the attribute only holds the ‘default value’ of the field, to which it will be reset if an <input type="reset">
is used. This maps to the defaultValue
property. In some browsers, setting the default value also sets the value, but this is non-standard and not to be relied upon.
Use the DOM Level 1 HTML properties, they're both more readable and more reliable:
input.name= 'add_bar';
input.value= <?php echo json_encode(generate_session_token(), JSON_HEX_TAG); ?>;
Use json_encode
to create values for JavaScript literals. Although you can be sure an MD5-sum will not contain characters special to JS like '
or \
, or the </
sequence that ends a <script>
block (against which the HEX_TAG
argument is protecting), it's not the output template's job to know what the session token may contain. This is a safe way to output any string into a <script>
block.
See this question for an approach to generating anti-XSRF tokens that requires no extra token-storage in the session or database.
回答5:
Yes, it's secure
Unfortunately, the holy text encourages to provide an option to disable the referrer (but you still can't invent your own mechanism of what a referrer is); so indeed, someone could disable referrers on his browser, thus denying himself access to your site.
Your solution is secure, but users can legitimately complain if your site only works with referrers enabled.
This is very sad because it now means that there is no sane way to ensure your site isn't secure against CSRF.
Alternative
The only other thing you can really do is put a nonce in each authenticated request to your web service. This however is somewhat dangerous because you have to make sure every request point on your web service validates the nonce. However, you can use a framework to do this for you, to somewhat migitate this annoyance. Stack Overflow itself seems to be using nonces.
However
Referrers in principle are a bit more secure because you can just apply a global rule that says no request can take place unless the referrer is in the same domain. This is acceptable if you are willing to drop users who disable referrers.
Contrary to what nonsense people are saying here, you can't issue an HTTP request with a spoofed header from non-privileged browser code. As expected, Flash was able to do this once to bypass anti-csrf that relies on referrer, but it was patched. Why was it patched? Because the HTTP RFC dictates what the referrer is, and thus, you are not allowed to change the meaning of it in your client code, lest your client be insecure.
Someone here even claimed that a Java applet can issue arbitrary headers over HTTP to another domain, but that is simply not the case because a sandboxed Java applet is not allowed to make requests to anything, except the domain on which it was loaded from. He quickly removed his comment before anyone could correct him...
Case in point
A web browser is an HTTP client. An HTTP client must conform to the HTTP RFCs; thus it has to adhere to what the RFC states a referrer header should look like. Since a web browser is an HTTP client, any application embedded in a web browser must not be able to make requests that violate HTTP protocol. The fact is that every violation of a standard is a potential security hole.
In any case: there is no proper way to determine weather a request is from your domain or an adversary's domain. This is just one of the many sad defects of the web. You must use one of these mentioned workarounds.
回答6:
Actually, you can use a dynamic address for that form. Like when user go to your form you forward him to a page with random address such as www.something.com/form.php forwarded to www.something.com/form.php?13mklasdkl34123 where 13mklasdkl34123 is randomly generated for each user and store it in $_SESSION. Upon receiving form submit, you can check the referrer address is the address you generated for the user. Referrer can be spoofed, but a dynamic referring cannot as the spoofer cannot know the address unless he (individually) visit your form page.