Hacking myself with Troy Hunt
I recently spent two days in a cloudy Copenhagen along with 20 or so other developers listening to
Troy Hunt talk about web security and teach
us the basics of hacking, or rather how an attacker can exploit vulnerabilities on a website.
It was a great couple of days with a long list of take-aways so I decided to write a summary
which I expect I'll often return to myself.
It's fairly easy to build a website nowadays with all the tools and frameworks available.
What seems close to impossible after listening to Troy is making the website secure.
Attackers are ingenious, they are creative and they are prepared to spend both
time and money to gain access to your precious data.
We as developers, have an obligation to understand how attackers work and to make their job as
difficult as possible. I don't say that we should aim to stop them altogether because I don't think we can.
What we can do is slow them down and make them jump through hoops to access our users passwords,
email addresses, shoe sizes and whatnot. If we can make it hard enough, perhaps they'll move on to another
target and leave your site alone.
How do we slow attackers down?
There is no end to the countermeasures we can put in place because there is no
end to the different approaches that attackers will use.
I've put together a non-exhaustive checklist with some of the essential defences we can
and should put in place to protect our users from attackers, and in some cases, from themselves.
Not all of the items in the list applies to every case and in some cases, partial implementation may suffice.
It will come down to a number questions you'll need to ask yourself:
- What data do I have?
- How attractive a target am I? (You might be more attractive than you think. Users tend to reuse passwords for example...)
If I implement defensive measure A, will usability of my service suffer too much?
Can I implement a version of A that provides sufficient protection?
To the list. As mentioned, this list is not exhaustive and everything might not apply to your application
but it will hopefully cover the most obvious entry points an attacker will attempt to use.
Nothing in this list in is anyway new or something I take credit for, it's just a summary of some
of the practices that I think should be implemented when applicable.
You'll find that there is some repetition in this list. The reason for this is that one security risk isn't
necessarily independent of all others. Likewise, some counter measures will purpose more than one
It is also important to remember that things change. This article is written in 2016.
New threats will come, new standards will be implemented. Keeping up to date is crucial.
SQL Injection (SQLi)
SQLi is at the top of the list for good reason;
QWASP listed injections as the top risk in 2013.
OWASP doesn't limit this to SQLi but they specifically mention SQLi and it is something that is frequently found in web applications.
- Parameterize your queries
Never use the raw input in a query.
Always parameterize the values when you create the query.
This will protect you from most potential attacks.
- Hide errors
Never throw database errors all the way back to the user.
Log them internally and give the user a friendly message that doesn't give away details of your setup.
Cross-Site Scripting (XSS)
Cross-Site Scripting, or XSS vulnerabilities allows an attacker to inject code onto your website
which is then executed in somone else's client.
- Validate your input
Never trust anything a user gives you. Always work under the assumption that any data provided
to you has malicious content.
- Sanitize your output
This goes hand in hand with validation and is equally, or even more, important. Whenever you output something on your web site,
from HTML or CSS and so on.
- Trust no one
I want to stress this further.
Imagine that you read data from your database and you put the contents on your web site without sanitizing
it because you think "This is my data, it's fine".
But what if someone found a way to put malicious data in your database?
What if someone at your workplace put malicious data in the database?
Trust no one, not even yourself. Always sanitize.
Secure your Cookies
Some Cookies are not meant for the client but are merely something the client needs to send to the server.
One example of this is a Cookie containing a session id. It should only ever be read by the server.
If possible, you should also set the Secure attribute on your Cookies so that they are only sent if the connection
There is an IETF draft for the SameSite attribute
that looks promising and adds additional protection against both XSS & CSRF attacks.
Although not fully implemented I think it's worth taking a closer look at.
Cross-Site Request Forgery (CSRF)
CSRF is a vulnerability where an attacker makes request
via your browser to another site without your consent, often by hijacking latent sessions in the browser window.
- Check the request origin
Verify that the Origin and/or Referer headers are legit.
- Use CSRF tokens
A CSRF token
is a random key that is sent both as part of the form/url and as a cookie.
The server can then match the two to ensure that the request is valid.
- Password protect sensitive data
Do you have a form where your users can change password, email or other sensitive data?
Password protect it even if they have to be logged in to use it.
- Use a strong algorithm
MD5, SHA1 and the likes aren't good enough. Use tools such as bcrypt instead.
- Add workload
Hashing passwords isn't something you do too often. Usually it's during registration, login and password resets.
Let it take a little bit of time. This will slow down an attacker that's gotten their hands on your hashes.
What's a good number of milliseconds to spend hashing?
Good question, it depends on a lof ot factors but a couple of hundred milliseconds shouldn't affect your application too much.
- Always hash, even if there is no need to hash
If it takes your application 300ms to hash a password to compare the provided password with the hash in
your database, a login request for a user that is not found will respond ~300ms faster than one where the user is found.
From this, an attacker can deduce that the provided username doesn't exist.
Even if there is no user, and hence no need to hash the password, do it anyway for consistency.
Account enumeration is used to find out if an account exists in an application or not.
Often such information is leaked by login, registration and forgot password flows.
- Be consistent
Example: If someone enters an incorrect username on login, give the same error message as if the password was incorrect.
- Don't leak information
"Sorry! The email is already taken!" is a common error message on registration.
This tells an attacker that the email exists. Treat a failed registration just like you would a successful one:
"Thanks for registering! An email has been sent with a verification link.". If the email was already taken,
send a different email from the verification dito informing the user that there already is an account and
that someone (else?) have tried to use the same information to register.
In some cases, it makes sense to use ReCaptcha or equivalent by default.
Registration is one example. In other cases, a ReCaptcha will be too intrusive, say on login.
However, when someone has several failed login attempts in a row it might be time to put some pressure
on whoever is on the other end and require that the person at least confirms that he or she is human and not a bot.
- Use it
HTTPS is a huge topic so I'll just say this: use it.
With HTTP/2, the arrival of TLS and projects such as
Let's Encrypt and other providers giving free certificates,
there is almost no reason not to secure your traffic.
General Data Leakage
Don't expose error messages such as exceptions, database errors and the like to your clients.
Log everything you need and present the client with a message that is informative enough without giving
away any details of your tech stack.
- Never disclose debugging/server information
More of the above. For example, don't put a phpinfo page on your production server.
The less an attacker knows about your system, the more they have to work to gain access.
- Clean up your response headers
A response header most web servers supply is the Server header. This usually gives away a little
bit of information about your tech stack. If you can remove it, do so. If you can't, at least try to remove
as much detail as possible. It's bad enough that we say that we're running on nginx, we don't have to give away
the exact version too.
The Server header is just an example. Take a look at your response headers and strip away those that aren't useful
or those that give away details of your setup. This will also reduce your overall bandwidth usage.
Content Security Policy (CSP)
Using Content Security Policies is a way of preventing
clients from loading resources from sources other than those you allow.
support will not allow that script to load.
You enable CSP on your website by adding the Content-Security-Policy response header with a proper value.
This sounds very good but unfortunately the support for CSP seems to still be
somewhat lacking and a little dodgy.
You can use it but make sure you test everything in all browsers to ensure you don't block resources you
really want to allow. There is also a report functionality for any breaches to the CSP and you can use CSP
in report mode only if you want to try it out without enforcing the policy.
If you want to try the report function, just change the header to Content-Security-Policy-Report-Only.
There are a lot of potential entry points to (web) applications.
This article just covers the most basic defensive measures you can (and probably should) look into.
There is a lot more reading to do on the topic,
Troy Hunts ultimate list of security links
is a good place to start. A Google search for the right keywords will give you tons more to read.
In short, keep on building great stuff on the web but stay safe!