Recently I was going through a number of Bug Bounty programs looking for one particular weakness. The weakness I was focusing on is called Open Redirect (or Insecure Redirect). The most common form of this is where a user tries to load a page which requires them to be logged in. On many websites this results in the user being redirected to the login page and a query string parameter being added to the Url so that they can be sent back to the page they requested after they log in. The login Url may look like this:
A common tactic in phishing is to abuse this functionality by setting the parameter to be an external website hosted by the attacker which looks like the primary website. A user would load the Url, any initial wariness is alleviated by seeing they are on the valid Url. After logging in they do not see the redirect has taken them to a malicious website and then continue to give away sensitive information.
To neutralise this threat, typically a website will have an allow list which it checks against and only performs the redirect if it is one of those cases. If a website doesn’t do this then it is likely vulnerable to this type of attack.
In one particular bug bounty program, they had put in multiple measures to prevent XSS. I am going to share how through trial and error, I bypassed the filters and demonstrate how careful you need to be when coding to prevent these types of attack.
I am not able to detail the specific website due to strict confidentiality agreements, but will provide information on what I did. The website had a number of shops on different domains which funneled through a single checkout process. When you visited the checkout, your original website address was passed as a query string and that information was used to populate a “back” link within the page.
I identified that the anchor tag of the back link had an onclick event set to:
The fact there appeared to be an arbitrary deny list being used gave me hope that there would be a way to bypass it. Two key things that lead me to be able to abuse the open redirect were using char code HTML entities (e.g. () and a workaround for accessing the document.
I will skip ahead and show the final payload which resulted in the checkout process being replaced with my own, then I will break it down.
The first thing to note is there is a lot of URL Encoding. Stripping that way you can see a little bit more clearly what is happening.
If we translate the char code HTML entities we get the following
var f=new Function("return document")
This would be blocked though because using the word document in the Url results in a 403 response. Using the String.fromChar() function I was able to swap the denied “document” keyword and create it with individual char codes instead:
var f=new Function("return "+String.fromCharCode(100,111,99,117,109,101,110,116))
The attack was complete and I was able to substitute the checkout process with my own, which captured the users personal information and credit card information. The bug was triaged, rated Medium risk and is in the process of being fixed.
The act of abusing an Open Redirect vulnerability in itself isn’t particularly interesting or complicated. But in this instance, I found the journey of tying to get around the restrictions that had been put in place a fun challenge which also reminded me how careful you have to be to prevent unwanted input. It showed that like most things in security, you should only rely on allow lists and not deny lists, as ultimately you cannot be aware of every malicious approach that an attacker can take.