Showing posts with label php. Show all posts
Showing posts with label php. Show all posts

Friday, December 7, 2012

On handling your pets and a CSRF protection that wasn't

Security is hard. While security-related issues are fun and challenging subject for many - fun enough for them to take part in various CTFs, crackmes etc, it's usually not the first thing a developer cares for. Yes, they do have other priorities. That's why usually leaving a developer with a task of writing security-related code results in either:

Look what I found, ma!

Using libraries though is like bringing a pet home. Sure, it's cute and all, but you should be responsible for it from now on. That means you should:
  1. Care for it day after day (= keep libraries up to date).
  2. Observe it. If it's sick, go to a vet (= monitor for security vulnerabilities discovered).
  3. Accept that it's a part of the family now (= treat it with as much scrutiny as your own code).
Whatever you're bringing into your codebase, wherever it came from - it's your responsibility now. No matter if it's a snippet found in a forum, a github.com hosted library used by a few dozen people or a project used for many years and extensively tested. It may have a security vulnerability, it may be used insecurely, it may not be a right tool for a task. Be skeptical.

The code allmighty

There are no sacred cows. Any code is just that - a piece of instructions made by humans, with a certain possibility of having errors, some of that security related. And every human makes mistakes, some of them catastrophic.

Let me give you an example - OWASP PHP CSRF Guard - a small library for enriching your PHP application with CSRF protection. Similar to what OWASP CSRFGuard does for Java applications. This small library is presented in Open Web Application Security Project wiki, so it must be secure.....Right?

No. No sacred cows, remember? Take a look:
if (!isset($_POST['CSRFName']))
{
 trigger_error("No CSRFName found, probable invalid request.",E_USER_ERROR);  
} 
$name =$_POST['CSRFName'];
$token=$_POST['CSRFToken'];
if (!csrfguard_validate_token($name, $token))
{ 
 trigger_error("Invalid CSRF token.",E_USER_ERROR);
}
Application uses separate tokens for every form. Upon form submission, it gets form id (CSRFName) and appropriate token (CSRFToken) from POST request and calls csrf_validate_token(). So far so good. Let's dig deeper though.
function csrfguard_validate_token($unique_form_name,$token_value)
{
 $token=get_from_session($unique_form_name);
 if ($token===false)
 {
  return true;
 }
 elseif ($token==$token_value)
 {
  $result=true;
 }
 else
 { 
  $result=false;
 } 
 unset_session($unique_form_name);
 return $result;
}
Function retrieves the token for a form id from session (get_from_session). Function returning false is some exception, let's skip that. Then token value from POST is compared to its session equivalent. Looks ok. But..
function get_from_session($key)
{
 if (isset($_SESSION))
 {
  return $_SESSION[$key];
 }
 else {  return false; } //no session data, no CSRF risk
}
What happens if there is no such $key in $_SESSION? null will be returned. So, retrieving a token for non-existent form id will return null.

Guess what? In PHPnull == "". So, submitting this:
CSRFName=CSRFGuard_idontexist&CSRFToken=&input1=value1&input2=value2....
in your POST request will call:
csrfguard_validate_token('CSRFGuard_idontexist', '') // and then
$token = get_from_session('CSRFGuard_idontexist') = null; // => 
$token_value = ''; // => 
$token_value == $token; // =>
return true;
Total CSRF protection bypass. No sacred cows. Of course, the code in OWASP wiki has been fixed now.

Developers, remember: Do not blindly include libraries, even OWASP ones, without testing them, especially for security errors. If you can't do it - there are other people who can ( ^-^ ). After all, even if it's adopted, it's part of the family now.

Thursday, July 19, 2012

CodeIgniter <= 2.1.1 xss_clean() Cross Site Scripting filter bypass

This is a security advisory for popular PHP framework - CodeIgniter. I've found several bypasses in xss sanitization functions in the framework. These were responsibly disclosed to the vendor and are now fixed in version 2.1.2. (CVE-2012-1915).

Wednesday, August 24, 2011

Death to the filters - how to validate JSON correctly

JSON is a pretty popular data-interchange format. It's supported across programming languages, fits naturally into Javascript environment, it's human-readable and it's much lighter than XML. No wonder the format is widely adopted in many web applications.
Disclaimer: This post will probably be nothing new to security experts. It's meant for developers, as I've seen vulnerable applications using the bad practices described here. 

Is JSON secure?

