Friday, March 19, 2010

Hardening PHP: SQL injection - Complete walkthrough

Below are the slides from the presentation I recently gave on SQL injection on OWASP Poland Chapter meeting. The materials teach how to use prepared statements, how to escape and write secure stored procedures. Many PHP projects are covered - PDO, Propel, Doctrine, Zend Framework and MDB2. Multiple gotchas and caveats are included. I discuss why escaping is usually the wrong choice, which practices to avoid or follow and how stored procedures sometimes offer no protection at all.

I tried to make this as complete as possible, so a PHP developer could learn how to protect his applications no matter what framework / database he uses.


English version


Polish version


You could also watch video recorded from the presentation. There are already some comments on the slides on niebezpiecznik.pl (Polish), but of course feel free to add comment here.

Wednesday, March 3, 2010

You're from Cracow and want to beat SQL injection?

Anyone interested in secure development probably knows what OWASP is. If not - it's a worldwide non-profit organization focused on web application security.

There is an upcoming OWASP Poland chapter meeting in Cracow, Poland next week. This time it is focused on secure PHP application development.

I will be giving a presentation there - it's entitled
"SQL injection: Complete walktrough (not only) for PHP developers".

We will talk a bit about the theory and demonstrate an attack. Then I'll show how to write code immune to SQL injection in various PHP frameworks, using various databases. We'll also talk about writing secure stored procedures in Oracle, MS SQL Server and MySQL. Different gothchas, bugs and tricks will be covered, so even if you think you know the subject - it will surprise you.

