This post will not cover the entirety of protecting your website against all forms of attack. Even a series of posts would be insufficient to cover such a large topic. While not in the scope of this post, I will begin by offering some general advice.
Assume that all user input is malicious!
If user input is being output to other users as it would for comments, make sure to either escape the input or remove anything that could be dangerous. In PHP, this could be done using either htmlentities or strip_tags.
$comment = '<p>Some text</p><script>alert("Hello");</script>';
// Output original comment as an HTML safe string
echo htmlentities($comment, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// Outputs<p>Some Text</p><script>alert("Hello");</script>
// where all special characters are converted into UTF-8,
// so<becomes<,"becomes", etc.
// Output comment as an HTML string, removing dangerous tags
echo strip_tags($comment, '<p><a>');
// Outputs<p>Some text</p>alert("Hello");as an HTML
// string, where<p>is an actual p tag and script tags
// have been removed, and therefore not executed.
// Note that this will not remove attributes such as
Be extremely cautious with database queries
Similar to the above, user input should not be trusted as part of a database query. You should always sanitize/escape anything that a user inputs. Or, better yet, use prepared statements.
When using PHP and MySQL, it is important to know that the standard
mysql_* functions are not secure, even when using the built in escaping methods.
mysqli_* functions are a little bit better, but
PDO is definitely the way to go!
Protect your sensitive information
Even if you do a flawless job of protecting your website from attackers (which is actually impossible), it is still possible for someone allowed access to your code or databases to be malicious.
The exact methods used to secure your sensitive information will vary based on what the information is and what you intend on doing with it, but a good starting place would be to encrypt user passwords. In cases like passwords where all you need to do is verify a match rather than know the exact value, you should use a salted hash rather than a form of encryption which can be decrypted. For PHP, I recommend using the
Users, if a website offers you the ability to retrieve your password, either in addition to or instead of just resetting it, you should not place any trust in that website. If they can tell you what your password is, then they are not storing it securely enough.
Other practices to follow
- Set file permissions and access carefully (do not allow public access to everything).
- Use techniques such as hidden inputs to verify form submissions. Set a unique & random string in
$_SESSION, and use that to verify the authenticity of forms.
- Prevent your website from being a medium to other attacks though things like redirects http://example.com?redirect=http://thebadguys.com
<iframe>s the fewest permissions necessary using the sandbox attribute.
- Keep it simple… The more complicated your code, the more difficult it is to identify potential problems.
- Know that you will be attacked, and probably by people smarter than you.
All of the above are just a short list of best practices meant to protect your server and code, but until recently there was little if any client side security methods. All of the server-side security in the world will be of no use to you if a user logs into your site with an extension that contains a key logger. How useful is all of your security when an attacker targets your users rather than your server? Isn't all of your work worthless when an attacker can change your code while it's in transit?
Hopefully, this is an obvious step to all web developers, but it is an extremely important one! Unfortunately, this is only one step of the process, and it can be difficult to get this right with all of the vulnerabilities that exist such as heartbleed and poodle.
If a user is entering sensitive information such as credit card numbers or passwords, it had better be done using TLS.
Set your HTTP headers
It's really tragic that more web developers don't know anything beyond the basics of HTTP Response Headers. I'm not trying to criticize other developers or say that I am not somewhat guilty myself, but this is one fundamental of web development which I haven't seen much emphasis on.
Header set Strict-Transport-Security max-age=16070400;
If sensitive information is central to your website, or even if you just prefer https over http (which might be a good idea), a simple redirect might not be enough, and a better method does exist. At best, a standard redirect is an additional request, which means longer load times. At its worst, a redirect is a possible exploit for a man-in-the-middle attack. Allow me to explain…
When you use a standard redirect from http://example.com to https://example.com, the initial connection is unencrypted and could be intercepted. At this point, it is possible for the user to be redirected elsewhere or to have a forged site served instead, even with the same address. A really clever hacker could even load the actual page, but serve as a medium between client and server, intercepting all traffic.
Using Strict Transport Security, however, this unencrypted step would be skipped, and http://example.com would be rewritten to https://example.com by the browser itself. A browser which supports STS will remember this header and automatically rewrite all http requests to https ones.
You should still include the redirect on your server as well for any browsers that to not support STS as well as for the the initial connection.
Header set X-Frame-Options "SAMEORIGIN"
The Frame-Options header is fairly well supported and can be used to restrict loading your content in an
<iframe> or to deny it entirely. Among other reasons, this can be useful to prevent a phishing site from loading the login page to your website in an
<iframe> and collecting your credentials.
The two main options for Frame-Options are
DENY. The first will only allow pages from the domain to be loaded on the same domain, and the second will deny even that.
CSP is perhaps the most important and complicated of the bunch. I say it is the most important because it allows developers to lock-down the resources used on their websites using a whitelist for each type of resource. If an attempt is made to load content that is not in the whitelist, the request is never even attempted.
Content-Security-Policy: "default-src 'self';script-src 'self' *.google-analytics.com https://apis.google.com https://platform.twitter.com https://gist.github.com/;style-src 'self' https://gist-assets.github.com;font-src 'self' data: themes.googleusercontent.com/ http://openfontlibrary.org/;img-src 'self' data: *.google-analytics.com https://*.googleusercontent.com/ chriszuber.com;media-src 'self' mediastream:;connect-src 'self';frame-src 'self' https://*.youtube.com https://*.youtube-nocookie.com http://*.youtube.com http://*.youtube-nocookie.com https://plusone.google.com https://facebook.com https://platform.twitter.com;frame-ancestors 'self';object-src 'self';report-uri /CSP_Errors.php;"
CSP is the best answer we have to this problem by allowing developers to specify exactly which resources they wish to allow by type and domain.
The core issue exploited by XSS attacks is the browser’s inability to distinguish between script that’s intended to be part of your application, and script that’s been maliciously injected by a third-party. For example, the Google +1 button at the top of this article loads and executes code from
https://apis.google.com/js/plusone.jsin the context of this page’s origin. We trust that code, but we can’t expect the browser to figure out on it’s own that code from
apis.google.comis awesome, while code from
apis.evil.example.comprobably isn’t. The browser happily downloads and executes any code a page requests, regardless of source.
An Introduction to Content Security Policy
The source list:
default-src— fallback/default if no whitelist exists for another resource type.
style-src— list for allowed CSS.
object-src— allowed plugin content, such as Flash.
connect-src— allowed to be used for
frame-src— origins which can be loaded as
img-src— allowed origin for all
<img>s (does not cover SVG
media-src— allowed origins for
font-src— allowed origins for web fonts.
Each of these is specific to a type of content or request. This way, allowing images to be loaded from Google does not mean that scripts may be loaded from there as well. If any of these are not set, it defaults to
The keywords (mind the
'none'means nothing… deny everything!
'self'means anything from this exact domain, including schema.
'unsafe-eval'allows things similar to
*is a random string will allow a resource to be loaded even if not otherwise allowed (such as inline scripts) by adding a
'sha256-*'is similar to nonce, except that it verifies by the hash of the resource instead of the
Otherwise, each type of content may contain a space-separated-list of allowed URIs or schemas, and even wildcards.
For example, if for some strange reason you wanted to create a page that blocked everything, including stylesheets from itself, you could use
content-security-policy: default-src: 'none';. Or if you wanted to be able to claim that you used CSP but have it server absolutely no purpose whatsoever, you could set
content-security-policy: default-src *;.
As a more practical example, if you wanted to only allow loading of any resources from the same domain and block all inline code, you could simply use
content-security-policy: default-src 'self'; and be done with it.
Or if you want to allow any local resources, load jQuery from a CDN, allow inline-style, and allow images from anywhere, you could use
content-security-policy: default-src 'self'; script-src ajax.googleapis.com; style-src 'self' 'unsafe-inline'; img-src *;
While trying to set CSP to your needs, you may set the same values to a
Content-Security-Policy-Report-Only header instead, and make use of the