Well, it has some quirks (it had even more of them in the past) but mostly it's good enough. Provided two things:
  • you don't use eval!
  • you are sure that's JSON (not everything that's a valid Javascript is JSON, it's only a subset)
Eval() is, as usual, asking yourself for trouble. Unfortunately, it's common - it's even mentioned in JSON's own documentation as a recommended practice:
To convert a JSON text into an object, you can use the eval() function. eval() invokes the JavaScript compiler. Since JSON is a proper subset of JavaScript, the compiler will correctly parse the text and produce an object structure. The text must be wrapped in parens to avoid tripping on an ambiguity in JavaScript's syntax.

var myObject = eval('(' + myJSONtext + ')');
The eval function is very fast. However, it can compile and execute any JavaScript program, so there can be security issues. The use of eval is indicated when the source is trusted and competent. It is much safer to use a JSON parser. In web applications over XMLHttpRequest, communication is permitted only to the same origin* that provide that page, so it is trusted. But it might not be competent. If the server is not rigorous in its JSON encoding, or if it does not scrupulously validate all of its inputs, then it could deliver invalid JSON text that could be carrying dangerous script. The eval function would execute the script, unleashing its malice.
* docs are outdated, you can do cross-origin request now (CORS)

Example - no filters

So, when using eval() it's crucial that the input is in fact valid JSON that has not been tampered with. Consider the following situation:

Our application uses JSON to store various user preferences (e.g. background color settings). Server does not care about the JSON, it's only relevant to client-side scripts. The data flow looks like this:
  1. User fills out the 'change preferences' form
  2. Javascript code creates JSON out of this and POSTs it to the server
  3. Server stores JSON in the database and retrieves it in future requests. The resulting code looks like this:  preferences_json = '{"background":"red"}';
  4. Javascript needs to get preferences, so it uses:
var prefs = eval("(" + preferences_json + ")");
document.body.style.backgroundColor = prefs.background;
...
XSSing this is simple - escape from single quotes:
//  payload:    '; alert(1); //
// this will execute in the preferences_json line like this:
preferences_json = ''; alert(1); //';
So the server needs to either deny ' in input or escape the output (' becomes \'). Are we secure now? No!

Filtering

Even with proper escaping, we do not have to escape the string at all for XSS. We can do the code execution in eval() call:
// payload:   alert(1),{"background":"red"}
// This will become:
eval("("+'alert(1),{"background":"red"}'+")");
// and alert will execute. 
One very bad practice I saw is disallowing certain characters in JSON input when storing it. For example, you could disallow "(" and ")" so that function can't be executed. But there are ways to execute functions without "()" - see for example this wonderful vector for IE. In fact, it's even possible to execute JS in this context if you're only allowing a-z A-Z 0-9 { } - : , . " (most of these characters are required for JSON to work anyway). Another example - allow \ and attacker can use obfuscation (e.g. \u0020 ) and embed pretty much any character.

If you validate JSON by looking at characters only and disallowing / allowing them - you're toasted. You've just opened a bag of tricks for attackers to use (and new techniques requiring fewer characters are still in development). Don't!

Try for yourself!

Think you can execute arbitrary code in this victim page? Yes, you can!

Source code for examples is, as always, on GitHub. And remember:
  • If you accept JSON input, make sure it's a legit JSON input (JSON is a subset of Javascript!). In PHP you can use json_decode()to validate server-side.
  • Validating JSON using character whitelist is a dead end. Don't do it. There are really tricky vectors around.
  • Don't use eval! There's JSON.parse() built into newer browsers, for older - use this.

Saturday, June 18, 2011

File path injection in PHP ≤ 5.3.6 file upload (CVE 2011-2202)

Since the thing went public before new PHP version has been released, I present full details of the latest PHP vulnerability I reported - together with some sweet demo exploit. The issue was found with fuzzing being part of my recent file upload research. And I still have some more to show in the future :)


My thanks go to Paweł Goleń who helped analyze the vulnerability.

The PHP Part

The whole issue is tracked as PHP bug #54939, but the website is now down. The exemplary exploit is at pastebin. The nature of the bug is simple. PHP claims to remove the path component from HTTP file upload forms (transferred as MIME multipart/form-data requests), leaving only the file name given by the user agent. This is both for security, and to fix MSIE incompatibility (IE used to send full path like this: c:\WINDOWS\WHATEVER\My_file.txt).

