If you're like me, you got into this business because you love building awesome apps.
Let's take a second to reflect on the good times.
...back to the real world.
It sucks that you even have to worry about security. It's just sick that there are people who wake up every morning and try to smash the things you're building.
But they do...And when they succeed, it's your fault.
Your client can't make your code secure. Your manager can't. It's all on you. So you better know what the hell you're doing.
Should you read this?
- Do you try to follow best practices for security but don't really understand them?
- Are you unclear about how XSRF, MITM, and XSR attacks work?
- Do you think that because your apps are small, or internal, they're not at risk?
...then this guide is for you.
You're being attacked, now! Freak out!
If it's on the internet, it's under siege. Automated systems are scanning around the clock for vulnerable web apps.
By the mafia
Are you storing credit cards in your database? Unsalted password hashes? You're screwed.
By bot-masters
When you run a botnet, more bots equals more money.
- So you buy an exploit pack!
- Set it up to install your malware when an old browser hits the payload
- Hack some legit sites to deliver the payload to lots of users.
By governments
It sounds like a joke. But the Chinese government has thousands of people hacking systems in search of:
- Trade Secrets
- Government Intelligence
- Information about political enemies.
They were recently caught on tape.
And, umm..camera. Doesn't that look like a fun job?
By Hacktivists
Do you sell depleted uranium slugs to the DOD? Then you should have enough money to hire someone who knows what they're doing.
Required reading
This is the inadequate guide. Did you expect it to be comprehensive? At minimum you should also read:
Eight easy ways to get p0wn'd
We're going to cover eight classes of vulnerabilities that are crucial for web developers to understand.
- Non Technical
- Cross Site Scripting (XSS)
- Cross Site Request Forgery (CSRF, XSRF)
- Man in the Middle (MITM)
- SQL Injection (SQLI)
- Mass Assignment & Parameter Injection
- Denial of Service (DOS, DDOS)
- Platform Attacks (Hacking linux boxen)
Non-Technical Exploits
Sometimes the obvious problems are the hardest to see.
The number and variety of stupid things you can do is endless, so we'll just point out a few.
Don't:
- Send plaintext passwords via email
- Use the same password everywhere
- Trust people who show up at your office with cookies
- Carry sensitive data on your laptop
Cross Site Scripting
If I'm an attacker, I'd be very interested in ways to insert some of my JS into your page.
Once I do that I can:
- Steal session cookies
- Bootstrap an exploit kit into the dom
- Make the pages say wacky things
There's a simple defense
To thwart me, you need to escape any text that a user has entered, so javascript gets turned into harmless text.
You want this
<script>alert('I am stealing your cookies! Ha!');</script>
not this:
<script>alert('I am stealing your cookies! Ha!');</script>
Except when there's not
Here's the thing. If you have an old rails 2 application, you probably have XSS vulnerabilities. That's because rails 2 made you manually escape untrusted input, using the h() method.
Rushing to meet a deadline? Pushing a quick bugfix? Outsourcing work to a junior developer? The h() would be forgotten. Kittens would cry.
Rails 3 and 4 are much better. Output is escaped by default. But it's still possible to shoot yourself in the foot.
Cross site request forgery
If a user wants to change their password, they post a form, right? So what happens if the form isn't on your website?
The request still goes through. Attackers can exploit this fact.
Anatomy of a CSRF attack
- I'm an attacker
- I know you're logged in to your app as an admin
- I create a "change password" form disguised as something else
- You submit the form and, without realizing it, you've changed your password.
And it's even worse if you're using GET requests instead of POSTS.
I wouldn't even have to trick you. I could cause an authenticated request to happen just by adding an image tag to a page you visited.
Rails saves your bacon
Luckily, rails sidesteps this class of attack by offering CSRF protection. You may know it as "that weird text that gets put in all my forms for some reason."
The weird text is a secret key that is stored in the user's session. If the user submits a CSRF token that doesn't match the one in the session, you get an exception.
But you can still screw it up
Of course you can disable CSRF checking like so:
skip_before_filter :verify_authenticity_token
And you can add GET routes for things that should be POSTS:
MyApp::Application.routes.draw do
match "/launch_all_the_missiles", to: "missiles#launch_all"
end
But you wouldn't do that, would you?
If you're not using rails
And keep in mind that if you're using a minimalist framework like sinatra or goliath, you'll need to handle this yourself. Here's an example.
Man In The Middle / Packet Sniffing
So you need to send some cash to your rotten no-good cousin Vinny. You take the cash, put it in an envelope, write that bastard's address, add a stamp and hand it to the mail man.
A week later, the envelope arrives with no cash. This is a simple man in the middle attack.
All your cookie are belong to us
The same thing can happen with your user's cookies.
Cookies are usually sent with every request. If they're sent in cleartext over a public (wifi) network, game over.
An evil-doer running FireSheep can easily get copies of the user's cookies and compromise their account.
SSL to the rescue
The easy answer is to use HTTPS for everything. The people in the cafe can still see the user's traffic, but it's encrypted.
A not-so-easy-but-perhaps-better-in-some-circumstances answer is to use rails' secure cookies.
Secure cookies will only be sent to the browser over an HTTPS connection.
...that is if you're running rails >= 2.3.10 or >= 3.0.2 .
Before that, secure cookies could be sent over plain text. Don't ask.
SQL Injection
99% of all websites work by glueing fragments of text together to make little programs in a language called SQL.
Just think about that. Do you have any idea how backasswards this is?
But like it or not, this is the way things work. And based on the fact that the internet exists, it must work reasonably well.
But one side effect of this is that if a hacker can add their own bits of text into little SQL programs we are absolutely screwed.
Example
Imagine that your contractor sends you some code that looks like this.
Building.where("st_intersects(st_setsrid(st_makebox2d(st_point(#{params[:northeast][:lng]}, #{params[:northeast][:lat]}), st_point(#{params[:southwest][:lng]}, %{params[:southwest][:lat]})), 4326), buildings.location)")
This is a huge mistake. I can make the params[:southwest][:lat]
contain
")), 4326), buildings.location); UPDATE users SET admin=1 WHERE id=666;--"
And now I have admin privileges.
Rails protects us...
Rails protects in some cases. If we'd used where
correctly, the parameters would have been excaped, and SQL injection attempts would fail.
where("st_intersects(st_setsrid(st_makebox2d(st_point(?, ?), st_point(?, ?)), 4326), #{table_name}.location)", box[:northeast][:lng], box[:northeast][:lat], box[:southwest][:lng], box[:southwest][:lat]).
...until it doesn't
But there are lot of places where rails uses raw sql. Places you might easily overlook.
For example, did you know that in SomeModel.sum("amount")
, the "amount" string is added to the query without being escaped?
So if you have a report where you sum parama["col"], you're in trouble.
Here's an example IRB session, showing the consequences of passing untrusted input into the sum method:
pry(main)> User.sum(%[id) AS sum_id FROM users; update users set
admin='t' where id=224;--])
(22.4ms) SELECT SUM(id) AS sum_id FROM users; update users set
admin='t' where id=224;--) AS sum_id FROM "users"
=> "0"
The same is true for #pluck, and a host of other methods.
For more detailed info, check out: http://rails-sqli.org/
Mass Assignment Attacks
March 4, 2012, Russian hacker Egor Homakov disclosed a mass assignment vulnerability that could let an attacker masquerade as any github user.
How github was hacked
I bet you've written code like this hundreds of times:
User.create(params[:user])
This of course creates a user with the given params.
Prior to Rails 3.2.3, that meant that any parameter that was input would be assigned to the model...unless you explicitly specified otherwise.
Problem is, this was easy to forget. And so you find yourself in a world where an attacker just has to send in the right POST request to cause this to happen:
User.create({admin: true, ...})
Or in github's case, something like this:
PublicKey.update({user_id: "123"})
It's an easy fix
Of course you avoid these problems by using attr_protected
, or attr_accessible
class PublicKey < ActiveRecord::Base
attr_protected :user_id
end
But this is easy to overlook.
Therefore, if you have an app running rails < 3.2.3, it's recommended that you make all attributes protected by default. Then you're forced to explicitly list the ones you want to be whitelisted.
This article shows you how: https://gist.github.com/petenixey/1978249
Rails brings down the hammer
Rails 3.2.3 comes like this out of the box. And Rails 4 will use an altogether different whitelist mechanism, called Strong Parameters. It's available as a plugin if you can't wait: https://github.com/rails/strong_parameters
Denial of Service
How many people have you pissed off today? Last month?
DOS, and DDOS attacks are infosec's version of a rage comic. They're not going to steal your data or spread malware. They just want to shut you down.
DDOS is crude but effective
To mount a destributed denial of service attack, just call up 500 of your closest friends, have them head over to amnesty international's website and keep pressing refresh for an hour. Bots can be substituted for friends.
If you have problems like these, talk to your ISP, use cloudflare, etc. They're pretty boring and well known problems.
Algorithmic Complexity Attacks
That just sounds freaking cool, doesn't it?
These take advantage of a particular weakness in your operating system, application stack, etc. to fill up your server's memory, peg your CPU, etc.
For example, the classic SYN flood attack works by sending SYN requests to a server. The server opens a ton of connections which aren't closed. With enough open connections, the OS will run out of ram.
A recent exploit
Ancient history? Not really. A recently patched flaw in the JSON gem would let an attacker max out your ram by creating millions of Ruby symbols. Symbols are never garbage-collected.
Good old fashioned server exploits
This is really what we think of when we think of hackers, right? People using buffer overflows to cause apache to run arbitrary code as root.
But like most boring jobs, this too has been automated. If you look at your system logs right now, I bet you'll see bots trying to brute force an SSH login.
Automate attack detection
You can use something like fail2ban to automatically block the IP addresses of hosts that are attempting known attacks against you.
Do the things you know you should do.
Keep your OS Patched. Don't run services you don't need. Don't allow password authentication for SSH. The list goes on.
Conclusion
PANIC!
Then start taking security seriously. This is part of your job whether you like it or not.