We scanned the at the time current version 1.3.11 of ImpressCMS and found an unauthorized SQL Injection vulnerability. The exploit affects installations that use PDO as a database driver. The issue was fixed in version 1.4.0, though the patch does not follow best practices and might not be sufficient. The attack vector does not require user interaction, thus it can be used to automatically manipulate many ImpressCMS sites on a large scale. With this extremely dangerous vulnerability present, the users are highly advised to update their ImpressCMS installations.
The truncated analysis results are available in our RIPS demo application. Please note that we limited the results to the issues described in this post in order to ensure a fix is available.
Spotting the SQL Query
ImpressCMS modules are extensions that provide certain functionality. For instance, the content module allows users to post articles. The basic structure requires a directory for each module. If a user wants to edit content, a PHP file in the according subdirectory is called. Consequently, the directory name is part of the URL. To load the module data from the database, the directory name is used as an identifier. In the case of the content module the URL looks like this: https://example.com/modules/content/admin/content.php.
The susceptible class
icms_module_Handler is in the
Handler.php file and handles the modules of the system.
Each request goes through the
service method of that class to provide the according module which needs to be loaded.
In detail, the directory name is determined through
PHP_SELF, which is part of the
$_SERVER superglobal environment information array.
PHP_SELF contains a short part of the current URL. If a file inside the module directory is called,
PHP_SELF contains such directory/module name.
For example, when calling
/modules/profile/admin/user.php, the module profile needs to be loaded. Therefore, first, a substring from
/modules/ to the end of the string is extracted.
Second, that substring is separated by the slashes as seen in line 417.
$url_array now has the string
profile in the third position, index 2.
If index 2 of the array is set the method
getByDirname() is called with its value as seen in line 420.
getByDirname() the parameter received as
$dirname is not sanitized, concatenated into a SQL query, and executed right after in the following code:
Exploitation via PHP_SELF
To exploit the vulnerability, the
$_SERVER['PHP_SELF'] value must be manipulated to set up
$url_arr so that the value contains an arbitrary SQL command.
This can be achieved by changing the corresponding module directory name to a SQL statement inside the URL. Though the directory does not exist and thus the request results in a 404 file not found error. However, the vulnerable method in
Handler.php is executed on any page, including pages outside of the modules directory.
So to exploit it we call
/admin.php instead because this URL does not try to load a module already. We can append slashes to that URL and it still resolves to the
admin.php file, e.g.
/admin.php/foo, which is crucial for this exploit. This results in
PHP_SELF being user-controlled after the file name, a fact many developers are not aware of. As seen in this exploit the
PHP_SELF value was helpful to determine the directory name, but the developers did not think of it being user-controlled and therefore, dangerous in the context of the method.
Calling https://example.com/admin.php/modules/bar has the consequence that the script
admin.php in the web root is executed and
$_SERVER['PHP_SELF'] has the value
/admin.php/modules/bar. The code from line 417 onwards proceeds, trying to load the “bar” module. As this leads us to the SQL query, we can manipulate “bar” to inject a SQL statement. Calling
/admin.php/modules/' OR SLEEP(1000) -- - is an example for escaping the single quote and executing an arbitrary SQL command.
Furthermore, although we are not logged in, the variable
$isAdmin is set to
true. This allows us to pass the
Handler.php check in line 416.
The best practice to prevent malicious statements from being injected is to use prepared statements. As the vendor never used prepared statements in their code, this fix can not be applied. We do not want to rewrite a large base of the code and therefore, only provide a hotfix. The single quote, which allowed us to break out of the string, needs to be escaped using the
quote() method. Replace line 133 of the
Handler.php file with the following:
In this blog post, we have seen that the innocent appearing
$_SERVER['PHP_SELF'] value is user-controlled and can lead to vulnerabilities like every other user input.
Often it is not obvious which variables can contain user input, thus it is necessary to always use prepared statements and sanitize inputs to maintain a secure application.
We contacted the vendor about the vulnerability but did not receive an answer to our request. The issue was resolved in version 1.4.0, though, the patch does not follow best practices and thus its security can not be guaranteed.
|12/09/19||First contact attempt via contact form|
|12/16/19||Second contact attempt via contact form|
|01/08/20||Third contact attempt via email setting 30 days deadline|