php security 101
DESCRIPTION
The second in the series, PHP Security 101 is focused on the principles of web development security, and utilizes the methods of Prison Theory for Web Development Security.TRANSCRIPT
PHP SECURITY101
AUGUST 12, 2012
michael stowe
• 10+ years experience hacking PHP
• Published Author, Speaker, and Consultant
• Developed applications used by the medical field and law enforcement
• Software Engineer at CaringBridge.org (half a million visitors every day)
• Zend Certified PHP 5.3 Software Engineer
.com
@mikegstowe
MIKESTOWE
WHAT DOES IT MEAN
TO BE SECURE?
PRISON THEORY OF WEB DEVELOPMENT
SECURITY
PRISON THEORY OF WEB DEVELOPMENT SECURITY
• Control User Access and Mobility
• Have a Emergency Response Plan
• Provide Proper Training
• Restrict Potential for Negligence by Staff
• Scan Incoming Traffic
• Validate and Sanitize Everything
• Use Obfuscation
• Log and Monitor Everything
• Proactively Scan and Monitor Code
CONTROL USER ACCESS
One of the most basic layers of security is to simply
control what pages the user has access to. This, in it’s
simplest form means providing work flows to direct users
to only the pages you want them to visit, and in more
complex forms: restricting script directives and validating
user roles and privileges on each page.
Remember, it’s YOUR application. You built
it with a purpose. The way your application
is built should GUIDE users to do the things
you want them to, and PREVENT them from
doing the things you don’t.
INI DIRECTIVES
register_globals = off; < PHP 5.4, Extremely Dangerous
session.use_only_cookies = 1; Ignores querystring session ID
session.cookie_httponly = 1; Limits access to cookies*
allow_url_fopen = 0; Disallows using fopen with external URLs
allow_url_include = 0; Disallows including external URLs
zend.script_encoding = UTF8; PHP 5.4+, sets encoding
* not supported by all browsers
USER AUTHENTICATION <?php
<?php class restrictedController { public function restrictedAction() { // Load User Class $user = new User(); // Validate User if (!$user->loggedIn() || !$user->role('Author')) { $this->redirect('/notAuthorized'); } /** ... **/ } }
HAVE A EMERGENCY RESPONSE PLAN
Security breaches can occur night or day, you need to
have a plan for responding when they do. Your plan
should include different scenarios, and the proper
protocols to follow. You should also have an escalation
plan, in case the security breach is beyond your control or
expertise. Your plan should also include a debriefing to
review how effective the response was, and how to
improve and prevent future security breaches.
SAMPLE PLAN
• Take database containing personal data offline
• Place maintenance page online
• Review logs to identify breach
• Notify on-call experienced employee
• Patch and review for other obvious threats
• Restore database and site
• Proactively scan and identify any other threats
• Report issue and cause
• Debriefing with team
THE DEBRIEFING
Version control, backups, logging, and proactive scans
can help reduce the damage caused by some breaches.
However, team debriefings can prevent this breach from
occurring again by explaining to team members what
caused the vulnerability to begin with.
Debriefings should not be blame sessions, but rather
teaching moments.
PROVIDE TRAINING
It is vital that developers are trained on the proper
security techniques and stay up to date on new
vulnerabilities/ security techniques.
Training helps reduce the risk of security breaches by
teaching the BEST methods while identifying incorrect
practices (ie add_slashes()).
You can INVEST in your DEVELOPER’S
EDUCATION or you can PAY for their
MISTAKES. Guess which one costs more…
RESTRICT POTENTIAL FOR NEGLIGENCE
Some of the most dangerous hackers are on the payroll.
More often than not developers are rushed to complete
part of a project that is dependent on code that they are
not familiar with (ex: services, models, etc).
Without the proper steps this rush code has the potential
to be a welcoming mat for pernicious* visitors.
* Yes, I pulled out the dictionary…
RESTRICT POTENTIAL FOR NEGLIGENCE
Methods to prevent negligent code include:
• Restricted Access (only the code they need)
• Version Control (Git, SVN, CVS, etc)
• Code Reviews and Audits
• End-Point Validation (ex: type-hinting)
• Using Magic (getters and setters)
VERSION CONTROL
Version control allows you to quickly see WHAT code is
being modified, and determine whether or not it should
be merged into your master branch or trunk.
And when something breaks, version control makes it
fairly painless to identify issues and revert back to
previous code.
Git and SVN are two of the more popular version control methods
VERSION CONTROL BASH
Get the latest from Master Branch in Git
git pull master
Create a new branch for a project
git checkout –b mybranch
Make Edits to index.php, and see differences
git diff index.php
Add to be committed
git add index.php
Commit file with message to local repository
git commit –m “Added check to redirect Mobile Users”
Push to Master Repository
git push origin master
GitHub.com provides a nice online code management interface
END-POINT VALIDATION
Simply put End-Point Validation validates the data at it’s
end, instead of relying on previous validations. This
prevents the use of invalid or malformed code that is not
properly vetted prior to injection.
This also prevents developers from passing the wrong
type of information to a service, model, class, or function.
END-POINT VALIDATION <?php
<?php class User { public function editPhone(PhoneNumber $phoneNumber) { if (!$phoneNumber->isValid()) { throw new Exception('Invalid Phone Number'); } $this->phoneNumber = $phoneNumber->get('number'); /** ... **/ } }
USING MAGIC
PHP provides magic methods that can be used to validate
or restrict data based on value or type. By using the
magic __get() and __set() methods you are not
only able to restrict class properties, but ensure the data
passed is correct or manipulate it as necessary.
One such pre-existing code base for this is the
smrtClass() which allows you to set rules, restrict, and
even lock property values.
There are more useful magic methods such as __clone() and __invoke()
USING MAGIC
class User { private $data = array('name', 'phone'); public function __set($name, $value) { if (!isset($this->data[$name])) { throw new Exception('Invalid Property Name'); } $this->data[$name] = $value; } public function __get($name) { if (isset($this->data[$name])) { return $this->data[$name]; } throw new Exception('Property Does not Exists'); } }
Learn more at: http://php.net/manual/en/language.oop5.magic.php
<?php
USING SMRTCLASS
<?php $data = new smrtClass(); // Add an Integer $data->add('number', 'int'); $data->number = '10'; // Add a String $data->add('string'); $data->string = 'Hello World'; // Add an Email String $data->add('emailAddress', 'email'); $data->emailAddress = '[email protected]'; // Lock Data (No Changes) $data->lockData();
Download at: http://www.phpclasses.org/smrtClass
<?php
SCAN INCOMING TRAFFIC
The first step to defending your site against hackers is to
prevent malicious incoming traffic. By checking the user
against a known database of hackers, checking the
browser, checking incoming data, and the use of Cross-
site Request Forgery tokens we can eliminate several
potential threats.
IP BLACKLISTING
IP Blacklisting allows you to prevent IPs identified as
belonging to, or being used by spammers and hackers
from preventing your web application.
IP BLACKLISTING <?php
<?php $blacklisted = array( '123.123.123.120', '123.123.123.121', '123.123.123.122', '123.123.123.123', ); if (isset($_SERVER['REMOTE_ADDR']) && in_array($_SERVER['REMOTE_ADDR'], $blacklisted)) { header('Location: banned.html'); exit(); }
BROWSER SNIFFING
By checking the HTTP_USER_AGENT we can learn more
about our user, including what browser they CLAIM to be
using. Keep in mind that this, like all variables set client
side can be spoofed.
However, if a potential hacker has not gone the extra step
to set this variable in their attack, they will be easily
identified.
BROWSER SNIFFING <?php
<?php $allowedBrowsersRegex = '/ Safari | Chrome | IE | Firefox | Opera /'; if (isset($_SERVER['HTTP_USER_AGENT'])) { if (!preg_match($allowedBrowsersRegex, $_SERVER['HTTP_USER_AGENT'])) { // Unsupported HTTP_USER_AGENT $this->redirect('/browserupgrade'); exit(); } } elseif (PHP_SAPI == 'cli') { // Accessed through Command Line - Unit Test? } else { // No HTTP_USER_AGENT! Curl attack? $this->redirect('/browserupgrade'); }
Remember, HTTP_USER_AGENT CAN be spoofed, do not rely on this
CROSS-SITE REQUEST FORGERY
Simply put End-Point Validation validates the data at it’s
end, instead of relying on previous validations. This
prevents the use of invalid or malformed code that is not
properly vetted prior to injection.
This also prevents developers from passing the wrong
type of information to a service, model, class, or function.
CSRF TOKENS <?php
<?php session_start(); if (!isset($_SESSION['token'])) { $_SESSION['token'] = sha1(uniqid(mt_rand(), TRUE)); $_SESSION['token_time'] = time(); } if ($_POST) { if (isset($_POST['token']) && $_POST['token'] == $_SESSION['token'] && (time() - $_SESSION['token_time']) <= 600) { // Valid Token Submitted within 10 minutes } else { echo '<span class="error">Invalid Submission. Please try again!</span>'; } } ?> <form action="..." method="post"> <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>" /> ... </form>
REQUEST SNIFFING
Request Sniffing is similar to Browser Sniffing with the
exception that we are testing the incoming GET/POST
data to ensure we are receiving ONLY the data we should
be.
Zend Framework 2 allows you to setup form filters that will remove any undefined form elements
REQUEST SNIFFING <?php
<?php $formFields = array( 'name', 'address', 'city', 'state', 'zip', 'token', // don't forget to add any tokens! ); // Validating Post foreach ($_POST as $k=>$v) { if (!in_array($k, $formFields)) { // Delete it unset($_POST[$k]); // Or Just Redirect Them (Especially if Register_Globals enabled) $this->redirect('/invalidRequest'); exit(); } }
VALIDATE EVERYTHING
The biggest mistake any developer can make is trusting
users to send them the correct data. Not only can users
make mistakes, but hackers are keen at taking advantage
of scripts that lack the proper vetting of incoming data.
Failure to validate and sanitize incoming data will open
your application to numerous attacks and vulnerabilities.
SESSION VALIDATION
In order to prevent Session Fixation and Session Hijacking it is
important to validate that the Session ID being used belongs to the
correct user, and is not being used multiple times.
This can be done by storing and validating the user’s IP (problem for
AOL users), the HTTP_USER_AGENT, or using a Cookie based token
(recommended).
Also be sure to utilize session_regenerate_id() when performing
sensitive tasks (logins, credit cards, password updates, etc).
SESSION VALIDATION <?php
<?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); } ?>
VALIDATE POST/ GET
Whenever possible, try to avoid using $_REQUEST to get
incoming data, instead ensuring the data is being sent to
your script through the proper (and expected) channels.
Especially since $_REQUEST will utilize Post, Get, and
Cookie data unless otherwise set in your INI.
Be sure to check that the data exists using isset()
before trying to validate to avoid index errors.
VALIDATE GET/POST <?php
<?php // For a Specific Pattern if (!isset($_POST['year']) || !preg_match('/[0-9]{4}/', $_POST['year'])) { die('This is not a valid 4-Digit Year'); } // For Specific Choices $array = array('male', 'female'); if (!isset($_POST['gender']) || !in_array($_POST['gender'], $array)) { die('This is not a valid gender'); } // For Specific Types if (!isset($_POST['year']) || !is_int($_POST['year'])) { die('This is not a valid integer'); }
PHP FILTER_VAR()
PHP 5.2+ includes the filter_var() function, which
allows you to both validate and sanitize data depending
on the flag used.
You can learn more about filter_var() at:
http://php.net/manual/en/filter.filters.validate.php
PHP FILTER_VAR() <?php
<?php // VALIDATE DATA if (!isset($_POST['email']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) { die('This is an invalid email'); } if (!isset($_POST['genurlder']) || !filter_var($_POST['url'], FILTER_VALIDATE_URL)) { die('This is an invalid url'); }
SANITIZE EVERYTHING
Validation helps reduce the risk of malicious data being passed
through, however, like any security measure it is not completely
fool proof. It is essential to sanitize the data once it has been
validated in order to help prevent malicious data from sneaking
through.
Three helpful functions for sanitizing incoming data include:
• filter_var()
• strip_tags()
• htmlentities()
SANITIZE EVERYTHING <?php
<?php // Sanitize using filter_var() (PHP 5.2+) $safeEmail = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL); $safeUrl = filter_var($_POST['url'], FILTER_SANITIZE_URL); //------------------------------------- // Using strip_tags() $unsafeData = '<script>location.href=\'mysite\';</script>'; $new = strip_tags($unsafeData); echo $new; // echos out "location.href='mysite';" //------------------------------------- // Using htmlentities() $new = htmlentities($unsafeData, ENT_COMPAT | ENT_HTML401, 'UTF-8'); echo $new; // echos out // <script>location.href='mysite';</script>
For PHP versions prior to 5.4 be sure to set the Encoding parameter
DATABASE SANITIZATION
It is important to sanitize your data prior to submitting it
to the SQL Query Engine. When using MySQL you can
utilize the mysql_real_escape_string() function,
but it is highly recommended to take advantage of
MySQLi or PDO.
MySQLi and PDO allow you to build highly efficient
prepared statements, or parameterized statements that
utilize a single query template.
USING PHP DATA OBJECTS <?php
<?php // Setup PDO Object and Connection Information $db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 'username', 'password'); // Build Query Template $stmt = $db->prepare("SELECT * FROM myTable WHERE username = :username AND password = :password"); // Bind and Sanitize Values // You can bind PHP Variables using the bindParam() method instead $stmt->bindValue(':username', $_POST['username'], PDO::PARAM_STR); $stmt->bindValue(':password', $_POST['password'], PDO::PARAM_STR); // Execute and Fetch $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
USE OBFUSCATION
Often times, the simplest forms of protection are also some of the
best. Obfuscation allows us to prevent users from having easy
access to our data and the architectural structure of our
application.
Methods of obfuscation include:
• Using harder to guess variable names
• Not using variable names in URLs or forms
• Encrypting personal information (password, credit cards, etc)
• Providing generic error messages
LOG AND MONITOR
It is extremely important to log system access, system errors,
PHP errors, and user activities. These logs not only provide us
valuable insight into WHO the user is, and WHAT they are trying
to do, but also help us identify problems within our server
environments or PHP scripts.
Whenever testing an application, be sure to check the PHP Errors
log to ensure you are not creating new errors or warnings.
LOGGING BAD ACTIONS <?php
<?php class restrictedController { public function restrictedAction() { // Load User Class $user = new User(); if (!$user->loggedIn()) { $this->redirect('/login'); } // Validate User if (!$user->role('Author')) { $this->logAttempt('restrictedAction', $user); $this->redirect('/notAuthorized'); exit(); } /** ... **/ } }
Multiple logs for the same user may identify a potential hacker
PROACTIVELY SCAN AND MONITOR
It is also important to be proactive in scanning and monitoring
your code for bugs, unexpected behaviors, and maliciously
uploaded code.
Tools to Monitor and Scan Code Include:
• PHPUnit – behavior audit (phpunit.de)
• Selenium – front-end audit (seleniumhq.org)
• PHP Security Audit - code (sourceforge.net/projects/phpsecaudit/)
• Eval Scanner – code audit (mikestowe.com)
• PHP Scanner – front-end audit (mikestowe.com)
TO REVIEW
PRISON THEORY OF WEB DEVELOPMENT SECURITY
• Control User Access and Mobility
• Have a Emergency Response Plan
• Provide Proper Training
• Restrict Potential for Negligence by Staff
• Scan Incoming Traffic
• Validate and Sanitize Everything
• Use Obfuscation
• Log and Monitor Everything
• Proactively Scan and Monitor Code
THANK YOU. @mikegstowe
visit mikestowe.com/slides for more on Security and PHP