Security tips for web developers

Cross-site scripting (XSS)

A cross-site scripting (XSS) hole is when an attacker can inject scripts into a page sent by your server in a way that gets them executed in the victim's browser.

For example, if http://www.yoursite.com/search?q=<script>alert(5)</script> returns "<p>There were no hits for <script>alert(5)</script>.</p>", your site is vulnerable to a simple XSS hole. If a malicious site links to such a URL, it can take control over the user's account on the site in several ways:

Internet Explorer supports a non-standard extension to HTTP cookies called HttpOnly. HttpOnly guards against the first version of XSS attacks, but only the first version, so you should not rely on HttpOnly to protect your site and visitors. Mozilla does not support HttpOnly (bug 178993).

Even on sites where users do not have accounts, XSS holes can be a problem. For example, intranet sites are automatically limited to users within the intranet, but XSS holes can allow outsiders to access them through the web browsers of employees.

Avoiding XSS holes

"Open redirect" scripts should not redirect to URLs with special protocols that inherit the security context of the site (hi Gmail). In Mozilla, these protocols are javascript: and data:.

For sites where the input is meant to be plain text, the fix is simple: escape everything as it is output as HTML, using an escaping mechanism appropriate to where it is.

Avoiding XSS holes in forums that allow HTML

For sites where the input is meant to be HTML (e.g. forum posts), escaping doesn't work. The best approach is to parse the input HTML on the server, keeping only tags, attributes, and attribute values you want to allow, then output the result as "well-formed" HTML. The naive approach of "stripping tags" with regular expressions often misses things. For example, the regular expression <.*?> matches nothing in "<script src='http://evil.com/evil.js' </script", leaving your Internet Explorer and Firefox visitors vulnerable (see bug 226495 and for details about "half-tag" parsing). Another example.

Things you should ensure are not allowed in forum posts to protect the accounts of visitors who use Firefox and IE:

The above list might not be complete and it is safer to use whitelists than blacklists. For example, only allow http:, https:, and ftp: links rather than allowing all links other than javascript:, vbscript:, and data:.

If you must allow unsanitized user-posted HTML (web host, attachments in a bug-tracking system), ensure that those pages are not on the same hostname as where other users log in. For example, Hotmail gets this right.

Cross-site request forgery (CSRF)

A Cross-site request forgery hole is when a malicious site causes the user to load a URL from your server (possible with form POST data) that causes a change on the server. Depending on which forms on your site are vulnerable, an attacker might be able to do the following to your victims:

CSRF vulnerabilities are typically in sites that use login cookies, but they can also exist in sites that allow changes based on IP addresses (such as intranet sites).

CSRF attacks may involve JavaScript to submit the cross-site form automatically, but they don't have to -- form fields can be hidden and buttons can be disguised as links or scrollbars.

Preventing CSRF

  1. Make sure that the cgi that handles form submissions for forms that change server state only accepts POST parameters, not GET parameters. Some server-side languages default to accepting both.
  2. Make sure form submissions use your own forms by including a hidden field that is an MD5 hash of the login cookie and a secret on the server. Then only accept the form if the hidden field is correct.
  3. Optional added paranoia: Add a timestamp as a hidden field and include it in the hash. Make the form expire if the timestamp is too old. Give users a way to submit the form again when the form expires, such as by returning the form pre-filled with the data they entered last time but with a fresh hash.

Do not rely on the Referer header to protect your visitors from CSRF. (Browser bugs and features allow web sites to create referrerless links, so you would have to reject referrerless form submissions. Some users choose to turn off referers, so you can't protect these users without preventing them from accessing your site at all. Some users even spoof their referer so it always appears to come from the site they are requesting a page from, making them impossible to protect in this way. Users that spoof referrers usually do so in order to access porn sites that restrict access to content solely based on referers.)

Since so few sites protect their visitors against CSRF attacks, we have discussed possible client-side fixes for CSRF. We didn't come up with anything good. For reference, see bug 38933, bug 40132, bug 246476, and bug 246519. At most, browsers might be able to prevent CSRFs from web sites to intranet sites, but not between web sites.

Other common holes

Private user data or restricted-access data in JavaScript files. JavaScript files can be included from sites in other domains -- the same-origin policy does not apply to including a file as a script. The only thing keeping most HTML and text files from being read this way is that they result in JavaScript parse errors.

Don't load an untrusted site in a frame. The site will be able to load data into other frames, leading to a spoofing attack. If you visitors use Internet Explorer, don't use frames at all, because other sites can replace your site's frames. See bug 246448 for more infromation.

Don't put passwords (or any data that should remain private) in a URL if the page at that URL contains external links. When users click those links, the entire URL is sent as a referer. https sites are not safe, because links to other https sites include the full referer.

Firefox and Internet Explorer follow the DNS protocol, which allows bogus.attacker.com to resolve to an attacker's IP one minute and your server's IP the next. The "Princeton attack" takes advantage of this and has the same security impact as XSS. Intranet servers should check the Host header and return "400 Bad Request" if an unrecognized hostname is given. (The HTTP protocol requires HTTP servers to do this, but many do not.) For an extra layer of security, intranet firewalls should prevent DNS requests on external hostnames from resolving to internal IP addresses. Similarly, browsers, operating systems, and ISPs should all prevent external hostnames from resolving to 127.0.0.1. (See bug 162871, bug 174590, bug 205726, and 223861.)

If you're http://www.amazon.co.uk/, you should ignore cookies that are set for all of co.uk. We're working on disallowing sites from setting cookies on such domains (bug 252342).

If your page is designed to be displayed as an iframe on other sites, you may want specify a background color. If your page is displayed in an <iframe> and does not specify a background color, the background will be transparent, not white (bug 154957).

Even if you protect against traditional CSRF attacks, malicious sites may cause users to make changes on your site by making them click parts of your site unknowingly. Examples of attacks include sub-reaction-time page changes, pages that frame your site but cover all but an 8px-by-8px portion of the frame, and pages that frame your site and make the frame 100% transparent. Removing "One-click purchase" buttons is a partial solution, but I don't know if web sites can really solve this problem.

Web services

Web Services are subject to the web services security model. To enable this security model, create a file called /web-scripts-access.xml. If your site doesn't use web services, you shouldn't have to worry about this new security model.

Sites used at public computers

After users log out, encourage them to close the browser window.

To prevent the back button from reaching private pages after the use has logged out of your site (but not closed the window), use cache-control. This will piss off your users, because it prevents the browser from going Back quickly and makes some browsers forget form information upon going Back.

To prevent the browser's "Save password?" dialog from appearing on misconfigured public computers, use the non-standard attribute autocomplete="off" on the form or password field. This will piss off home users, because they want their browsers to remember passwords for them. (They can use a bookmarklet to get around autocomplete=off, but most don't know that.)

Use autocomplete="off", but when the user checks the "remember my password" or "stay logged in when I close my browser" checkbox on your site, use JavaScript to remove the attribute. Then users at public computers won't see the dialog but users at home will. I haven't seen any sites do this but I think it's a good idea.

Purely server-side holes

Some security holes in web sites don't involve web browsers at all. Examples include directory traversal, buffer overflows, SQL injection, and forgetting to apply form access controls to both the page with the form and the cgi that handles the form. The Web Application Security Consortium's Threat Classification enumerates common server-side holes.

Contacting you

Don't let bug reports, especially reports of security holes, get lost in your customer service department before they reach your developers.