The meeting is totally free and no registration is required*, so if anyone wants to develop securely in PHP or deals with databases (who doesn't?), please bring your fellow developers and come. It's important - please come and let's beat the SQL injection once and for all! 

I'll share the floor with Łukasz Pilorz who will talk about creating Secure PHP framework - and, given some internal details, I can assure you there's much to expect from this talk - don't miss it!

Date: 10.03.2010 (Wednesday), 17:00
Place: Compendium Centrum Edukacyjne, ul. Tatarska 5, Kraków, I piętro

Update: Read the full announcement from OWASP mailing list.

* If you're planning to go, please drop a mail to Przemysław from the link above to prepare the room for the audience.

Wednesday, December 23, 2009

5 ways to prevent clickjacking on your website (and why they suck)

Clickjacking attack is a very nasty attack. The most common form of it is when an attacker creates a webpage and tricks the visitor to click somewhere (on a link, button, image). Attacker in the code of his website includes a victim website (like Facebook, your webmail, amazon) that is cleverly hidden from the user and placed so that a user actually clicks on a victim website. Citing the example from OWASP page on clickjacking:

For example, imagine an attacker who builds a web site that has a button on it that says "click here for a free iPod". However, on top of that web page, the attacker has loaded an iframe with your mail account, and lined up exactly the "delete all messages" button directly on top of the "free iPod" button. The victim tries to click on the "free iPod" button but instead actually clicked on the invisible "delete all messages" button. In essence, the attacker has "hijacked" the user's click, hence the name "Clickjacking".
The problem with clickjacking attack is that it is extremely difficult to prevent. Unlike other popular vulnerabilities like CSRF, XSS, SQL injection, this one is based on a functionality that is widely used in the web nowadays - frames (I'm skipping the case of plugin-based-clickjacking for clarity here). Frames allow you to nest one webpage or widget in another page - this is now used for login pages, commenting, previewing content in CMSes, for JavaScript interactions and a million other things.

Browsers nowadays use same origin policy to protect your data if you're framing or being framed from another domain (this prevents JavaScripts from talking to each other and accesing documents across the domain boundary). But JavaScript is not required for a clickjacking attack - CSS is enough. In the simplest form (e.g. used in recent Facebook users attack), you're just using a small <iframe>, and position it absolutely. The rest is just social engineering.

Our users have a few options to protect themselves. So maybe 1% of them will be "protected". But what can we - web developers do to prevent the clickjacking on our sites? Sadly, not much, but here's the list:

Monday, December 21, 2009

New Facebook clickjacking attack in the wild - fb.59.to

There's a malicious website set up at http://fb.59.to that tries to trick users into a clickjacking attack that shares the link on victims' Facebook accounts.


Some Facebook users today saw a comment looking like this (new pix!):







Clicking on the comment that links to

http://www.facebook.com/l.php?u=http%253A%252F%252Ffb.59.to%252F%253F4ff11a526ae73e9f170bbe6702ebb93c&h=..somehash...&ref=nf

redirects users to http://fb.59.to web page.

On this page they are given a fake Turing test that tricks them into clicking a "blue button" which is their clickjacked Facebook page positioned at adding a new comment ("Share" button). The whole web page looks like this (clickjacked area is marked green):



In page source we can see that there is a IFRAME element:

<iframe frameborder=0 scrolling=no height=25 width=100
src="2.php?u=http://fb.59.to/?...somehash...."
></iframe><span style=background-color:yellow;><font 
style=font-size:13 ; color=white>

The target URL (2.php) has another IFRAME which in turn has yet another one with the target page being

<div style="left:-90px;top:-386px;position:absolute;"
<iframe height=400 width=250  src="http://www.facebook.com/sharer.php?u=http://fb.59.to/?hash" 
frameborder=0  scrolling=no> </iframe>
</div> 

Clicking on the button shares the malicious link on Facebook.

The page has a meta-redirect set up to a Youtube movie launching  in 12 seconds so a users might get the impression that the movie launched because they successfully passed the Turing test.

Multiple iframes are probably set up to trick clickjacking protections within browsers. A quick look tells that currently Firefox and Chrome are vulnerable to the attack, IE and Opera being safe, although that requires a bit more time to investigate.

Update: The attack does not work in IE and Opera only because of incorrect HTML used in one of the pages in this malicious site. Doing a simple fix in HTML makes both mentioned browsers also vulnerable to the attack.

Thanks go to Grzegorz Ciborowski and Pawel Czernikowski for detecting the attack.

Wednesday, October 21, 2009

Hardening PHP: magic_quotes_gpc - False sense of security

Writing secure applications from the ground up requires a programmer to fully understand all the features he uses to protect his code from vulnerabilities. Today's languages provide many ways to ease coders with hardening their code. You can rely on built-in libraries to filter input variables, escape the output in HTML etc.

However, staying secure is not that simple - you need to know all the limits and caveats of your tools, because sometimes they promise you something but do not deliver - as it is in case of magic quotes functionality.

Magic quotes

PHP magic_quotes_gpc are sometimes recommended as a protection from SQL injection .When this ini setting is on, all GET/POST/COOKIE variables are automatically run through addslashes() function, basically escaping all quote characters with "\".

All is good

Let's look at an example, where that protection would work:

<?php
// some admin login functionality
$logged_in = mysql_query("SELECT 1 FROM users WHERE login='admin' AND password=sha1('" . $_POST['password'] . "')");
// process $logged_in

When magic_quotes_gpc are ON injecting the PASSWORD variable won't work. A simplest attack vector "' OR 1=1 -- " would transform to
SELECT 1 FROM users WHERE login='admin' AND password=sha1('\' OR 1=1 -- ')
That's a perfectly escaped SQL query, nothing can get injected. magic_quotes_gpc escape the dangerous single quote for us. So - goodbye SQL injection? WRONG!

Magic quotes only insert a backslash before a few characters, nothing more. That protects you from SQL injection only in some particular cases like above and only by coincidence. To prove that, let's analyze the following script.

So much for magic...

<?php
// display a single post based on id
$post_data = mysql_query("SELECT title, content FROM posts WHERE post_id={$_GET['id']}");
// display $post
The same "' OR 1=1 -- " transforms to invalid query:

SELECT title, content FROM posts WHERE post_id= \' OR 1 = 1 --

and if you have display_errors ON, you just made the attacker very happy with your database details outputted right before his eyes. Even if you don't, there still is a possiblity of blind sql injection.

But what if an attacker set id parameter to one of these:
  • 1 AND (SELECT COUNT(*) from another table_name) BETWEEN 0 AND 100
  • -10000 union select user_password from users where user_login=CHAR(97,100,109,105,110) (admin)
You won't get any protection from these vectors - they slip right under the eyes of  magic_quotes_gpc - they don't contain quotes.

In this case a single mysql query parameter that was left unprotected could lead to your whole database content dumped to the attacker (I'm not kidding - see sqlmap and try for yourself).

Know your context

The fundamental problem with magic_quotes_gpc is that they know nothing about the context. They don't know if you're using the data to insert it into MySQL, Oracle, or if you're writing to a file. Maybe you're sending it through SOAP or displaying it in HTML? Or maybe all of it. They just don't have enough information, only you know it. Escaping values depends on a context in which they are used. You should disable magic quotes now also for the following reasons:
  • they mangle your data before you get the chance to reach it and can lead to double escaping,
  • most frameworks expect them to be off, and if they're enabled, try to undo these changes at bootstrap,
  • they're deprecated in PHP 5.3 and will be removed in PHP 6.
But you need to escape your data nonetheless. Follow these simple rules:
  • to output to a HTML - use htmlspecialchars()
  • to escape a string in MySQL - use mysql_real_escape_string(). 
  • to escape a string in Postgresql - use pg_escape_string() 
  • When you are expecting an integer in SQL query - simply cast to int
  • ALWAYS use prepared statements when querying databases.
If you want to read more on bypassing magic quotes and SQL injection in general, I recommend:
Have any comments on the subject to share?  Please do so, this is my first security-related post and I'd like to hear your opinion.

Tuesday, September 29, 2009

Weird date format from FreeTDS with mssql.datetimeconvert = 0

Today I encountered a problem with date field format in MS SQL Server 2000. We are accessing the server through FreeTDS and for data abstraction we are using latest stable PEAR MDB2_Driver_mssql v 1.2.1.

We were migrating to newest daily FreeTDS. As soon as we recompiled FreeTDS, our PHP applications started acting weird, showing invalid dates. We narrowed down all problems to a change in date format we got from SQL server.

A simple query:
echo $mdb2->getOne("SELECT datefield FROM table");
now returned some weird data format:
2009-01-01 31695:06
However, when we used mssql_* functions, skipping MDB2 altogether the date format was OK. I noticed one bug report: mssql.datetimeconvert force set to 0 - generate wrong dates. It was correct - even though we used mssql.datetimeconvert = 1 in our php.ini, MDB2 driver sets it to 0 on connect().

But the same code used to work with older FreeTDS. It seems that FreeTDS changed its internal date format, some locale settings etc. And disabling datetimeconvert by MDB2 triggered the problem as we started getting this new weird format.

I didn't want to mess up with internal date handling by MDB2 and keep patches to apply on future MDB2 updates - luckily we found a simple solution: recompile PHP with the newest FreeTDS libraries and everything goes back to normal. The same script now outputs:

2009-09-29 18:11:00

Even though mssql.datetimeconvert = 0 thanks to MDB2, we are receiving standard Y-m-d H:i:s format we love so much. Hope that helps anyone with similar problem.

Thursday, September 24, 2009

Using shared SVN working copy on samba

Part of deployment procedure for PHP applications in company I work for is to update the code on a staging server through a samba share. The code is then replicated to production machines.

We use Subversion as our VCS of choice so we simply kept working copies on staging server and used TortoiseSVN to do an svn update. Later on we would e.g. migrate the database, update configuration files etc. This proved to be very simple and effective technique.

However,when our administration changed authentication procedures and gave every developer separate account (we used a single user name to log in to our samba share before, now we authorize through NT domain) - it suddenly stopped being so effective.

As soon as one developer updated the staging working copy, he started "owning it". Anyone else trying to update it later on got access denied errors. The reason? Subversion client created files in .svn that are read-only and these could not be deleted and recreated by anyone else (and Subversion client does this during svn update).

Luckily, there is an easy solution, found here and also mentioned in SVN FAQ. In our case, inserting:
[global]
delete readonly = yes
in smb.conf solved our problem. Any developer that is authenticated to use our staging share may now update the working copy.

Disclaimer: Although using working copy on samba share is not recommended by Subversion team, we use this technique for years now with different server and client versions and this was our first problem. However - we don't do development on this working copy, we only update it, so it's not a real full-blown multiuser working copy.