Arbitrary class instantiation & local file inclusion vulnerability in QRadar Forensics web application

Abstract

It was found that the QRadar Forensics web application is vulnerable to instantiation of arbitrary objects based on user-supplied input. An authenticated attacker can abuse this to perform various types of attacks including Server-Side Request Forgery and (potentially) arbitrary execution of code.

In addition, the same input is also used to include PHP files, which can be used to include arbitrary local files. By abusing the case upload functionality, it is possible for an authenticated user to upload a PHP file to a known location on the system. By exploiting the local file inclusion vulnerability it is possible to run arbitrary PHP code. This code will be executed with the privileges of the Apache system user (generally the nobody user).

See also

CVE-2020-4272 6189645 - IBM QRadar SIEM is vulnerable to instantiation of arbitrary objects (CVE-2020-4272)

Tested versions

This issue was successfully verified on QRadar Community Edition version 7.3.1.6 (7.3.1 Build 20180723171558).

Fix

IBM has released the following versions of QRader in which this issue has been resolved:

Introduction

QRadar is IBM's enterprise SIEM solution. A free version of QRadar is available that is known as QRadar Community Edition. This version is limited to 50 events per second and 5,000 network flows a minute, supports apps, but is based on a smaller footprint for non-enterprise use.

The QRadar web application contains functionality to render various graphs. The graph that needs to be rendered is based on user-supplied request parameters. The correct graph and dataset classes are dynamically loaded based on these parameters. No validation is performed on the user-supplied parameters, allowing authenticated users to instantiate arbitrary classes, which can be exploited to perform various attacks including Server-Side Request Forgery and (potentially) arbitrary execution of code via specially crafted Phar files.

In case a dataset class is provided that has not been declared (loaded) yet. The code tries to include the correct PHP file in which the class is defined. The file name of the include file is also based on the same request parameter. Consequently, the web application is vulnerable to local file inclusion.

If an attacker manages to place an arbitrary PHP file on the local system, it is possible to abuse this issue to run arbitrary PHP code. It was found that the case upload functionality allows uploading of PHP files to a known location, thus allowing for the execution of arbitrary PHP code. This code will be executed with the privileges of the Apache system user (generally the nobody user).

Details

These issues are present in the graphs.php file. This PHP file accepts a number of request parameters, including chart, dataset, and output_image.

/opt/ibm/forensics/html/graphs.php:

$chart        = ( isset($_REQUEST['chart']) ? htmlspecialchars($_REQUEST['chart']) : null );
$dataClass    = ( isset($_REQUEST['dataset']) ? htmlspecialchars($_REQUEST['dataset']) : null );
$output_image = ( isset($_REQUEST['output_image']) ? $_REQUEST['output_image'] : null );

If the output_image parameter is set to true, the PHP code will directly try to instantiate an object with the name provided in the chart parameter. One argument is passed to the constructor for which its value is obtain from a request parameter with the same name as the selected class name. If the class is successfully loaded, the drawChart() method is called - regardless of whether this method actually exists.

/opt/ibm/forensics/html/graphs.php:

// Present the data
**$cparams = $_REQUEST[$chart];**
**$cs = new $chart($cparams);**
if($cs)
	$cs->drawChart();

No validation is performed on the user-supplied input, allowing for authenticated attackers to instantiate practically any object in scope of the page. In addition, the first argument that is passed to the constructor is also controlled by the attacker.

What an attacker might do depends on the class that is instantiated and the code that is executed by the constructor. A possible attack scenario would be to perform a Server-Side Request Forgery attack by instantiating a class that calls a method supporting one of the built-in PHP wrappers.

Several classes exists in the Forensics code base, like the DistribConfigHelper class. There are also built-in PHP classes that are in scope and also allow for Server-Side Request Forgery, like the SplFileObject class. For example:

https:///forensics/graphs.php?chart=DistribConfigHelper&DistribConfigHelper=https://127.0.0.1/&output_image=1 https:///forensics/graphs.php?chart=SplFileObject&SplFileObject=https://127.0.0.1/&output_image=1 https:///forensics/graphs.php?chart=SplFileObject&SplFileObject=php://filter/read=string.toupper/resource=https://127.0.0.1/&output_image=1

Using the same PHP wrappers it is also possible to load arbitrary Phar files from the local machine. A known attack (by Sam Thomas) exists where an attacker can trigger PHP objects to be deserialized when a Phar file is loaded. Although code execution through deserialization is possible in the Forensics application, exploiting this issue is not that trivial. In particular, the attack can only be executed from an object with a __wakeup() or __destruct() PHP magic method. The classes in scope of the vulnerable page don't appear to have suitable magic methods that could be used to execute an exploit (POP) chain.

Besides finding a suitable magic method, exploiting the Phar wrapper also requires that the attacker can place a Phar file on the target systems as Phar files can't be loaded from remote locations. It was found that the case upload functionality allows uploading of files to a known location. However, since the graph page also contains a local file inclusion vulnerability, it makes more sense to target that vulnerability instead.

The vulnerable code is executed in case the output_image request parameter isn't present or is set to false. In this case the requested class name is provided in the dataset request parameter. If this class isn't (yet) in scope of the PHP page, an attempt is made to load it. This is done by iterating though a list of predefined folder names, if a file exists with the same name of the requested class, it will be included after another which check is done to see if the class is in scope.

/opt/ibm/forensics/html/graphs.php:

$haveDataClass = class_exists($dataClass);
if(!$haveDataClass) {
	**foreach(array('', $DEJAVU_URL. 'Reports/','reports/') as $path)** {
		$module = $path . $dataClass . ".php";
		if(**file_exists($module)**) {
		try {
			**require_once($module)**;
			$haveDataClass = class_exists($dataClass);
			if($haveDataClass)
				break;
		} catch (Exception $e) {
			// Do nothing
			$msg = $e->getMessage();
		}
	}
}

As no validation is done on the class name, it is possible to include files outside of these folder using path traversal. However this isn't really needed as the first folder that is searched is empty, thus allowing for absolute path names. In addition, it is also possible to provide URL type paths. The call to file_exists() will block most PHP wrappers. Some built-in wrappers will pass through the file_exists() call, including the ftp:// and ssh2.sftp:// wrappers. In theory, it should be able to include a file over (S)FTP were it not that including files from remote locations has been disabled in the PHP configuration.

/etc/php.ini:

; http://php.net/allow-url-include
allow_url_include = Off

Because it is possible to upload arbitrary files via the case upload functionality, it is not that difficult to run arbitrary PHP code regardless of these restrictions. Although other methods also exists, we can just upload a PHP file to a known location and abuse this local file inclusion vulnerability to execute the uploaded file.

Proof of concept

Vragen of feedback?