07-01-2009 18:16
Type hinting for PHP 5.3 For a few years now at work we've been using a patched version of PHP, one those patching featuring type hinting. Over time this proved to be a very handy feature that allows for a much more readable code and introduces a language based validation layer to ensure that the right data types are getting to your functions and methods. It also caught numerous bugs due to functions returning or passing un-expected values. Best of all this feature does not require any changes on the part of opcode caches (essential component for PHP performance) and allows for simple deployment.
I and other people have tried a number of times in the past to introduce type hinting into stock PHP, but unfortunately have never been successful. On a general level most people agree it would be a good idea to have, since it is an optional feature and does not introduce any regressions, heck you can even mix type hinted code with the non-type hinted one. The "PROBLEM" has always been combining of PHP's typeless nature with type hinting, which is where the consensus has been difficult (impossible) to reach. To illustrate the problem let's consider the following:
function foo(int $bar) {}
Some people would expect that passing "1" (string containing number 1) would be accepted by function foo() and not raise any type errors, since in PHP typically, numbers within strings are considered to be perfectly valid numbers ("1" + "1" == 2). Hence the conflict, some people (I am a part of that group) think that type hinting should be strict, while others think it should be more permissive to be inline with PHP's fluid nature.
With introduction of PHP 5.3 and free day in the middle of the week (Happy Canada Day to all Canucks) I've decided to port my internal patch to 5.3 and introduce a new 'feature' to it to hopefully bridge the divide. I've added a IS_NUMERIC (numeric) type hint that allows the script author to designate a parameter as having to be number, meaning input of type boolean, long or float as well as strings containing purely numbers will be accepted. This means if were to rewrite my previous function as:
function foo(numeric $bar) {}
Then calling foo("112"); would perfectly valid. To further extend basic type hinting support I've also added IS_SCALAR (scalar) type hint that allows a parameter to be designated as scalar, which means it'll accept any boolean, float, string or integer value.
The patch is available here: http://ia.gd/patch/type_hint_53.txt
I've also posted it to the internals list in the hope of gathering enough support on the part of PHP developers and users to have it added to 5.3 and future releases of PHP.
It should be noted that this is not the first idea for type hints, that credit goes to Hannes Magnusson who had posted a similar patch on the internals list back in 2006. Also, back in 2008 Felipe Pena wrote a complete RFC on type hinting with patches and even test.
Blogs ::
Ilia Alshanetsky
Povezani zapisi:
08-31-2010 11:20
PHP injection attacks have become increasingly popular lately. If you look at your web server logs Im pretty sure that you will find dozens of requests for PHP injection, usually by bots that are simply trying some well known (and less known) vulnerabilities.
One of our readers, Blake, managed to capture some interesting attempts to exploit various PHP injection vulnerabilities on his web site, thanks to installation of mod_security. Contrary to popular PHP injection attempts, where the attacker tries to exploit a variable to get the PHP interpreter to retrieve a remote PHP script, Blake noticed that the attacker tried to exploit a vulnerability in a PHP script through POST request. The attacker submitted a malicious PHP script (with other data) hoping that the PHP interpreter will execute it this vulnerability also exist, although not that common. Here is what the attack looked like in log files:
POST http://www.hostname.somewhere en-US) AppleWebKit/133.7 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4
Host: www.hostname.somewhere boundary=---------------------------phpsploit
Content-Length: 46266
The POST request contained, besides data needed by the main script, an (of course) obfuscated PHP script that the attacker tried to execute. The deobfuscation part is shown in the picture below where I beautified it a bit and cut the long eval string.
Now, the interesting part is that the script uses the User-Agent field as the deobfuscation key. If you carefully check the User-Agent shown in above you will see that, while it looks legitimate, it in fact isnt the combination of versions is not legitimate.
But thats not all the injected PHP script contains multiple eval() calls of which every one uses a different deobfuscation key. This allows the attacker to test only parts of the script and never reveal its true side unless the attack works the part that I was able to deobfuscate is shown below and it just tries to connect to a well known (public and legitimate) IRC server. Very clever, especially if we know that PHP will nicely eat any garbage that it cant parse so the attacker doesnt have to worry about only one eval() call working.
This attack demonstrated how important it is to use all available protection layers not only Blakes scripts where not vulnerable, but he also ran mod_security which successfully blocked this attack and he was checking his logs, something that a lot of administrators underestimate.
What do your logs look like? If you find similar attacks or something else that looks interesting, let us know through our contact form available here.
--
Bojan
INFIGO IS
(c) SANS Internet Storm Center. http://isc.sans.org Creative Commons Attribution-Noncommercial 3.0 United States License.
ISC
08-30-2010 20:52
About a week ago, I was doing some upgrades on my development machine and came across a rather nasty issue when it comes to how .php(s) files are associated with PHP in Apache. It seems that a number of distros including Gentoo (which is what I was using) are using the following configuration directive to make the PHP module parse PHP files:
<IfModule mod_mime.c>
AddHandler application/x-httpd-php .php
AddHandler application/x-httpd-php-source .phps
</IfModule>
The non-obvious problem with the above is that it will allow not only "file.php" to be treated as PHP scripts, but also "file.php.txt", which means that any file containing ".php" in its name, no matter where in the filename, would be treated as a PHP script. This of course creates a rather nasty security hole, since many upload file validation tools, only check the final extension. Consequently allowing the user to by-pass the validation, by simply prefixing another "harmless" extension like .txt, .pdf, etc... to the filename, but still get the code to execute.
To mitigate this problem you should instead use the following configuration, that would only pick-up of files ending with a .php extension.
<IfModule mod_mime.c>
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps
</IfModule>
Ilia Alshanetsky
08-27-2010 13:56
PHP Excel Extension 0.8.5
The 0.8.6 version of the Excel extension was released and is now available for download. This version was updated to contain LibXL 3.0 support which introduces Excel 2007/2010 read/write support, which means that this extension can now read and generate any Excel file. Support for XSLX (2007/2010) format can be enabled by passing "true" as the 3rd parameter to the ExcelBook() construtor.
GitHub: http://github.com/iliaal/php_excel/
Source: http://github.com/downloads/iliaal/php_excel/php-excel-0.86.tar.bz2
Ilia Alshanetsky
08-16-2010 22:03
It's been a crazy few weeks! I moved across the country, attended some of Security B-sides and all of Defcon; more on the conferences to come later. Because it's been a while between posts, and this is the final post in this series on ESAPI PHP, let's start out by reviewing what we've done so far.
Part 1: We talked a little bit about the ESAPI PHP project, set up a plan for this series and wrote an extremely insecure basic blogging PHP web application.
Part 2: We set up the PHP ESAPI project and started validating user input.
Part 3: We started properly handling our output and used prepared statements to make our database queries safer.
And this week (our final entry in the series), we're going to go over securing our user authentication and sessions. There are some other great controls in ESAPI PHP, but the project is not as mature as the Java version and some of the controls are not quite ready for prime time. So I'm mostly going to stick with what can be implemented now with minimal changes. You can find the files from this week here.
Handling users and sessions Let's start out listing what we'll be doing to harden our User class. This is not a comprehensive list on hardening a User control, but it includes items that will mostly touch on ESAPI controls in some way.
Improve our session handlingImplement CSRF tokensHash user passwordsRetrieve object properties from the databaseExpire user sessions after a set timeThink about what we're storing in our session variables That sounds like a lot, and it is. From the last post to this one, the User class has more than doubled in size. Luckily, none of it is too difficult. ESAPI PHP does have some objects and utilities for handling users. For the most part though, they're not complete. One thing it does have is very good utilities for handling CSRF tokens via the HTTPUtilties control. We're going to use that while we work on our User class.
We'll start out like we start out every other Class, by including the ESAPI controls we'll require. We require BlogHTTPUtilities because we're going to modify the DefaultHTTPUtilities just a little bit.
require_once("owasp/src/ESAPI.php"); require_once("owasp/src/reference/BlogHTTPUtilities.php");
We're also going to add a few new properties to our object:
private $esapi = null; private $httputils = null; private $logged_in = null; private $salt = null; private $expire_time = 600;
$esapi is our ESAPI object - we've seen this plenty of places before. We store the HTTPUtilities control in $httputils. Since we only have one level of user, we'll just use a boolean called $logged_in to store that information. $salt is used to improve our password hashing. And finally, $expire_time is the number of seconds of inactivity before a user's session expires.
One thing, we'll be storing the salt in the database, so let's alter our user table right now. Also, we'll be hashing the password so we need to make the password field a bit bigger.
ALTER TABLE user ADD salt VARCHAR(30); alter table user change password password varchar(100) not null;
We'll just go function by function and go over what we're changing in the User class. First up is our constructor:
function __construct($user_id='') { $this->esapi = new ESAPI("/var/www/insecure_week1/lib/owasp/test/testresources/ESAPI.xml"); ESAPI::setHTTPUtilities(new BlogHTTPUtilities()); $this->httputils = ESAPI::getHTTPUtilities(); $this->logged_in = false; if($this->check_user_session()) { if($this->retrieve_user()) { $this->logged_in = true; } } }
Not much we haven't seen before. We create our ESAPI controls and default them to not logged in. Then, we check to see if a user session exists and contains valid information. If so, we change logged_in to true. The retrieve_user function is new, and we'll go over that soon.
Next, we create a few simple get/set methods for our new properties:
function set_password($password) { $this->password = $password; }
function get_password() { return $this->password; }
function get_token() { return $this->httputils->getCSRFToken(); }
function get_logged_in() { return $this->logged_in; } function set_logged_in($logged_in) { $this->logged_in = $logged_in; }
The only thing that may not be obvious here is the get_token function. The HTTPUtilities controls handles most everything we need for CSRF tokens, so our getter function just calls HTTPUtilities'.
Now we get to the interesting stuff, starting with our login function:
function login($username, $password) { $db = DB::get_instance();
$sql = $db->prepare("SELECT id, username, password, salt FROM user WHERE username = ?"); $sql->bind_param('s', $username); if(!$sql->execute()) { $this->error_list[] = "Could not log in."; $sql->close(); return false; } $sql->store_result(); if($sql->num_rows() != 1) { $this->error_list[] = "Could not log in."; $sql->free_result(); return false; } $sql->bind_result($user_id, $username, $stored_pass, $salt); $sql->fetch(); $sql->free_result(); $sql->close();
if($this->hash_pass($password, $salt) != $stored_pass) { echo("<br>password = $password<br>salt = $salt<br>stored_pass = $stored_pass<br>hashed_pass = " . $this->hash_pass($password,$salt) . "<br>"); $this->error_list[] = "Invalid password."; return false; } $this->user_id = $user_id; $this->username = $username; $this->password = $stored_pass; $this->salt = $salt; if(!$this->create_user_session()) { return false; } return true; }
The first thing you might notice is that if we're given a valid username, we're selecting all of the user's information from the database. This is because of the addition of the salt field. We need to hash the password the user submitted to us to compare the two. We might as well grab all the information in one fell swoop rather than making two database calls.
The next thing we'll noticed we changed a bit is the num_rows check. We know that this query should never return more than one row, so we should check for that specifically.
Next, we hash the password passed to the function and compare it to the stored password. If it matches, we'll create the user object properties and start the session.
We start the session with the create_user_session function, which we've modified as follows:
private function create_user_session() { session_start(); if(!session_regenerate_id(true)) { $this->error_list[] = "Could not create user session. Please try again"; return false; } $this->httputils->setCSRFToken(); $_SESSION['user_id'] = $this->user_id; $_SESSION['expire_time'] = time() + $this->expire_time; return true; }
Before, all we did was set user_id and username session variables, now we're doing a bit more. First off, when a user logs in, we regenerate any session ids. Next, we use the httputils control set a CSRF token. This will get passed along with any requests the user makes to prevent CSRF attacks. We're not storing the username anymore because we're going to be grabbing it from the database from now on. And finally, we set a session expire_time.
Our check_user_session has also become a little more complicated. Here it is.
function check_user_session() { session_start(); $token = $_GET['token']; if(!$token) { $token = $_POST['token']; } if(!$token) { return false; } if(!$this->httputils->verifyCSRFToken($token)) { $this->error_list[] = "Could not verify session."; return false; } if(!$_SESSION['expire_time'] || time() > $_SESSION['expire_time']) { $this->expire_session(); $this->error_list[] = "Session_expired"; return false; }
if(!$_SESSION['user_id']) { $this->error_list[] = "Session not found."; return false; } else { $this->user_id = $_SESSION['user_id']; } $this->update_expire_time(); return true; }
Basically, what we're doing here is adding in several more checks for the user session. We start off by checking the CSRF token. Once we grab the CSRF token from the request, we can use the verifyCSRFToken in our HTTP utilities control to check it against the stored token. This is actually the function we're going to modify in DefaultHTTPUtilities to create our BlogHTTPUtilities class - the DefaultHTTPUtilities verifyCSRFToken method depends on some functionality that's not 100% implemented yet.
Next, we're just checking the expire time and clearing the session if it's been too long. Then we check the user id. If all this checks out, we update the session expiration time and return true - meaning the user is logged in with a valid session.
Next we have a couple of simple functions to handle our expire times. These are pretty self explanatory:
function expire_session() { session_destroy(); }
function update_expire_time() { $_SESSION['expire_time'] = time() + $this->expire_time; }
Now, we'll go over our retrieve_user function we called in the constructor. Again, really simple; we've seen functions almost exactly like this in our other classes.
private function retrieve_user() { if(!$this->user_id) { $this->error_list[] = "No user to retrieve!"; return false; } $db = DB::get_instance(); $sql = $db->prepare("SELECT username, password FROM user WHERE id = ?"); $sql->bind_param('i', $this->user_id); if(!$sql->execute()) { $this->error_list[] = "Could not retrieve user."; $sql->close(); return false; } $sql->store_result(); if($sql->num_rows() != 1) { $this->error_list[] = "Could not retrieve user"; $sql->free_result(); $sql->close(); return false; } $sql->bind_result($this->username, $this->password); $sql->fetch(); $sql->free_result(); $sql->close(); return true; }
Here is a simple write function I'm using to create a new user. There is no UI for it, but this will make it easier to create a user with the hashed password if you need it.
function write() { $db = DB::get_instance();
if(!$this->salt) { $this->gen_salt(); } $hashed_pass = $this->hash_pass($this->password, $this->salt); $sql = $db->prepare("INSERT INTO user (username, password, salt) VALUES (?, ?, ?)"); $sql->bind_param('sss', $this->username, $hashed_pass, $this->salt); if(!$sql->execute()) { $this->error_list[] = "Could not write user, please try again."; $sql->close(); return false; } $sql->close(); return true; }
And finally, the salt and hash functions that will hash our passwords and generate a salt:
private function gen_salt() { $this->salt = rand(1,100000) . time() . $this->username . rand(1,100000); }
private function hash_pass($password, $salt) { return md5($password.$salt); }
Let's talk briefly about the HTTPUtilities control. As I mentioned before, we need to make a fairly simple change to it in order to use the verifyCSRFToken function. We'll do that by copying the DefaultHTTPUtilities.php file to BlogHTTPUtilities and make our changes.
public function verifyCSRFToken($token) { if(!$this->getCSRFToken() == $token) { throw new IntrusionException('Authentication failed.', 'Possibly forged HTTP request without proper CSRF token detected.'); return false; } return true; }
And that's all we need.
There are a few other simple changes we'll need in our controllers, just to handle our tokens and sessions. I've included these changes in the file archive above, but I'm just going to summarize them here, because they're very simple. Any time a user is logged in, we need to append the CSRF token to any links. We also begin using the logged_in user property instead of checking session variables directly. Finally, we stop depending on hidden form fields.
The last thing to think about is our post.php page. In the original version, we assumed that if a person made it to this page, that they must be logged in. Of course, that means that anyone who goes to post.php can add any content they want. So obviously, we need to check the user log in on post.php before we allow someone to post.
Where we're at, and where to go from here So there you have it, a somewhat long-winded tutorial on using OWASP ESAPI PHP. We went over most of the features that are currently implemented in ESAPI PHP. On the whole, I would say that ESAPI PHP is well on its way to becoming a power house, but it's not quite ready to be implemented in most production environments as is. With a little work and customization, it definitely could be.
There are some great people working on the project, and I would encourage anyone with an interest in PHP security to get involved. You can find information on getting involved here.
As always, feedback, questions, comments are always appreciated. You can reach me at jackwillksecurity at gmail dot com or on Twitter (@jackwillk) where I will mostly be tweeting about total nonsense.
Feed!
| |
|
|
|