Solution for YesWeHack’s #8 DOJO Challenge

For the latest addition to YesWeHack’s Dojo series, we’re faced with the challenge of fetching the secret that EvilCorp2.0 is storing on the /secret endpoint. To obtain this secret, we need to find a way through the security mechanisms that EvilCorp2.0 has put in place for their small app. Their app is created using Deno and it allows us to specify a URL as a string, which the server will fetch for us if it passes the security mechanisms.

The security mechanisms are implemented as a function called waf. The function takes a parameter of type 'string' called str. The function starts by creating a URL object named url by using the user-provided string:

The rest of the function implements 5 security mechanisms which do the following:

1 - Skips all security checks if str (not the URL object) is equal to http://127.0.0.1:5000/ping

2 - Makes sure that the port from url.port is 5000

3 - Makes sure that url.hostname is not 127.0.0.1 or localhost

4 - Makes sure that url.pathname starts with /ping

5 - Makes sure that str doesn't not contain the substring /ping<ANY_CHARS>/.
The following would eg. not pass: http://example.com/smth/pingblabla/hihi

Time to exploit

We now have a basic understanding of the app, so let’s exploit it! Let’s start by providing the string http://127.0.0.1:5000/ping which we know will pass the security mechanisms because of check 1 in waf

Cool, we got a ‘pong’ from the /ping endpoint. Let's now bypass some of the security mechanisms! There doesn't seem to be a reason we would want to bypass the 'port' check since we assume that the /secret endpoint exists on port 5000, so let's move on to check 3. Hmm, this seems to be a pretty insufficient way to avoid requests to localhost. While we can't use 127.0.0.1 or localhost, there’s plenty of other options out there that will resolve to localhost. Simply ‘0’ would often do! Let’s try using the payload http://0:5000/ping to see if we can bypass this check and receive a 'pong':

Yup, it worked! Nice, let’s move on then. Check 4 ensures that the pathname property of the url object starts with the value /ping. This seems pretty hard to work around, but there's one important thing to notice in the app's logic! Notice that the url object only exists within the scope of the waf function, and that it's actually the string, which is also named url (confusing, huh?) that's provided as a parameter to fetch after the waf function is passed.

So why care about this? Well, wouldn’t it be nice if there was some inconsistency between the URL object in Deno and the user-provided string! What if eg. the pathname property of the URL object didn’t match the actual path being fetched when sending a request to the user-provided string? In other words: Can we craft a payload, which when used to create a URL object, causes some unexpected behavior?

Try, fail, try again

Let’s create a test environment to test for inconsistencies in the Deno URL object. To do so, we can simply use the Dojo Playground!

We now have a simple app that logs the value of url.pathname for us. Time to test! Maybe some simple path traversal trick will do the job?

Hmm, sadly no. But maybe we could try and do some URL-encoding? But first, a quick example. Visting a URL like: https://example.com/ping/%2e%2e%2fhihi from most browsers would result in the following:

As we can see, we didn’t end up on /hihi but /ping/..%2fhihi instead. But the notice how the period chars were decoded... What would happen if we instead visited https://example.com/ping/%2e%2e/hihi? You guessed it:

The path was resolved to /hihi. Let's try to apply this knowledge to our Deno test set-up and use the payload: http://0:5000/ping/%2e%2e/secret:

Would you look at that! Deno didn’t seem to care about our URL encoded period chars, and tells us that the pathname is simply /ping/%2e%2e/secret. But what will happen when we fetch this payload..?

Let’s head back to the challenge and find out!

Oh, we forgot about the last check… We can’t use any / chars after the string /ping. But could we maybe just use something else than /? Well, how about some nice \’s! Let's try with the payload http://0:5000/ping\%2e%2e\secret:

Voilà!🎉 We solved the challenge and retrieved the secret: flag{You just found a secret !}

Thanks for reading along and happy hacking!

@holme_sec

Bug Bounty Hunter