Ruby on Rails

The hub of Aura activity, the cortex that binds the forum together.
nik-w
Scavenger
Posts: 15
Joined: Fri Aug 15, 2008 11:31 pm

Post by nik-w » Wed Sep 10, 2008 3:41 pm

ok, you should have it now (again)!
User avatar
Chewi
Anti-Hero
Posts: 3521
Joined: Sat Jun 12, 2004 3:51 pm
Location: Edinburgh, Scotland

Post by Chewi » Wed Sep 10, 2008 4:18 pm

Heh oh dear. I've only had a quick look but I've seen all I need to see. You better delete those copies before someone 0wnz your site. I can't delete them myself because I don't have permission. You've left yourself wide open to the three major kinds of attack, SQL injection, XSS and CSRF. You could argue that normally these pages wouldn't be publicly available but CSRF attacks totally work around that so even locking them away behind a password doesn't help.

This is the sort of code I was writing before I started with Rails. It's the sort of code that some professionals worryingly still write. I've worked with this guy who's been in the industry longer than I have. He tried Rails and liked it at first but got in a muddle over routes and went back to ASP. We'd both forgotten about the named routes feature, which is really what we needed. That's a shame because he probably would have stuck with Rails otherwise. He later wrote a site in ASP for a mutual client. That client asked me to try and hack into the site. I'd never actually done any hacking before and I didn't have access to any of the source code. Yet with just a few minutes work, I was able to use flaws in the site to hack into the CEO's e-mail account. The attacks I mentioned above don't allow you to do that directly but as well as making some of the common mistakes, he'd made the even bigger mistake of storing the user passwords as plaintext in the cookies. You shouldn't store the user passwords in the cookies at all, nevermind as plaintext! A bit more work and I had plastered the site's background with dancing Chewbaccas. He has since addressed these issues but the client asked me (initially without his knowledge) to rewrite the site in Rails. It was more to do with scalability and personality clashes with the guy but still, those security issues can't have reflected well on him.

The SQL injection attack was beautifully illustrated by xkcd...
Image
Last edited by Chewi on Wed Sep 10, 2008 5:22 pm, edited 1 time in total.
nik-w
Scavenger
Posts: 15
Joined: Fri Aug 15, 2008 11:31 pm

Post by nik-w » Wed Sep 10, 2008 6:03 pm

Hrm... I've heard of SQL injection, but I thought that was just relevent if you actually did something with the stuff being transferred between the database (like store bits of code in the database). All I ever do is store text then retrieve it later (and the most processing I ever do on stuff pulled from the database is comparisons). No idea what XSS or CSRF are. The "jobs" site is just something I wrote for my dad which is only viewable to people on the local network, but the other (and several others based on the same code) is publically viewable. Can't say I've ever had any probs with people trying to hack in (apart from dictionary and semi-intelligent (i.e. specific usernames) SSH attacks).

I've no idea how to write differently, so how can I avoid such problems (short of learning RoR overnight!:p)? I never store any usernames or anything in cookies. The only time I use cookies is on another site of mine that I've had friends translate into several languages - that just stores the preferred language in cookies and simply reads the cookie and extracts the first 2 characters from it. I've often thought that it's not great storing the db username and passwd in plain text in a file, but short of doing a whole encrypt/decrypt thing with it (which if someone was trying to hack into it, this would likely not stop them), I can't think of another way of doing it.
User avatar
Chewi
Anti-Hero
Posts: 3521
Joined: Sat Jun 12, 2004 3:51 pm
Location: Edinburgh, Scotland

Post by Chewi » Wed Sep 10, 2008 8:21 pm

Okay, some ground rules. Don't just pass user input directly into SQL queries. Take this line of your code...

Code: Select all

$sql = "SELECT `client`, `deadline`, `detail`, `finished` FROM `jobs` WHERE `id` = " . $_GET['id'] . ";";
If someone browses to the page with ?id=123;DROP TABLE jobs, say goodbye to your jobs table. That's SQL injection and it can do worse. Rails completely bypasses the issue. That line would look like one of the following...

Code: Select all

@job = Job.find(params[:id])
@job = Job.find_by_id(params[:id])
@job = Job.find(:first, :conditions => [ "id = ?", params[:id] ])
@job = Job.find(:first, :conditions => { :id => params[:id] })
They all do the same thing, except the first one will raise an exception if the record is not found. The others will just return nil. First of all, these are much nicer because, apart from being shorter, you don't have to do any further processing. The data is all there in a very useful object, not just some array. More importantly, in all cases, the given user input is sanitised by Rails before being sent to the database. Notice that it is never given directly as part of an SQL string. The user input is always separated.

XSS is a bit harder to explain. The basic rule is don't allow users to add JavaScript ANYWHERE on ANY page. It is easier for them to do this than you might imagine. You know that client name field?

Code: Select all

<td>Client Name: <input type="text" name="client" size="50" maxlength="500" value="<? echo $arr['client'];?>" /></td>
What's to stop them entering alert("hello");. Harmless enough you might think. But what if they enter document.write('');. foo.com is a server belonging to the hacker. bar.gif is a 1x1 pixel transparent GIF. document.cookie contains the contents of the cookie belonging to whoever visits that page. Because the image is transparent, you'll never know it's there and the cookie of every user that visits that page will be logged on the attacker's server log. From this, they can determine each user's session ID and hijack their session. If you're an admin and they hijack your session, then they'll be an admin too. Rails makes it easy to avoid this.

Code: Select all

