Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Intro to PHP Security


Published on

This presentation covers the basics of PHP Security to help address common mistakes and misconceptions.

Published in: Technology

Intro to PHP Security

  1. Introduction toPHP SECURITYmichael stowe APRIL 16, 2012
  2. MIKESTOWE .com• 10+ years experience hacking PHP• Developed web applications for large non-profits, the medical field, law enforcement, and ecommerce websites (things that need to be secure)• Open Source Contributor (3 WordPress plugins, multiple scripts)• Software Engineer at (half a million visitors every day)• Zend Certified PHP 5.3 Software Engineer@mikegstowe
  3. So how many people have built a website?
  4. So how many people have built a website? THAT’S COOL!
  6. People try to hack it over2,000 times a dayMost come fromthe United States…
  7. All it takes is one mistake
  8. Or trusting outside sources…$_SESSION
  9. FIRST RULE OF PHP SECURITY:Don‟t Trust Anyone…
  10. ANYONE! (not even girl scouts)
  11. <?php This means controlling your data. Start by setting your php.ini configuration to: register_globals = off; session.use_only_cookies = 1; session.cookie_httponly = 1; Can‟t access the php.ini file? No problem, just create a file called “php.ini” in the root folder of your script
  12. ALWAYS DISABLEREGISTER_GLOBALSRegister_globals is so dangerousit has been removed from PHP 5.4completely! Let‟s take a look at it in action… register_globals takes all $_REQUEST data and sets it as global variables within your script
  13. <?php Pretty simple script… <?php if($_POST[username] == admin && $_POST[password] = pass) { // Setup User $userId = 1; } if(isset($userId)) { // do stuff... } ?> But if you want to login, just add ?userId=1 to the end of the url!!! register_globals takes all $_REQUEST data and sets it as global variables within your script
  14. <?php Also avoid emulating register_globals! <?php $userId = false; extract($_REQUEST); if($_POST[username] == admin && $_POST[password] = pass) { // Setup User $userId = 1; } if($userId) { // do stuff... } ?> The extract() function will overwrite pre- existing variables by default! Once again, adding ?userId=1 to the URL logs the user in. avoid using extract() and import_request_variables() in your scripts
  15. Security also means knowingwhat sources can be trusted…
  16. Security also means knowingwhat sources can be trusted… $_GET $_SERVER $_POST $_REQUEST $_SESSION $_ENV $_COOKIE $_FILESWhich one(s) can we trust?
  17. Security also means knowingwhat sources can be trusted… $_GET $_SERVER $_POST $_REQUEST $_SESSION $_ENV $_COOKIE $_FILESWhich one(s) can we trust?
  18. REMEMBERDon‟t Trust Anyone
  19. USER CONTROLLED DATA: $_GET $_FILES $_POST $_ENV $_REQUEST $_SERVER $_COOKIE These all contain data that can be manipulated by the user and shouldn’t be trusted!
  20. UH OH…This means that the user can send youwhatever information they want! They can lieto you about who they are, where they‟refrom, what a file mime-type is, and eveninject malicious code into your script!Browser plug-ins are designed to help make ourjob as developers easier… but they also help andenable hackers!
  21. <?php For Example… <?php $ch = curl_init(); curl_setopt($ch, CURLOPT_URL,; $hack = "><script language="javascript">location.href=mysite; </script><input type="hidden; curl_setopt($ch, CURLOPT_POSTFIELDS,name=.urlencode($hack)); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_COOKIEJAR, "cookies.txt"); curl_setopt($ch, CURLOPT_COOKIEFILE, "cookies.txt"); curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv: Gecko/20070309 Firefox/"); curl_exec($ch); ?> I just submitted a malicious script through your form! The above example tells the server I am using Firefox, pulls and stores cookie data, and submits malicious code as a $_POST
  22. SESSION DATA:$_SESSION, while the most secure can bemanipulated through the use of UserControlled Data. For example, they can injectcode that writes to the $_SESSION array,as well as hijack someone else‟s session!Remember those session php.ini settings…those are important. But we’ll get tothose a little later.
  23. <?php EVAL() = EVIL For this reason NEVER EVER eval() user submitted data… Doing so gives them the ability to execute whatever PHP code they feel like. <?php $user_input = var_dump(get_defined_vars()); die();; eval($user_input); // I now know all of your variables :) ?> Also NEVER directly exec() or shell_exec() user based data, as this may allow them to run commands on your server (ie: rm –r *)
  24. COOKIES:Because cookies are controlled by users, never storesensitive data that is used to determine a user‟s identity,role, permissions, password, or personal information.Instead utilize sessionswhich provide a unique,temporarily key to identify the user.
  25. COOKIES:Cookies may also be accessed through JavaScript, makingthem vulnerable not only to user manipulation, but tobeing read by sites injecting malicious code.Essentially, cookies should only be used for the sessionidentifier, and for basic data storage that is not sensitiveand needs to persist past the session expiration, forexample, you could use a cookie to set a theme, for“remember me,” or for making sure cookiesare enabled.
  26. OpenSource VulnerabilitiesIf you‟re using an Open Source Solution, such asWordPress, Drupal, Joomla, etc… it‟s a good ideato add a scanner to check for malicious evals()added through back doors in the code, this is a shameless plug for one of my scriptsDon’t worry… It’s free
  27. <?php AVOID USER DEFINED PATHS Avoid relying on user controlled data for defining include() and require() paths. <?php $user_input = images/my_malicious_file.php; require($user_input); // With allow_url_include set to 1 in php.ini $user_input =; require($user_input); ?> If you need to use user data to define a path, check it against an array of allowed files using in_array(). It is also recommended that you set the allow_url_fopen & allow_url_include php.ini settings to off (0).
  28. <?php PLACE INCLUDES IN AN ACCESS CONTROLLED FOLDER Files that perform sensitive actions and are included in your script through an include() or require() should be stored in a private directory, such as /var/usr/includes/ instead of the public_html, httpdocs, or www folder. This prevents the files from being called directly and helps prevent hackers from abusing your code and running files outside of their intended environment.
  29. <?php AVOID FORGEABLE $_SERVER VALUES Also avoid relying on PHP_SELF or REQUEST_URI to determine what page the user is on. These can be tricked if not handled properly. <?php // Actual Page is admin/index.php $_SERVER[PHP_SELF] = admin/index.php/login.php; $page = array_pop(explode(/, $_SERVER[PHP_SELF])); if(!$loggedIn && $page == login.php) { // let them access the page } ?> This was a security flaw in early versions of OS Commerce! These variables should only be used to determine the path of dynamic content, such as in a database driven site or an MVC framework. For procedural code, use $_SERVER[„SCRIPT_NAME‟] instead.
  30. A BIG part of security ishandling incoming dataUse the appropriate SuperGlobals - $_GETand $_POST. Do NOT use $_REQUESTas you can‟t tell WHERE the data is comingfrom ($_POST, $_GET, $_COOKIE)
  31. <?php Use the SuperGlobal Array When checking to see if a form is submitted, check the SuperGlobal $_POST array instead of relying on $_SERVER[„REQUEST_METHOD‟]. This helps reduce illegal offsets. <?php if($_SERVER[REQUEST_METHOD] == POST) { // Can be triggered without data } if($_POST) { // Will not be trigged - no data } ?> Remember that CURL example? I can tell your server that it‟s a POST without sending any $_POST data!
  32. <?php And make sure values exist Check that a value exists using the isset() function before using it in your script to prevent illegal offset errors. Form inputs such as text, select, submit, etc should be set even if empty. <?php if(!isset($_POST[firstName])) { die(Not from my form! firstName was a textbox!); } // Checkbox or Possibly Radio (if not checked by default) if(!isset($_POST[signMeUp])) { die(They didnt check the checkbox); } ?> Note – an unchecked checkbox will not be set in the $_POST array.
  33. <?php ALWAYS VALIDATE Validating Parking is… err, sorry, wrong presentation. Always validate that the data provided by the user meets the criteria of the data that is expected. PHP 5.2 introduced the filter_var function to help with this. <?php if(!filter_var($_POST[email], FILTER_VALIDATE_EMAIL)) { die(This is an invalid email); } if(!filter_var($_POST[url], FILTER_VALIDATE_URL)) { die(This is an invalid url); } ?> It‟s great to validate data using HTML5 and JavaScript, but keep in mind these can be bypassed. Always validate on the back-end
  34. <?php ALWAYS VALIDATE You can also create your own validations by checking the type of the user provided data, or running it against a Regular Expression. <?php // For a Specific Pattern if(!preg_match(/[0-9]{4}/, $_POST[year])) { die(This is not a valid 4-Digit Year); } // For Specific Choices $array = array(male, female); if(!in_array($_POST[gender], $array)) { die(This is not a valid gender); } // For Specific Types if(!is_int($_POST[year])) { die(This is not a valid integer); } ?>
  35. SO FAR WE HAVE 3 LAYERSAs you can see, these security steps aren‟t independent of eachother, but work together to provide a multiple levels of security toprevent malicious code and attacks from getting through. So far wehave: • Controlling Incoming Data (ini file) • Checking Data Types ($_POST) • Validating All Incoming DataEvery layer reduces the risk of security breaches, and with eachlayer we eliminate different types of attacksthat other layers can’t protect against.
  36. <?php ALWAYS SANITIZE Validation helps prevent bad data from getting through, however, sometimes we can‟t test the data for a specific pattern, or it may still contain dangerous code. It is important to sanitize the data before processing it. <?php // Sanitize an Email in PHP 5.2+ $safeEmail = filter_var($_POST[email], FILTER_SANITIZE_EMAIL); // Sanitize an URL in PHP 5.2+ $safeUrl = filter_var($_POST[url], FILTER_SANITIZE_URL); ?> PHP 5.2+‟s filter_var() function can also be used to sanitize data by using the sanitize filters instead of the validate filters
  37. <?php STRIP_TAGS() The strip_tags() function removes HTML tags from a string. It takes an optional parameter for allowed tags (<b>, <p>, etc). <?php $unsafeData = <script>location.href=mysite;</script>; $new = strip_tags($unsafeData); echo $new; // echos out "location.href=mysiste;" ?> This function does not modify any attributes on the tags that you allow using allowable_tags, including the style and onmouseover attributes that a mischievous user may abuse when posting text that will be shown to other users.
  38. <?php HTMLENTITIES() The htmlentities() function converts text into it‟s HTML entity. This means characters like “<“ and “>” are converted to “&lt;” and “&gt;” htmlentities also takes a flag to convert quotes (ie &quot;) <?php $unsafeData = <script>location.href=mysite;</script>; $new = htmlentities($unsafeData); echo $new; // echos out // &lt;script&gt;location.href=mysite;&lt;/script&gt; ?> If your script lets users add their own HTML, you can use htmlentities for sanitizing the code prior to storing, and then utilize html_entity_decode() to restore their code on output.
  39. <?php BLACK LISTING Black listing is not considered an effective method as you cannot possibly black list all potentially dangerous code. Black listing requires that your script be constantly playing catch-up to the latest techniques used by hackers. Just the same, black listing can be used in conjunction with other sanitization methods, but should not be used as your only method of sanitization. <?php $blacklist = array(/</?script[^>]*>/); $unsafeData = <script>location.href=mysite;</script>; $new = preg_replace($blacklist, , $unsafeData); echo $new; // echos out "location.href=mysite;" ?>
  40. <?php BLACK LISTING WIN <?php $blacklist = array(/onMouseOver/); $unsafeData = <b onclick="location.href=mysite;">Text</b>; $new = strip_tags($unsafeData, <b>); // Text still contains vulnerability if(preg_match($blacklist, $new)) { die(Text is not secure); } echo $new; // Yay! Just prevented a vulnerability! ?> The above example assumes that the user needs to be able to utilize the <b> tag. But because strip_tags() doesn‟t remove the onMouseOver attribute, we can check for it in our script and then kill the page if we find it. However, there are better ways around this.
  41. <?php BLACK LISTING FAIL <?php $blacklist = array(/</?script[^>]*>/); $unsafeData = <Script>location.href=mysite;</Script>; $new = preg_replace($blacklist, , $unsafeData); echo $new; // Whoops! We just ran their JavaScript ?> Because our preg_replace is case sensitive we missed this simple work around. This is the problem with black listing, we are limited to what we think of and guard against. strip_tags() and htmlentities() are preferred methods over blacklisting.
  42. <?php TAG PLACE HOLDERS Another way around allowing HTML tags is to use html tag placeholders. These are commonly used in forums and comment forms. For example, instead of using allowing <b> and </b> which can have onMouseOver hidden in them, we can let the user use [b] and [/b]. Now we can use htmlentities() to protect against these attacks. <?php $find = array([b], [/b]); $repace = array(<b>, </b>); $userData = [b]<b onMouseOver="script">My Text</b>[/b]; $new = str_replace($find, $replace, htmlentities($userData)); // Weve removed their HTML code injection, and turned the // [b] and [/b] into HTML tags. The world is happy. ?>
  43. <?php DATABASE SANITIZATION It‟s extremely important to ensure that user imported data is sanitized for database storage. In MySQL you can use the mysql_real_escape_string() function. <?php $userData = 1" OR 1="1; // Returns ALL Records mysql_query(SELECT * FROM table WHERE column = ".$userData."); // Will Not Return Any Records - Vulnerability Prevented $new = mysql_real_escape_string($userData); mysql_query(SELECT * FROM table WHERE column = ".$new."); ?> Most PHP database extensions have their own escape function. However, for best results it is recommended to utilize PDO and take advantage of it‟s binding capabilities.
  44. DATABASE ACCESSYou should also control database access, grantingthe web application only the privileges it needs forthe commands it needs to run.For example, unless your application needs theability to create and drop tables, the web applicationuser (for the database) should not have “create”and “Drop” privileges. By controlling database access you help reduce the potential for damage from an application that has been compromised. Keep in mind this will not protect sensitive data.
  45. DATA ENCRYPTIONTo prevent sensitive data from becomingcompromised in the event of a vulnerability, it isrecommended to use 1 or 2 way encryption to protectthe data.For example, passwords can be encrypted usingMD5() to prevent reverse engineering (since manyusers only have one password for all accounts).Data that needs to be interpreted by the script canbe encrypted using one of PHP‟s 2-way encryptionfunctions (such as crypt()). This protects dataintegrity in the event that the database iscompromised.
  46. DATA ENCRYPTIONNote – MySQL has it‟s ownencryption function called password().However, this function is not designedfor storing encrypted data, as thealgorithm may change in a futureMyQL release, causing an encryptionmismatch- making your dataunusable.
  47. Another BIG part of securityis handling OutputAlong with sanitizing data for storage andinternal use within your script, it is just asimportant to ensure that your users are notsubjected to malicious code, or given toomuch information.
  48. XSS – Cross Site ScriptingEarlier, we looked at how to sanitize code containing a<script> tag. This is just one example of Cross SiteScripting (XSS).Hackers can post malicious code in comments, posts, oreven append it as part of a query string. If not escaped,it allows the hacker to send malicious code to yourviewers, whether it be a simple redirection, downloadingmalicious software, or stealing user data.
  49. XSS – Cross Site ScriptingBy using strip_tags() AND htmlentities(), we can prevent theseattacks.We can also prevent JavaScript from accessing cookies bysetting the session.cookie_httponly = 1 and using the optional$httponly flag in the setcookie() function. setcookie($name, $value, $expire, $path, $domain, $httponly)HttpOnly is not supported by all browsers, but when set can helpreduce the risk in the browsers that do support it. By default,$httponly is set to false in setcookie.
  50. Display Generic ErrorsPHP provides terrific logging for Errors,Warnings, and Notices. Displaying thisinformation is tremendously helpful in adevelopment environment, but can give hackersinside information on how your script works ifavailable in production.Turn off display_errors and setup custom errorhandlers to prevent this.
  51. KEEPING SESSIONSSECUREWhile sessions tend to be fairly secure, thereare two very popular attacks designed toexploit the way sessions are handled. Theseare the session hijacking and session fixationattacks.
  52. SESSION HIJACKINGSession Hijacking is just that, a hackerguesses or steals the session ID, and thensets up a cookie or applies the session ID tothe page url to “hijack” the users session,accessing their account with their privileges.
  53. SESSION FIXATIONSession Fixation is similar to sessionhijacking, with the exception that the hackercreates the initial session, and then sends aurl with the session ID to their victim. Thevictim unknowingly logs in not only on theircomputer, but on the hackers computerthrough the session ID.
  54. KEEPING SESSIONSSECUREThankfully, both of these attacks are fairlyeasy to prevent, and while they require a littlebit more work, you can use the same steps toprevent both simultaneously.
  55. <?php Remember Your INI session.use_only_cookies = 1; This ini directive prevents session IDs from being obtained from the URL. This helps prevent session fixation as any session IDs attached to the url are simply ignored.
  56. <?php Remember Your INI session.cookie_httponly = 1; This ini directive tells the browser NOT to let client-side scripts access the session cookie. However, it is up to the browser to enforce this, and for this reason HTTPOnly is not considered reliable.
  57. Change the Session IDWhenever performing sensitive orimportant actions within a session,use session_regenerate_id() to changethe session ID. Constantly changingthe ID makes session fixation andhijacking more difficult.
  58. Use a Token IDAlong with having a session ID, set acookie with a unique token ID. Thisway you can validate that the machineattempting to use the session is thesame machine that the sessionoriginated from.A cookie token ID is more reliable than trusting theHTTP_USER_AGENT or IP Address as the hacker must haveaccess to the machine or intercept communication in order toretrieve it.
  59. <?php Using a Token ID <?php session_start(); if(!isset($_SESSION[token]) || !isset($_COOKIE[token]) || $_SESSION[token] != $_COOKIE[token]) { session_regenerate_id(true); // note the delete_old_session parameter is set to true $token = md5(rand(11111111111, 99999999999)); // you can create a much more secure token //by utilizing alphabetic $_SESSION[token] = $token; setcookie(token, $token); } ?> While this is not 100% fool proof, it does add another extremely effective step to help protect your application, data, and user integrity.
  60. There are a lot more ways to add security to your application, unfortunately, we don‟t have time to discuss all of them… However, you can find a lot more information on the web, and I will be posting more on
  61. You should also check out: /manual/en/security.php@mikegstowe