However, in 2008 PHP developers made a off-by-one error, and, as a result, if a name starts with \ or / and has no other (back)slashes, it's left as-is. So, this allows for:
  • /vmlinuz
  • /autorun.inf (/ will map to C:\ in WINDOWS - the drive where your PHP is run from)
  • /boot.ini
and other interesting file "names" to pass through.

Thursday, December 16, 2010

Squid-imposter: Phishing websites forever with HTML5 offline cache

Recently I've been doing some HTML5 hacking and I encountered Imposter by Lavakumar Kuppan. It's a framework to perform browser phishing attacks - a tool that integrates a DNS server, a web server and a configuration utility running on Windows machine. Once a victim connects to Imposter (e.g. through a rogue WiFi access point) it tries to e.g. steal his cookies, inject payloads into chosen websites etc. There is also a module that uses HTML5 offline cache to store the payload permanently in all supporting browsers. It's a pretty clever framework, but it requires Windows.

I've decided to take away the HTML5 offline cache storage functionality and port it to Linux. The result is presented here as Squid-imposter. Now you can easily spoof websites that will be stored in victim's browser cache forever.

Thursday, July 29, 2010

Hardening PHP: How to securely include remote code (part 2)

In second post of the series I describe methods of checking the integrity of remote code - from checksums to (simple) Public Key Infrastructure. To transfer the code I introduce the popular Phar archives.

Hardening PHP: How to securely include remote code (part 1)

First post of the series discussing various methods of including remote PHP code in your application - from security standpoint. In this post we discuss the history of remote code execution vulnerabilities in PHP apps and ways to prevent them. We finish off by presenting an unsecure method of including a remote code and describe what is the problem with that method.

Thursday, May 13, 2010

Deobfuscating PHP scripts with evalhook

Just a quick note - Similar to my previous approach in JavaScript Stefan Esser from Month of PHP Security successfully tried to deobfuscate a PHP script today.

He developed a PHP extension called evalhook that, well, hooks into eval()calls in PHP, displays a code to be executed and asks for a confirmation to run it. That way all user space PHP obfuscators (usually called encoders) are pointless - so please don't use them to protect your script from being seen.

Funny thing is that Stefan took the same way as me to deobfuscate a code written in a dynamic language - just hook into eval() and you're done. It's THAT simple.

Go ahead and read more on decoding a user space PHP script.

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:

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 17, 2009

HTTP File server released

Problem

Imagine a situation where your application has to store and retrieve files on the web (i.e. not on a local filesystem). You have many options - you may upload them to FTP server, e-mail them, use some file hosting services like Dropbox, upload files using a HTML form, use WebDAV server. Finally you may mount some remote filesystem like NFS.
All of these options are valid, but they all carry certain amount of requirements that may not always be met:
  • To use FTP, you need to set up a remote FTP server, have an implemented FTP client in your language of choice and the ability to open FTP connections on the system you're using.
  • To use e-mail you need to be able to handle POP3 and SMTP protocols and have a mail server set up.
  • WebDAV, although convenient, is hard to set up in the first place. The protocol itself takes some time to implement.
  • Using any other web application like Dropbox requires you to have a client for their services and you need to accept the licence restrictions.
  • HTML form - an excelent choice. If you're doing the uploads manually, you may write a simple script in minutes - but what if you want to upload files automatically (e.g. in a batch script)? You need to make a HTTP request with form and the file within encoded, you have to deal with mime-types, encoding file contents etc. Not really fast to implement.
  • Mounting remote filesystem is impossible on a shared Linux server, or Windows server.

Solution

HTTP File server to the rescue. This small little fellow, written in PHP5 is a simple REST-oriented file server with minimal requirements:
  • PHP 5 (5.2 I suppose)
  • web server (Apache will do)
  • writable directory (this is where your files will be stored)
This is for the server part. For the client part you only need to be able to do HTTP GET and HTTP POST requests, so you're good with just wget in a batch script (or .NET application, or Ruby, PHP, Java - pretty much anything nowadays can form HTTP requests).

Example

Example usage:
# store file on server - use HTTP POST
wget --post-file=file_to_send.txt http://server/index.php/path/to/store/file.txt -O -

# retrieve file - use HTTP GET
wget http://server/index.php/path/to/store/file.txt
That's pretty much it. The server is so simple, it doesn't (yet?) offer even the ability to list directory contents. All it does is store files and retrieve them.

Download

Download HTTPFileServer and take a look for yourself. Your comments are welcome. The project is BSD licensed.