Joomla SQL Injection Vulnerability Exploit Results in Full Administrative Access
Trustwave SpiderLabs researcher Asaf Orpani has discovered an SQL injection vulnerability in versions 3.2 through 3.4.4 of Joomla, a popular open-source Content Management System (CMS). Joomla had a 6.6 percent share of the market for website CMSs as of October 20, 2015 according to W3Techs—second only to WordPress. Internet services company BuiltWith estimates that as many as 2.8 million websites worldwide use Joomla.
CVE-2015-7297, CVE-2015-7857, and CVE-2015-7858 cover the SQL injection vulnerability and various mutations related to it.
CVE-2015-7857 enables an unauthorized remote user to gain administrator privileges by hijacking the administrator session.
Following exploitation of the vulnerability, the attacker may gain full control of the web site and execute additional attacks :
- The vulnerability can be exploited in Joomla versions 3.2 (released in November 2013) through version 3.4.4.
- Because the vulnerability is found in a core module that doesn’t require any extensions, all websites that use Joomla versions 3.2 and above are vulnerable.
- Asaf also uncovered the related vulnerabilities CVE-2015-7858 and CVE-2015-7297 as part of his research.
Trustwave SpiderLabs recommends that ALL Joomla users update their Joomla installations to version 3.4.5. Version 3.4.5 is dedicated to fixing this security issue and expected to be released Thursday, October 22 at approximately 14:00 UTC.
Notes For Trustwave Customers and ModSecurity Users :
- Trustwave WAF’s “Blind SQL Injection” rule detects this attack provided the rule is enabled. A dedicated, fine-tuned rule will be issued in an upcoming signature update (CorSigs 4.39).
- If it’s enabled, ModSecurity Commercial Rule Set rule 95007 blocks attempts to exploit this vulnerability. For Commercial Rule Set customers, a virtual patch will also be issued soon.
Technical Overview :
We discovered that the following code in /administrator /components /com_contenthistory/ models/history.php is vulnerable to SQL injection :
The Road to the Joomla Admin Panel
Joomla is written in PHP and uses object-oriented programming (OOP) techniques and software design patterns. It stores data in a MySQL, MS SQL, or PostgreSQL database. Joomla’s features include page caching, RSS feeds, printable versions of pages, news flashes, blogs, search and support for language internationalization. Joomla is also used for e-commerce via a popular shopping cart template. Virtuemart.com is an e-commerce solution built on Joomla. Because the vulnerability is located in Joomla’s core module, e-commerce sites using VirtueMart are also vulnerable to exploit.
The code vulnerable to SQL injection is found in /administrator/components/com_
The class ContenthistoryModelHistory inherits from JModelList, which is the parent class for all types of models that handle lists of items. Components will usually use different models to serve the specific functioning of the component within the application. This specific one states what it does in its declaration below :
In other words, as a guest I can access this even though it requires a file from JPATH_COMPONENT_ADMINISTRATOR, obviously an item in the admin folder. Any access to the ‘admin zone’ will always draw a hacker’s attention.
Below you see the execute function’s code :
And here’s that function :
So after supplying the component(‘option’) and view parameters in the URL as described in the POC, we’ve got our hands on the vulnerable model. Since there was arequire_once call to the admins folder where the model is found, it’s easy to guide Joomla in creating the desired model, and that model has now been setModel-ed into the configuration.
Now that I can control the model the following question arises, can I also force the execution of the vulnerable code with which we started?
From personal experience and after researching Joomla, I understood something regarding models in Joomla.
Here’s the vulnerable code again :
Now that we understand why that function is executed, there is still something that needs explaining. The query is being ‘made’, and the highlighted area (where we inject SQL) is at the ‘SELECT’ part of the SQL query. Common sense tells us that this SQL query will be executed later—and so it does when testing the exploit on a test site of Joomla on a local computer.
But, what is the expression $this->getState(‘list.select’,…)? And how do we control it? After all, this is a potential place in which to inject the SQL query.
First of all, note that the second long parameter in the getState() function is just a ‘default value’ parameter. This means that if no value is given in ‘list.select’ then this will be the value.
Here you see the getState() function implementation :
An interesting function populateState() draws my attention because of its name and the fact that it’s only called the first time getState() is executed.
And here’s that populateState() function :
There’s still hope! A call for the parent function of populateState is seen here :
It does some filtering and handling for the keys fullordering, ordering and limit. Those three keys do not relate to our case at all. However, looking at the default case, we can see how simple our attack is going to be! We find that $value is kept as it was as if it’s none of the above switch{} cases (fullordering, ordering or limit), and then it sets the state (setState()) of ‘list.select’ to the value we passed, which means our input is left untouched! And all we’ve got to do is simply pass the list array in the request with a key name of ‘select’ and the SQL expression we want to inject!
Bravo! We executed a successful SQL injection attack in one of the most-popular web CMS applications out there—Joomla! Now onto doing something interesting with that…
SQL INJECTION Proof of Concept :
The road from our SQL injection to “game over” is very short.
Since our SQL injection exploits a SELECT type of query we will only be able to extract data from the database and not insert any data, which is limiting. After all, not all of the data in the database is interesting. In the users table the password is hashed, and since MD5 can’t really be reverted we’re kind of stuck.
Then, an interesting table in Joomla caught my eye :
There is also this table :
The number eight, for example, represents a Super User, as shown in this table in the Joomla Administration Panel :
(GET index.php?option=com_
We can’t really work like that! We need to know what full query is being executed. In order to know how to work with it, we’ll debug our vulnerable code and see what query is generated.
This resulted in the following :
The query is multi-lined, meaning that we can’t just comment it all out. All we can comment is everything after the ‘SELECT’ word at the first line. Then we need to figure out how to address the other 4 lines.
Okay, so that gives us the following issues we have to deal with :
- The query will only pull data from the table jml_ucm_history (meh…)
- We have to learn how to get rid of that “ORDER BY Array” or how to make the query according to that.
- The query will execute ONLY if there will be a row in the database at jml_ucm_history where `ucm_item_id` will be equal to 0 AND `ucm_type_id` will be equal to 0.
These seem like some tough and annoying conditions, but we soon find that we can overcome them easily.
If you have some experience with SQL injection, you can already solve the first issue. After thinking about it I decided upon an Error-based SQLi! Since we saw that Joomla prints SQL errors on their page, an error-based SQLi will be successful here. We can run multiple queries to cause an error that will extract data from the database.
Alright now, about that last line “ORDER BY Array”, we have previously gotten an error “Unknown column Array”, well, that’s the line giving us trouble. Researching the code a little bit more and following the build of the query, I figured out that to address this line you should give another input in the URL which will be a part of our ‘list’ array. It islist[ordering]= (equals nothing). Setting it equal to nothing will put an order by command as default that will not interrupt our error-based SQLi session. That takes care of the second issue.
For last obstacle, the ucm_item_id and ucm_type_id, well, what is it?
I checked out the jml_ucm_history table to see :
Let’s not forget purpose of the component in which we found the injection. It’s a content management history component after all—every piece of content that managed or modified in some way is recorded here.
Obviously we would have to supply the modified content’s ID and type because Joomla needs to know on what content to pull out information. If one of those conditions are false the query will not execute. I found out it’s not hard at all to supply correct IDs—it involves some guesswork, but the range is very small (although there is a better solution within the website itself, which we’ll cover in a bit).
- ucm_type_id relates to the item’s content type’s id, content types usually range from 1-20+ identifying the type of content (e.g., article, gallery, video, etc).
- ucm_item_id relates to the item’s id. For example, a new article is assigned an item id. The range for item ids is small as well because every article you create has its item id incremented from the last content item. The numbering always starts from one, which can be seen from a normal Joomla installation’s populated database, or an empty Joomla installation after creating an article or piece of content.
After researching the code flow for building the query a little more, we find that to supply both parameters we need to pass the following parameters in the request :
- type_id – The item’s content type ID
- item_id – The item’s ID
Accessing the content component of the Joomla-based website (?option=com_content), would usually give you a list of all contents available on the site. If it doesn’t, you can simply look for an article or some gallery found on the website and click on it.
Then you’d see you’re visiting a URL that looks something like this :
That solves third and last issue. We know it’s not so hard to reach all requirements for the SQL query to work, and we can easily understand by the website’s behavior if we’re on the right path to full website compromise.
Now, let’s pull out the first row from the session table just to test using the error-based SQLi discussed earlier.
After some attempts to get the query to run we might see this :
GET index.php?option=com_contenthistory&view=history&list[ordering]=&item_id=75&type_id=1 &list[select]= (select 1 FROM(select count(*),concat((select (select concat(session_id)) FROM jml_session LIMIT 0,1),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a)
The SQL query that will execute would look like this :
And that’s it—we’ve compromised the website!
The opinions expressed in this post belongs to the individual contributors and do not necessarily reflect the views of Information Security Buzz.