URL Validation Bypass Using Browser URI Normalization

Marx Chryz Del Mundo
4 min readDec 4, 2022

Hello everyone, I am Marx Chryz and I do bug bounty hunting for about two years now. It’s also been three and a half years since I started doing web penetration testing.

Introduction

This is a bug I found on a website that has bug bounty program. It is an external program so I can’t disclose the name. The vulnerability is also not yet fixed as of the writing. In that sense, we’ll just call the target as redacted.com

Uniform Resource Identifier (URI) Normalization

Have you ever noticed that when we type HTTPS://GOOGLE.COM in the browser, it automatically converts to https://google.com (lowercase)?

That’s URI Normalization in action! URI Normalization is a process in which web browsers modify the URI to make it standardized and consistent [read more here].

So, URI Normalization converts uppercase to lowercase?

Yep, but that’s not all about URI normalization. URI normalization also converts a ton of other things which we can use to bypass URL validation. Here are some examples:

Figure 1: ⒼⓄⓄⒼⓁⒺ.com is treated as google.com
Figure 2: 𝓰𝓸𝓸𝓰𝓵𝓮.𝓬𝓸𝓶 is treated as google.com
Figure 3: google。com is treated as google.com
Figure 4: This cursed text lol is still treated as a valid URL, thanks to URI normalization

URI Normalization Process

The URI normalization shown above are examples in which the browser finds its compatibility equivalent. Here are some examples of compatibility equivalence [read more here].

Table 1: Compatibility Equivalence Examples

Here’s a quick way to check for the compatibility equivalence

Figure 5: This is the code and the output

Note that the 。 (full stop symbol show in Figure 3) is not handled by the String.normalize function in JS (I don’t know why)

Now that we have an insight into what URI normalization is, we can use this to bypass URL validation filters! For example, if the website doesn’t want us to input google.com, then we can use ⒼⓄⓄⒼⓁⒺ.com to bypass the validation.

Exploitation

The bug I found is a lot similar to this Hackerone report of zurke. With that, let’s begin.

  1. A POST parameter in the API accepts a “profilePicture” parameter. If I input a URL like “https://mydomain.com/1.jpg”, the response is “You entered invalid value”
  2. However, if I inputted a URL like “https://img.redacted.com/random.jpg”, the response tells me that the changes I made were successful.
  3. Based on those observations, I can infer that the backend uses some kind of whitelisting that only allows images from img.redacted.com to be inputted. This means that we need to bypass this whitelisting.
  4. I tried several payloads that use URI normalization including:
    - https://img.redacted.com。mydomain.com/1.jpg
    - https://img.redacted.com.ⓜⓨⓓⓞⓜⓐⓘⓝ.ⓒⓞⓜ/1.jpg
    however, none of the payloads worked because life is not a fairy tale.
  5. I remembered a common bypass for whitelisting, which is:
    - https://img.redacted.com@mydomain.com
    Even if this does not use URI normalization techniques that I discussed above, guess what? It still didn’t work.

6. I investigated further about URI normalization and stumbled upon something.

Figure 6: Usage of multiple @ symbols

7. I immediately tried to bypass the whitelisting filter by using the payload: https://img.redacted.com@@mydomain.com/1.jpg

It worked! I never knew this payload existed because I think I can’t found this anywhere in the internet. Even swisskyrepo’s PayloadAllTheThings doesn’t have this.

Report Timeline

Nov 18, 2022 — Bug Submitted
Nov 18, 2022 — Triaged
Dec 1, 2022 — Bounty Received

--

--