Monday, 23 August 2010
Writing secure codes in PHP[basic]
PHP is the most used web developing language and with the increased use of it, there have been increase in poorly programmed sites which have resulted in number of admin hacks and sometimes even worse, server rooting. So I am going to give you some ideas for writing secure codes for general coding flaws in PHP. Most of the time, the programmers forget to sanitize the user input in their PHP code & hence, the code becomes vulnerable to some of the common exploits like file inclusion vulnerabilities, SQL injection, XSS & others... The programmer should never trust anything that comes from the clients and so (s)he should try to create whitelist for what is allowed to. So I am here to give you ideas on preventing these simple vulnerabilities from your PHP code...
General steps:
FILE INCLUSION : File inclusion vulns like RFI(remote) & LFI(local) are exploited by including another file(other than intended by programmer) & this is damn devastating as we can completely rm the box if we escalate privilege with PoC exploits... Anyway let me show the vulnerable code for it...
I've seen many programmers writing the same code & it leads to unexpected result... So any malicious user can include some evil files to r00t the box & you are own3d...
Also many programmers think that they can patch this vuln with the following snippet(based on real example from one of the Nepali ISPs site)
This seems to work fine as it intends to include only php files... aha but still it has got a hole... NULLBYTES - - ? Oh hacked but I secured it... Did you??? No, you didn't... Nullbytes in PHP will terminate the string at where they come and ignore anything that comes after it.
So let me talk about securing it... There are number of ways to secure it and all are perfect. But, at least for me, the switch is the perfect and simplest method to secure this whole code...
The above written code is simple yet secured. You may also create the array of valid files
Another method using regular expression is:
this also should work fine though as already stated I don't use this one... Its a regular expression method...
SQL INJECTION(SQLi): SQL injections are one of the most prevalent web vulns in the websites and they can be very harmful especially for the commercial sites. But still many sites still remain vulnerable to the SQL injection. & again the problem is again the lack of sanitization of GET/POST or COOKIE variables or any other inputs from users... To avoid SQL injection, you need to be as hard as you can. Don't allow any other data types where you assume to be integer types. Don't allow something that is not what you wanted to be accepted by your code. Be as strict as you can for the datatypes.
Now let me show you the simplest form of the vulnerability.
You can see that this takes uid from GET i.e. from user and works accordingly. Seems fine and most of the site visitors won't know about it. But what if someone elite visits the site. He/She will test the GET variable and change the uid value.
The query runs and runs without any filtering mechanism. And if the malicious runs the SQL query, he can do anything to the database. So what's the solution for this? Simply, type checking. You won't expect uid to be anything other than integer type. So why not tell PHP that the uid must be integer...
So this should be secure as the $_GET['uid'] is type casted as integer. Other ways are using the functions is_numeric() which tests if the given variable is integer or not and intval() that would return integer value of variable.
Note that the ways for securing other datatypes is different. I would list you some of the functions so that you can use them to secure your site from SQLi.
Functions to secure SQLi:
mysql_real_escape_string()
addslashes()
The above example was just for SELECT query but you need to watch the other queries like INSERT, UPDATE and DELETE because you can't just trust the user inputs. Moreover, it is always better to strip the inputs to the limited number of characters so that you won't mess up with SQL column truncation vulnerability(google if you want to know about it). Also, always use quotes and brackets in the SQL query strings if your database allows(MySQL does allow).
Cross site scripting(XSS): Its the most prevalent web app vulnerabilities which have been detected even in high profile sites like facebook, microsoft, twitter, etc. It also occurs when you don't sanitize the user inputs. Consider the guestbook which does something like below:
Now, in the name or comment field if I put something like the site is going to display it and as since the HTML tags are not filtered, samar will be alerted in the page. Its just an example. Hackers can redirect users from your site using this exploit by inserting Now let me come to securing it.
Here I have used the function htmlspecialchars() which converts all html special characters into their equivalent entities. For example, < to < and > to >
Since these conversions are made to tags, they do not work as HTML tags and hence prevent XSS. More functions to use while preventing XSS are htmlentities(), strip_tags(), url_encode(), etc. To make 100% XSS proof site, validate everything like $_SERVER variables too. They too can be compromised to XSS the site.
Some critical functions: Here are some of the functions you should be careful with.
passthru(), exec(), system(), shell_exec(), file_get_contents(), fopen(), fwrite(), glob(), file(), readfile(), popen(), mysql_query(),
Other Extra tips: Security of your server can be enhanced by doing some hardening through PHP.INI file too and coding in better styles.
1) Turn off the register_globals
2) Set error_reporting to 0
3) Use @ sign before the functions that are likely to fail usually. eg: @include($page);
4) Turn off allow_url_fopen in PHP.INI
5) Turn on magic_quotes_gpc in PHP.INI
6) Always encrypt the sensitive information. For eg. use md5() once or twice to hash your password.
NOTE: Written around a year ago.
General steps:
Validate every input.
Secure your file system.
Secure your file upload system.
Secure your file download system.
Secure your database connection codes.
Secure/encrypt the data.
Secure your file system.
Secure your file upload system.
Secure your file download system.
Secure your database connection codes.
Secure/encrypt the data.
<?php
$page = $_GET['page'];
if (isset($page)) #checks if the $page variable page is set or not
{
include($page); #includes the page without checking if it is legitimate or not...
}
?>
$page = $_GET['page'];
if (isset($page)) #checks if the $page variable page is set or not
{
include($page); #includes the page without checking if it is legitimate or not...
}
?>
I've seen many programmers writing the same code & it leads to unexpected result... So any malicious user can include some evil files to r00t the box & you are own3d...
Also many programmers think that they can patch this vuln with the following snippet(based on real example from one of the Nepali ISPs site)
<?php
$venpage = $_GET['page'];
$venpage = $venpage . ".php";
if (IsSet($venpage))
{
include($venpage);
}
?>
$venpage = $_GET['page'];
$venpage = $venpage . ".php";
if (IsSet($venpage))
{
include($venpage);
}
?>
So let me talk about securing it... There are number of ways to secure it and all are perfect. But, at least for me, the switch is the perfect and simplest method to secure this whole code...
<?php
$page = $_GET['page'];
if(isset($page)) #check if there's page variable set or not
{
switch($page)
{
case "info":
include("info.php");
break;
case "about":
include("about.php");
break;
default:
include("index.php");
break;
}
}
?>
$page = $_GET['page'];
if(isset($page)) #check if there's page variable set or not
{
switch($page)
{
case "info":
include("info.php");
break;
case "about":
include("about.php");
break;
default:
include("index.php");
break;
}
}
?>
Another method using regular expression is:
<?php
//ERROR_REPORTING(E_ALL);
if (IsSet($_GET['page']))
{
$page=$_GET['page'];
$page=preg_replace('/[^a-z]+/i','',$page); //regular expression working here
include $page.".php";
}
else
{
echo "No page set";
}
?>
//ERROR_REPORTING(E_ALL);
if (IsSet($_GET['page']))
{
$page=$_GET['page'];
$page=preg_replace('/[^a-z]+/i','',$page); //regular expression working here
include $page.".php";
}
else
{
echo "No page set";
}
?>
this also should work fine though as already stated I don't use this one... Its a regular expression method...
SQL INJECTION(SQLi): SQL injections are one of the most prevalent web vulns in the websites and they can be very harmful especially for the commercial sites. But still many sites still remain vulnerable to the SQL injection. & again the problem is again the lack of sanitization of GET/POST or COOKIE variables or any other inputs from users... To avoid SQL injection, you need to be as hard as you can. Don't allow any other data types where you assume to be integer types. Don't allow something that is not what you wanted to be accepted by your code. Be as strict as you can for the datatypes.
Now let me show you the simplest form of the vulnerability.
<?php
//configurations for mysql connection
$host = "localhost";
$user = "root";
$pass = "w000000t";
$db = "db_shop";
//connecting to mysql
mysql_connect($host, $user, $pass);
mysql_select_db($db);
$uid = $_GET['uid'];
if (isset($uid))
{
$query = mysql_query("SELECT * FROM `profile` WHERE `uid` = $uid");
if ($query)
{
while($profile = mysql_fetch_array($query))
{
//display or do something here
}
}
}
?>
//configurations for mysql connection
$host = "localhost";
$user = "root";
$pass = "w000000t";
$db = "db_shop";
//connecting to mysql
mysql_connect($host, $user, $pass);
mysql_select_db($db);
$uid = $_GET['uid'];
if (isset($uid))
{
$query = mysql_query("SELECT * FROM `profile` WHERE `uid` = $uid");
if ($query)
{
while($profile = mysql_fetch_array($query))
{
//display or do something here
}
}
}
?>
You can see that this takes uid from GET i.e. from user and works accordingly. Seems fine and most of the site visitors won't know about it. But what if someone elite visits the site. He/She will test the GET variable and change the uid value.
The query runs and runs without any filtering mechanism. And if the malicious runs the SQL query, he can do anything to the database. So what's the solution for this? Simply, type checking. You won't expect uid to be anything other than integer type. So why not tell PHP that the uid must be integer...
<?php
//configurations for mysql connection
$host = "localhost";
$user = "root";
$pass = "w000000t";
$db = "db_shop";
//connecting to mysql
mysql_connect($host, $user, $pass);
mysql_select_db($db);
$uid = (Int) $_GET['uid']; //you say that uid must be integer...
if (isset($uid))
{
$query = mysql_query("SELECT * FROM `profile` WHERE `uid` = $uid");
if ($query)
{
while($profile = mysql_fetch_array($query))
{
//display or do something here
}
}
}
?>
//configurations for mysql connection
$host = "localhost";
$user = "root";
$pass = "w000000t";
$db = "db_shop";
//connecting to mysql
mysql_connect($host, $user, $pass);
mysql_select_db($db);
$uid = (Int) $_GET['uid']; //you say that uid must be integer...
if (isset($uid))
{
$query = mysql_query("SELECT * FROM `profile` WHERE `uid` = $uid");
if ($query)
{
while($profile = mysql_fetch_array($query))
{
//display or do something here
}
}
}
?>
So this should be secure as the $_GET['uid'] is type casted as integer. Other ways are using the functions is_numeric() which tests if the given variable is integer or not and intval() that would return integer value of variable.
Note that the ways for securing other datatypes is different. I would list you some of the functions so that you can use them to secure your site from SQLi.
Functions to secure SQLi:
mysql_real_escape_string()
addslashes()
The above example was just for SELECT query but you need to watch the other queries like INSERT, UPDATE and DELETE because you can't just trust the user inputs. Moreover, it is always better to strip the inputs to the limited number of characters so that you won't mess up with SQL column truncation vulnerability(google if you want to know about it). Also, always use quotes and brackets in the SQL query strings if your database allows(MySQL does allow).
Cross site scripting(XSS): Its the most prevalent web app vulnerabilities which have been detected even in high profile sites like facebook, microsoft, twitter, etc. It also occurs when you don't sanitize the user inputs. Consider the guestbook which does something like below:
<?php
if (isset($_POST['sbtGuestbook']))
{
$name = $_POST['name'];
$comment = $_POST['comment'];
//insert these things into the database
//now print these infos in the page
echo $name."<br />".$comment;
}
?>
if (isset($_POST['sbtGuestbook']))
{
$name = $_POST['name'];
$comment = $_POST['comment'];
//insert these things into the database
//now print these infos in the page
echo $name."<br />".$comment;
}
?>
<script>alert('samar');</script>
<script>location.replace("http://hackerssite.com.np");</script>
<?php
$name = htmlspecialchars($_POST['name']);
$comment = htmlspecialchars($_POST['comment']);
//insert these things into database
//now print them
echo $name."<br />".$comment;
?>
$name = htmlspecialchars($_POST['name']);
$comment = htmlspecialchars($_POST['comment']);
//insert these things into database
//now print them
echo $name."<br />".$comment;
?>
Since these conversions are made to tags, they do not work as HTML tags and hence prevent XSS. More functions to use while preventing XSS are htmlentities(), strip_tags(), url_encode(), etc. To make 100% XSS proof site, validate everything like $_SERVER variables too. They too can be compromised to XSS the site.
Some critical functions: Here are some of the functions you should be careful with.
passthru(), exec(), system(), shell_exec(), file_get_contents(), fopen(), fwrite(), glob(), file(), readfile(), popen(), mysql_query(),
Other Extra tips: Security of your server can be enhanced by doing some hardening through PHP.INI file too and coding in better styles.
1) Turn off the register_globals
2) Set error_reporting to 0
3) Use @ sign before the functions that are likely to fail usually. eg: @include($page);
4) Turn off allow_url_fopen in PHP.INI
5) Turn on magic_quotes_gpc in PHP.INI
6) Always encrypt the sensitive information. For eg. use md5() once or twice to hash your password.
NOTE: Written around a year ago.
Bookmark this post:blogger tutorials
Social Bookmarking Blogger Widget |
Writing secure codes in PHP[basic]
2010-08-23T18:51:00+05:45
Cool Samar
php|security|
Subscribe to:
Post Comments (Atom)