ImpressCMS: From Unauthenticated SQL Injection To RCE
According to the official website ImpressCMS is an open source Content Management System (CMS) designed to easily and securely manage multilingual web sites. With this tool maintaining the content of a website becomes as easy as writing a word document. ImpressCMS is the ideal tool for a wide range of users: from business to community users, from large enterprises to people who want a simple, easy to use blogging tool. ImpressCMS is a powerful system that gets outstanding results and it is free!
The application comes with a built-in security module – Protector – which is designed to improve the overall security of ImpressCMS websites and prevent certain web attacks such as Cross-Site Scripting (XSS) and SQL Injection. In this blog post we will see how to bypass such a security mechanism to exploit a couple vulnerabilities I discovered about a year ago, which might eventually allow unauthenticated attackers to execute arbitrary PHP code on the web server (RCE)…
Let’s start to analyze the two vulnerabilities which can be exploited in tandem to bypass access control (KIS-2022-03) and reach a script vulnerable to SQL Injection (KIS-2022-04). Both of them are located in the /include/findusers.php script, which is intended to be used by authenticated users to search for other users. However, due to the following vulnerable lines of code, it can be accessed by unauthenticated attackers as well:
include "../mainfile.php"; xoops_header(false); $denied = true; if (!empty($_REQUEST['token'])) { if (icms::$security->validateToken($_REQUEST['token'], false)) { $denied = false; } } elseif (is_object(icms::$user) && icms::$user->isAdmin()) { $denied = false; } if ($denied) { icms_core_Message::error(_NOPERM); exit(); }
The “elseif” statement at lines 24-26 will check whether the user is currently authenticated and they have administrator privileges, if so it will grant access to the script functionalities. While the “if” statement at lines 20-23 will do the same by solely checking the provided security token, without verifying whether the user is currently authenticated or not. This means that if an attacker provides a valid security token, then they will get unauthorized access to the script. Such security tokens will be generated in several places within the application – just grep the code searching the string icms::$security->getTokenHTML()
– and some of them do not require the user to be authenticated, like the misc.php script, here at line 181.
Moving forward to some lines later we can see the following:
$total = $user_handler->getUserCountByGroupLink(@$_POST["groups"], $criteria); $validsort = array("uname", "email", "last_login", "user_regdate", "posts"); $sort = (!in_array($_POST['user_sort'], $validsort)) ? "uname" : $_POST['user_sort']; $order = "ASC"; if (isset($_POST['user_order']) && $_POST['user_order'] == "DESC") { $order = "DESC"; } $criteria->setSort($sort); $criteria->setOrder($order); $criteria->setLimit($limit); $criteria->setStart($start); $foundusers = $user_handler->getUsersByGroupLink(@$_POST["groups"], $criteria, TRUE);
At lines 281 and 294 the “groups” POST parameter is being used in a call to the getUserCountByGroupLink()
and getUsersByGroupLink()
methods from the icms_member_Handler
class, and both of them use the first argument to construct an SQL query without proper validation (assuming it is an array of integers), as shown in the following code snippet:
public function getUserCountByGroupLink($groups, $criteria = null) { $ret = 0; $sql[] = " SELECT COUNT(DISTINCT u.uid) " . " FROM " . icms::$xoopsDB->prefix("users") . " AS u" . " LEFT JOIN " . icms::$xoopsDB->prefix("groups_users_link") . " AS m ON m.uid = u.uid" . " WHERE 1 = '1'"; if (! empty($groups)) { $sql[] = "m.groupid IN (" . implode(", ", $groups) . ")"; } if (isset($criteria) && is_subclass_of($criteria, 'icms_db_criteria_Element')) { $sql[] = $criteria->render(); } $sql_string = implode(" AND ", array_filter($sql)); if (! $result = icms::$xoopsDB->query($sql_string)) { return $ret; } list($ret) = icms::$xoopsDB->fetchRow($result); return $ret; }
To sum up, a remote unauthenticated attacker might be able to manipulate the executed SQL queries, and this could be exploited to e.g. read sensitive data from the “users” database table through boolean-based SQL Injection attacks, without the knowledge of the tables prefix (which is randomly generated during the installation). This is possible by injecting a payload like this:
1) AND ORD(SUBSTR(u.pass,1,1)) = XX #
At a first glance, this seems to be a quite limited vulnerability: first of all, users’ passwords are hashed with “salting”, so they cannot be cracked without first disclosing the salt; another option would be leaking the admin’s email address and attack the password reset mechanism, but this won’t do the trick, because a random password will be generated and emailed to the user… So, here comes the question: is it possible to leverage these vulnerabilities to login into ImpressCMS as admin and escalate the attack to an RCE? And the answer is: yesss! However, we have to deal with the Protector module…
In a nutshell, without going deeper into the details, the anti SQL Injection measures provided by the Protector module check for suspicious strings within the request parameters, such as select
, concat
, or information_schema
, and if they are found then the request will be blocked and the event will be logged. As such, we can’t use something like UNION SELECT...
to complete the query and fetch data from an arbitrary table. On the other hand, ImpressCMS uses PDO as a database driver, which allows for stacked SQL queries separated by a semicolon. So, we can inject something like this:
0); INSERT INTO i36fd6f18_users (uname, pass) VALUES (0×65676978, 0×32333964…) #
This will create a new record into the “users” database table, allowing an attacker to login as an ImpressCMS administrator, which would mean game over! However, in order to do that, the attacker should first guess the database tables prefix… And this could be achieved again with a boolean-based SQL Injection attack, by injecting something like the following:
1) AND ORD(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema=’impresscms’ AND table_name LIKE ‘%users’), 1, 1)) = XX #
Unfortunately, this one will get blocked by the Protector module because it contains suspicious SQL strings, so we need to find another way… And here it comes: since stacked queries are allowed, an attacker might be able to bypass the Protector module by assigning to a variable the hex representation of the query they want to execute (by using SET), and then use the PREPARE and EXECUTE MySQL statements to ultimately execute the query. This means we should inject something like this:
0);
SET @q = 0x53454c45435420534c454550283129;
PREPARE stmt FROM @q;
EXECUTE stmt; #
This one will not be catched by the Protector module, because the “suspicious strings” are hex-encoded! At this point we have all the pieces to put together the puzzle, and here are all the steps to get from unauthenticated SQL injection to RCE:
- Retrieve a valid security token from
/misc.php?action=showpopups&type=friend
- Use the token to get unauthorized access to
/include/findusers.php
- Exploit the SQL injection in a boolean-based fashion to fetch the database name
- Exploit the SQL injection in a time-based fashion to fetch the tables prefix (by using the trick to bypass Protector)
- Exploit the SQL injection to create a new admin user
- Login as admin and abuse the “Auto Tasks” feature to execute arbitrary PHP code
Here you can find a full working Proof of Concept (PoC) script which reproduces the above steps. It’s a PHP script supposed to be used from the command line (CLI), and you should see an output like the following:
I think it’s very hilarious and ironic that Prepared Statements, which are generally intended as a protection against SQL injection vulnerabilities, can also be abused to bypass a security mechanism designed to prevent SQL injection attacks! Furthermore, I have a feeling that this SQL injection exploitation technique can be used to bypass most Web Application Firewalls (WAF) out there, and this makes me think about a lesson I learnt some time ago: application security is a process, not a product!
Probably WAF vendors will point the finger at me, but I truly believe that too often the concept of “application security” is being confused with the “network security” one, and people think they are safe just because they have a firewall: ok, you can also implement security protections at the application level, like a WAF, and by doing that the overall application security could be definitely increased. On the other hand, I believe these solutions shouldn’t be considered bullet proof, as they can’t completely save your ass if you also have security bugs in your code, and this ImpressCMS case is a clear example of that.
READ MORE HERE