<td>Client Name: <input type="text" name="client" size="50" maxlength="500" value="<%=h @job.client %>" /></td>
See that h? That's what does all the magic. It should actually be h(@job.client) but the above line does the same thing and putting the h off to the side like that looks tidier and it quickly becomes a habit. So what does it do? It escapes all HTML by just replacing a few characters, most crucially with . That'll kill any JavaScript. Why escape all HTML and not just JavaScript? You can do that instead but JavaScript can be included in the most bizarre places. Trying to catch every single catch is extremely dangerous. That is why allowing BB code and textile for user input is much preferred to allowing HTML. You can also avoid XSS by resetting sessions when users log out, timing out sessions when they don't and tying sessions to IP addresses, though the latter has its problems.

I'm too tired to explain CSRF now. :P Look it up and also look up what Rails does to prevent it. It's the forgery protection feature.

I'll explain how you should handle passwords tomorrow.
Last edited by Chewi on Wed Sep 10, 2008 8:23 pm, edited 1 time in total.
nik-w
Scavenger
Posts: 15
Joined: Fri Aug 15, 2008 11:31 pm

Post by nik-w » Wed Sep 10, 2008 9:05 pm

So to get past the SQL injection thing, apart from making sure that the db user has as few privs as necessary (only root ever has DROP privs, but obviously you can just "DELETE FROM `table`"), I could strip out [a-zA-Z] which would work for most cases, but not where the parameter has to contain text. Maybe search for ; and removing it and everything after that? For XSS, could it be achieved by searching for < and removing it and anything after it? CSRF looks to be the same as "phishing" - perhaps it's the proper term for it? I don't think that's something I need to be particularly worried about at the current time, as I don't write any sites where people store data about themselves like they would on a banking site or whatever.

Edit: After a bit of poking about, I think I've got the whole sql injection and XSS stuff more or less sorted - seems PHP automatically escapes the contents of POST, GET, & COOKIE. However, apparently it's not entirely reliable, so some bloke has written a more robust thing, and I've altered it further...

Code: Select all

function sql_quote( $value )
{

  if( get_magic_quotes_gpc() )
    {
      $value = stripslashes( $value );
    }
  $value = strip_tags(htmlspecialchars($value));
  //check if this function exists
  if( function_exists( "mysql_real_escape_string" ) )
    {
      $value = mysql_real_escape_string( $value );
    }
  return $value;
}
Firstly, if this magic variable escaping is on, undo what it's done, then run strip_tags & htmlspecialchars (which together should nuke any html found in there), then do some more robust mysql-approved escaping before chucking it back out the other end! I've just gone through all my publically-accessible sites and added this bit to any mysql-ness that's going on! I've also installed something called mod_security - it's an apache module which apparently does good things!:p It might not be as secure as RoR, but hey - it's better than it was!:D
Last edited by nik-w on Wed Sep 10, 2008 11:03 pm, edited 1 time in total.
User avatar
Chewi
Anti-Hero
Posts: 3521
Joined: Sat Jun 12, 2004 3:51 pm
Location: Edinburgh, Scotland

Post by Chewi » Thu Sep 11, 2008 11:45 am

I've not heard of mod_security but remember that your server is only as secure as its weakest point.

A lot to say but for now, passwords. It goes without saying that storing passwords as plaintext in the database is bad. Encryption is not the answer but that's not simply because they could decrypt it. If that were the entire answer, we would be asking ourselves why we bother encrypting anything at all. The real reason is we don't actually need to know what the passwords are. This is where hashing comes in. Hashes are (ideally) not reversible, short of brute forcing. Instead of comparing the plaintext passwords, we compare the hashed password in the database with the hash of the password given at login.

That's not the end of the story though. It is brute forcing that led to the popular hashes MD5 and SHA1 becoming practically useless for passwords. If the hacker obtained the password database, all they had to do was generate an MD5/SHA1 hashed list of common passwords (or just buy one) and then reverse-lookup to see what the original passwords were. These lists are called rainbow tables. The technique can be made infeasible through the use of salts. Before hashing the password, you generate a random string called a salt and this is used together with the password when creating the hash. The salt is stored in the clear together with the hash. Even though the hacker knows what the salt is, they would need a different rainbow table for every single salt, and because each salt is different, the whole thing becomes computationally unfeasible. It would take them the same amount of time to crack just one password as it previously took to crack all of them. MD5 and SHA1 are still considered weak even with salts though so these days, there are much better algorithms. With Rails, I always use the bcrypt-ruby gem.
Last edited by Chewi on Thu Sep 11, 2008 11:48 am, edited 1 time in total.
nik-w
Scavenger
Posts: 15
Joined: Fri Aug 15, 2008 11:31 pm

Post by nik-w » Thu Sep 11, 2008 12:33 pm

That's cool! I'll look into that if ever I need to start storing passwords and stuff (none of my sites require logins)!:) My sites all have fairly low userbases, so there's less chance of someone wanting to try and attack my server than a more widely-used site. Regardless, I try and do what I can regarding security without going to extreme lengths - my IP address can't be pinged, and the SSH filter is (as you found out) somewhat over-zealous! I've got auth.log going to tty11 and it's amazing how many people try dictionary attacks on my server - though they don't get very far, 'cause once they try two invalid users or one invalid password attempt for a valid user, their IP gets thrown over to iptables, which blocks it forever more! I also regularly update all the packages on my server now, so anything that gets a new version released is updated (granted this is as likely to introduce security holes as fix them, but hey)!
Post Reply