CakePHP Application Cybersecurity Research – Attack surface in CakePHP web application penetration testing
Improve your web application scanner by understanding the attack surface
Understanding the attack surface of the web application is a very important step while conducting cybersecurity research or penetration testing. Even if you are running a web application scanner as part of DAST activities, knowing the attack surface will help you cover more functionality of the application with a scan. If you are using a source code web application scanner, understanding the attack surface will help you find true positives in the SAST tool’s report.
!This is the second post in the “CakePHP Application Cybersecurity Research” series. In the first post, I described the basics of CakePHP applications in the context of security. If you haven’t seen it yet, check it out, as in this article I use many concepts explained in the previous blog post: White box penetration testing in action. Here you can also find the other articles in the series:
- Be Careful with Reflections For Your Web Application Security
- Protect Your Website from Stored XSS Attacks: Understanding and Preventing Vulnerabilities in Open-source Applications
- Exploring the PHAR Deserialization PHP Vulnerability: A White Box Testing Example
- The Impact of a PHP Vulnerability: Exploring the Password Confirmation Bypass in MISP
- Hiding in Plain Sight: The Hidden Danger of SQL Injection in Input Field Names
- Bypassing security mechanisms in CakePHP vulnerability scanning
In this article you will find:
- Improve your web application scanner by understanding the attack surface
- Attack surface in CakePHP web applications
- Web application scanner based on routes in CakePHP
- Automated route extraction in CakePHP
- Know your CakePHP controllers to scan them with a web application scanner
- Extracting actions in CakePHP web application
- Know which plugins your CakePHP application uses
- Understanding named parameters in older versions of CakePHP
- Putting it all together – dynamically extracting information ready for a web application scanner
- Utilizing the knowledge of CakePHP attack surface
- Whitebox penetration testing service
Understand the attack surface
Whether you are using a web application scanner as part of a DAST scan, or performing web application penetration testing or cybersecurity research, all functionalities accessible to users and all user inputs represent the attack surface.
Imagine a house with a front door and windows that anyone can see, touch, and try to open. If you have a key, you can put it in the keyhole of the front door, open it, and suddenly all the other rooms are available. Some rooms are locked, others require you to go to another floor. You may see a children’s room with a sign that says “secret password required to enter,” and they won’t let you in unless you knock on the door in the correct order and say the secret phrase.
The front door and windows in this analogy represent Web application functionality that is accessible to everyone. The keys to the front door and locked rooms, as well as the secret password and knocking sequence, are user input. The fact that you can use your legs and walk up the stairs is also a user input that allows you to reach rooms on different floor.
You can clearly see that some areas of the house are only available when you perform certain actions.
For example, if you want to see if the kids have cleaned their room, you have to:
- insert the right key into the keyhole (the key is a user input)
- go to the door of your child’s room
- knock 3 times (the knocking sequence is a user input)
- say the secret password “I’m rubber, you’re glue” (your password is a user input)
This means that if you want to use a web application scanner to find vulnerabilities in your application, the scanner may only see the front door and windows of your house and not touch the rest of it, if it is not “smart” enough.
Note that in the process of checking if the kids’ room is clean, we assume that you know the house, the layout of the rooms in it, and the way to your kids’ room. We also assume that you have the only key that fits the keyhole, that you know the secret password to the children’s room, and that you are able to climb the stairs. This shows your knowledge of one particular path. If you know all the paths in your house, it means you know your “attack surface” and you can check if all your rooms are clean. Not just the front walls of the house or the kids’ room.
CEO, Cybersecurity Expert
If you would like to conduct a white box penetration testing of your web application leave your email and I will contact you.
Book a chat with me
Attack surface meaning in SAST, DAST, and web application penetration testing
In practice, you may perceive the attack surface differently depending on the type of security assessment you want to perform.
For DAST using a web application scanner or a web application penetration testing, the attack surface is represented by all the pages, panels, views, API endpoints of your web application, and also by all the fields, parameters, forms, and metadata that you can provide to all those pages of the web application.
This is a bit different if you are using a source code web application scanner. These tools typically don’t use an attack surface concept, but follow code flows from sources (user input) to sinks (functionality).
Attack surface in CakePHP web applications
If you are focused on finding vulnerabilities in web applications based on CakePHP, the framework can be difficult from a security perspective. However, we can use its own functionalities to extract useful information for a penetration tester or researcher.
If you read the previous article, you know that the CakePHP framework is based on the MVC (Model, View, Controller) pattern. We can use this to map the attack surface of a CakePHP web application by extracting the routes, controllers, actions, and more information.
I will show you two methods to do this. The first is to statically analyze the source code, and the second is to use a script that automatically extracts it.
You can use the static analysis method to create similar scripts in other web application frameworks.
All the scripts that I will show you here are available in the GitHub repository https://github.com/Zigrin-Security/CakePHPHackingScripts
Web application scanner based on routes in CakePHP
Routes are definitions of URLs that can be accessed by users. From the previous article, you know that routes are defined in the file /config/routes.php. Let’s take Cerebrate as an example and focus on one scope:
$routes->scope('/', function (RouteBuilder $builder) { … $builder->registerMiddleware('csrf', new CsrfProtectionMiddleware([ 'httponly' => true, ])); if (empty($_SERVER['HTTP_AUTHORIZATION'])) { $builder->applyMiddleware('csrf'); } … $builder->connect('/', ['controller' => 'Instance', 'action' => 'home']); $builder->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']); $builder->fallbacks(); });
Scopes are groups of routes that share the same configuration. In this case, we have only one scope. All routes defined in this scope start with the / in path.
The first important thing in routes is the connect instructions (lines 10 – 11).
Based on this, we have two routes:
- / – executes the home action of the Instance controller
- /pages/* – executes the display action of the Pages controller.
The asterisk means that it accepts arguments that can be passed to the action of the controller. This can correspond to the following URL:
https://cake_php_application/pages/argument1
The fallback line 12 defines the default routes which are:
- /<controller> – executes the index action of the specified controller
- /<controller>/<action>/* – executes the specified action in a specified controller
We can access the same actions/controllers using the default routes:
- https://cake_php_application/Pages/display/argument1
- https://cake_php_application/instance/home
Another important thing to note is the middleware defined for all routes in the scope (lines 3 – 8).
The Cerebrate configures the CSRF protection for all routes in the scope except when the Authorization header is present in the request.
The next part of the routes.php file defines an Open prefix:
$routes->prefix('Open', function (RouteBuilder $routes) { $routes->setExtensions(['json']); $routes->fallbacks(DashedRoute::class); });
Prefixes are very similar to scopes. The main difference is that they look for the corresponding controller under a different path.
On line 2 of the code above, you can see that an extension is defined. This simply means that URLs starting with /open can end with .json extension.
At the end of the prefix definition there is a fallback with default routes, but this time it’s a dashed route. It basically changes dashed URLs to paths with capital letters. For example: https://cake_php_application/my-messages/show-message will result in the ShowMessage action of the MyMessages controller to be executed.
So we have 2 routes explicitly defined in the main scope, two default routes also in the main scope, and two fallback routes prefixed Open.
Are these all the routes in your CakePHP application?
Most likely not.
The thing is, routes can be defined in other places in the application, such as internal or external plugins. You can do a quick search for routes.php files. There may be other places with route definitions too. But how can we find out where they are automatically?
Automated route extraction in CakePHP
Opening files and searching for route definitions is already a step forward in understanding the attack surface, but we can take a step further and extract it from a working web application. This requires two steps.
The PHP application can be run from the command line like this php /var/www/webapp/webroot/index.php
In addition, we can write a small PHP script that imports the index.php and executes the code of the target web application. This is the first step:
<?php ob_start(); include $argv[1]; $output = ob_get_clean();
Lines 2 and 4 are responsible for capturing the application output. By default, the target web application will probably return some HTML response that we don’t care about. We just use the PHP mechanism to catch that output.
Line 3 imports the php file that we passed as a parameter to our example script. You can run it with the following command:
php cakephp_extractor.php /var/www/application/webroot/index.php
When you run it, you should get either no result or PHP warnings/errors. This is because the application may log a lot on stderr. For now, we don’t need to worry about that.
The second step requires playing with CakePHP itself. When you send an HTTP request to a CakePHP application, all the routes are stored in the memory in a Cake\Routing\Router
object. When we import index.php, we can access this object and compile each route into a printable regular expression.
Here is a script that does this:
<?php function get_routes() { $str_routes = array(); $routes = Cake\Routing\Router::routes(); foreach($routes as $route) { $str_routes[] = $route->compile(); } $str_routes = array_unique($str_routes); return $str_routes; } if(count($argv) <= 1) { print("Usage: {$argv[0]} path/to/index.php\n"); die; } ob_start(); include $argv[1]; $output = ob_get_clean(); $routes = get_routes(); foreach($routes as $route) print("$route\n");
First, we include the index.php from the command line argument in line 18. This instantiates all variables the target application is using together with the object Router. Next we call the get_routes function in line 21.
The get_routes function extracts all the routes in line 4. These routes are also objects, so we need to compile them into regular expressions. This is done on line 6 for each route. In line 8, we just make sure that there are no duplicates and return the routes.
If you run this script on a working Cerebrate application from the previous examples, you should get the output similar to the following:
You can see that there are more than six routes that we discovered through manual analysis.
The regular expressions may be difficult to understand at first, but eventually, you’ll see the static values and common blocks such as:
- <controller>
- <action>
- <_args_>
Let’s take a look at the last route from the screenshot above to explain the meaning behind the regular expression.
#^/(?:(?P[^/]+)) / (?:(?P[^/]+))(?: /(?P < _args_ >.*)) ? [ / ]*$#
Here are the main blocks of this route
- / – every URL path starts with a / at the beginning
- (?:(?P<controller>[^/]+))
- (?: Indicates the non-capturing, which group that consists of only one element (group), so we can skip it
- (?P – Specifies a name capture group named controller, the group catches everything except the character /
- / – a slash after the controller name
- (?:(?P<action>[^/]+)) – The same as above, but the group name is action
- (?:/(?P<_args_>.*))? – Non-capturing optional group that has two elements
- / – A slash character after the action group
- (?P – Specifies a name capture group with a name _args_ that captures everything
- ? – Indicates that the arguments group is optional
- / – Optional one or more slashes
This simply translates to this URL path scheme:
/<controller>/<action>/<optional_arguments>
To analyze regular expressions I highly recommend using https://regex101.com/
Know your CakePHP controllers to scan them with a web application scanner
Controllers are easier to extract because they are represented by files in /src/Controller directory and all its subdirectories. All filenames ending in Controller.php are controllers. Using Cerebrate as in our previous example, here is a quick bash command that extracts controllers from the CakePHP application:
Here we just search for all files that end with Controller.php and remove unnecessary path elements. Keep in mind that you will get subdirectories here such as Open/Individuals.
Using our clever method of importing index.php into our PHP script is not necessary here, but it will be useful for extracting actions. It has one main advantage. If the controllers are stored in a path other than /src/Controller, they will still be detected.
<?php function get_controllers() { $controllers = array(); $c_const = 'Controller'; $ext = '.php'; $dirs = Cake\Core\App::classPath($c_const); foreach($dirs as $dir) { $paths = get_dir_content($dir); foreach($paths as $path) { if(strpos($path, $c_const.$ext) !== false) { $controllers[] = substr($path, strlen($dir), -strlen($c_const)-strlen($ext)); } } } $controllers = array_unique($controllers); return $controllers; } function get_dir_content($dir, &$results = array()) { $files = scandir($dir); foreach ($files as $key => $value) { $path = realpath($dir . DIRECTORY_SEPARATOR . $value); if (!is_dir($path)) { $results[] = $path; } else if ($value != "." && $value != "..") { get_dir_content($path, $results); $results[] = $path; } } return $results; } if(count($argv) <= 1) { print("Usage: {$argv[0]} path/to/index.php\n"); die; } ob_start(); include $argv[1]; $output = ob_get_clean(); $controllers = get_controllers(); foreach($controllers as $controller) print("$controller\n");
On line 8, we dynamically extract all directories that are registered as controller directories. On line 10, we use a recursive helper function to extract all files and directories in each directory registered as a controller directory. On lines 12 and 13, we simply check if the file ends with the Controller.php and if so, we extract the name of the controller and store it in the array. Finally, in lines 17 and 18, we make sure that all controller names are unique and return the result.
In the case of the Cerebrate application, our script returns the identical output as the previous bash command:
Extracting actions in CakePHP web application
Actions are represented by methods within the controller’s class. For example, the “add” action of the “Users” controller in the Cerebrate project can be seen here: https://github.com/cerebrate-project/cerebrate/blob/a7dca8284bdba862c8367f2afb479011d7460ca3/src/Controller/UsersController.php#L59
Note that the “add” method is public.
Automating the extraction of actions is not as simple as extracting controllers, but we can still do it using a similar PHP script.
A brief explanation is below, let me know if you want me to explain the code in more detail.
For each controller, we extract its full class name including the namespace. This is done by the load_controller function.
The function get_controller_action extracts all methods of the controller and removes private methods from the list using the function is_controller_action.
The final script that extracts all controllers and their actions is over one hundred lines long:
<?php function get_controllers() { $controllers = array(); $c_const = 'Controller'; $ext = '.php'; $dirs = Cake\Core\App::classPath($c_const); foreach($dirs as $dir) { $paths = get_dir_content($dir); foreach($paths as $path) { if(strpos($path, $c_const.$ext) !== false) { $controllers[] = substr($path, strlen($dir), -strlen($c_const)-strlen($ext)); } } } $controllers = array_unique($controllers); return $controllers; } function get_dir_content($dir, &$results = array()) { $files = scandir($dir); foreach ($files as $key => $value) { $path = realpath($dir . DIRECTORY_SEPARATOR . $value); if (!is_dir($path)) { $results[] = $path; } else if ($value != "." && $value != "..") { get_dir_content($path, $results); $results[] = $path; } } return $results; } function get_actions() { $result = array(); $controllers = get_controllers(); foreach($controllers as $controller) { $class = load_controller($controller); if ($class!==false && class_exists($class)) { $result[$controller] = get_controller_action($class); } } return $result; } function load_controller($controller) { $pluginPath = ""; $namespace = "Controller"; $className = Cake\Core\App::className($pluginPath . $controller, $namespace, $namespace); $reflection = new ReflectionClass($className); if ($reflection->isAbstract()) { return false; } if (class_exists($className)) { return $className; } return false; } function get_controller_action($controller_class) { $controllerObject = new $controller_class(); $actions = array(); $methods = get_class_methods($controllerObject); foreach($methods as $method) { if(is_controller_action($controllerObject, $method)) { $actions[] = $method; } } return $actions; } function is_controller_action($controllerObject, $action) { $baseClass = new ReflectionClass(new Cake\Controller\Controller); if ($baseClass->hasMethod($action)) { return false; } try { $reflectionMethod = new ReflectionMethod($controllerObject, $action); } catch (ReflectionException $e) { return false; } $objectReflection = new ReflectionClass($controllerObject); if($reflectionMethod->class !== $objectReflection->getName()) { return false; } return $reflectionMethod->isPublic() && $reflectionMethod->getName() === $action; } if(count($argv) <= 1) { print("Usage: {$argv[0]} path/to/index.php\n"); die; } ob_start(); include $argv[1]; $output = ob_get_clean(); print(json_encode(get_actions()));
The end result is a JSON dictionary of controllers and corresponding actions:
Know which plugins your CakePHP application uses
Plugins are a little tricky to get. The easiest way is to look in the /plugins directory. It seems that Cerebrate only uses the Tags plugin: https://github.com/cerebrate-project/cerebrate/tree/a7dca8284bdba862c8367f2afb479011d7460ca3/plugins
However, there may be other plugins used by the application. A better way to get the plugins is to look for places in the code where the plugins are registered. A quick grep for addPlugin string should reveal more plugins:
The automated extraction of plugins from the application is still the most reliable way, as it retrieves exactly all plugins that were used in the application.
Source code:
<?php function get_server_var() { global $app_vars; foreach($app_vars as $k=>$v) { if($v instanceof Cake\Http\Server) return $v; } return null; } function get_object_property($object, $property) { $object_reflection = new ReflectionClass($object); $property_reflection = $object_reflection->getProperty($property); $property_reflection->setAccessible(true); return $property_reflection->getValue($object); } function get_plugins() { $server = get_server_var(); if(is_null($server)) return null; return get_object_property($server->getApp()->getPlugins(), 'names'); } if(count($argv) <= 1) { print("Usage: {$argv[0]} path/to/index.php\n"); die; } ob_start(); include $argv[1]; $output = ob_get_clean(); $app_vars = get_defined_vars(); unset($app_vars['output'], $app_vars['argv']); print(json_encode(get_plugins()));
As a result, we get the list of plugins used by the application:
Understanding named parameters in older versions of CakePHP
As far as I know, this element is only available in CakePHP versions 2 and 3. Keep in mind that CakePHP versions 2 and 3 are no longer supported. However, since there are still web applications that rely on this version of the framework, we will cover this topic.
HTTP is designed to pass parameters through a query string. You’ve seen this before:
https://example.com/posts/view?title=first&category=general
Named parameters are just another way of passing parameters to controller actions via a URL. Here is an example of a named parameter URL path that passes the same parameters as the URL above:
/posts/view/title:first/category:general
A little explanation:
- Controller: post
- Action: view
- Parameter 1:
- Name: title
- Value: first
- Parameter 2:
- Name: category
- Value: general
You can also pass arrays like this:
/posts/view/filter[title][value]:first/filter[title][sort]:asc
You can find these parameters being used in controller files by looking for $this->params['named']
, $this->params["named"]
, or $this->passedArgs
.
The application may use different named parameters in different places in the code. This may also depend on different conditions. Therefore, there is no generic way to automatically extract these parameters.
Putting it all together – dynamically extracting information ready for a web application scanner
We can put it all together in a single script that returns the requested information in a JSON format. The code is available in the GitHub repository.
Here are a few examples of how to use it.
Extracting routes:
Extracting controllers:
Extracting actions:
Extracting plugins:
Getting all this information in JSON format also allows you to pass it on to some scripts and other tools such as web application scanners.
Utilizing the knowledge of CakePHP attack surface
Armed with the attack surface I have described here, you can use this information to better configure your web application scanners, discover hidden functionality in the application during a white box web application penetration testing, and cover more areas of the application with your analysis.
In the coming weeks, we will be releasing a tool that relies heavily on these methods to perform Interactive Application Security Testing (IAST).
!In the next article I will describe the typical security mechanisms in CakePHP, such as blackholing and CSRF protection, and how to bypass these protections when you are configuring a web application scanner.
Whitebox penetration testing service
If you are looking for a professional white box penetration testing service, leave your email in the box below and we will contact you.
Let’s talk about conducting a cybersecurity research of your web application.
Book a chat with a cybersecurity expert
References:
https://book.cakephp.org/2/en/development/routing.html#named-parameters
Is this article helpful to you? Share it with your friends.