Skip to main content
  1. Writeups/

YesWeHack: Dojo 36

777 words·4 mins·
YESWEHACK WEB
Fayred
Author
Fayred
I’m French, and I’m passionate about computers in general, and computer security in particular. A CTF enthusiast and a Bug Bounty novice, I’m primarily interested in learning, having fun, and sharing what I’ve learned.
Table of Contents

Statement
#

A friend of yours has created a web application that allows you to check the availability of your locally hosted services. He assured you that it is secure and even allowed you to run it as a test user!

Prove him wrong by reading the flag.txt file on the server.

~ The flag can be found in the file: /tmp/flag.txt

Authors: Owne, Brumens

Overview
#

Initially, an application with accessible source code was identified. It takes two inputs: cmd and token. The cmd value is supposed to correspond to the IP or domain name of the machine to ping.

Solution
#

To solve this dojo, we need to gain dev access, then use the dev access for RCE. Once we achieve RCE, we simply need to read the file located at /tmp/flag.txt.

SQL-Like Injection
#

The first step before exploiting command injection is to ensure the PreProd_Sanitize() method is used instead of the Prod_Sanitize() method. Indeed, the Prod_Sanitize() method properly sanitizes the cmd input, making command injection impossible. To ensure cmd is sanitized by the desired method, we need to authenticate as the dev user, as indicated in the source code:

def Run(self):
        if self.user == "dev":
            cmd_sanitize = self.PreProd_Sanitize(self.command)
        else:
            cmd_sanitize = self.Prod_Sanitize(self.command)

        # At the moment we don't have internet access.
        # We should only ping localhost to avoid server timeout
        result = subprocess.run(["/bin/ash", "-c", f"ping -c 1 {cmd_sanitize}"], capture_output=True, text=True)
        if result.returncode == 0:
            return result.stdout
        else:
            return result.stderr

Looking at what user corresponds to, we see that it matches the first value returned by an SQL query:

# Get user that holds the given token
r = cursor.execute('SELECT username FROM users WHERE token LIKE ?', (token,))
try:
    user = r.fetchone()[0]
except:
    user = "test"

command = Command(cmd, user)

This query retrieves users who have the token provided as input. For the user to match dev, we would normally need the token belonging to them. However, since the LIKE operator is used to verify the token, we can retrieve a user with a token that matches a pattern like _ %. The _ symbol can be replaced by any single character, while the % symbol can be replaced by an unlimited number of characters. Combined, they match any token.

If the first token stored in the database belongs to dev, we will achieve our goal:

Now that we are dev, we can proceed to command injection.

OS Command Injection
#

In this section, we will see how to make a seemingly useless command injection useful. Once we are dev, the program uses a different method to sanitize cmd:

def PreProd_Sanitize(self, s:str) -> str:
        """My homemade secure sanitize function"""
        if not s:
            return "''"
        if re.search(r'[a-zA-Z_*^@%+=:,./-]', s) is None:
            return s
        return "'" + s.replace("'", "'\"'\"'") + "'" 

This method sanitizes all inputs that match the regex: r'[a-zA-Z_*^@%+=:,./-]'. Unfortunately, we cannot escape single quotes as one might think (at least I couldn’t find a way). However, we can attempt to read the flag.txt file without matching the regex. To do this, we need to be creative. To read the desired file, we must do so without using alphabetic characters, which greatly complicates the task. First, we can chain command execution using the ; character, which is not present in the regex. To make the ping valid and avoid an unreadable terminal, we can use a payload like 0;. Pinging 0 corresponds to pinging localhost, then we separate the ping command from our future command to read the flag.txt file with ;. Next, we need to call /bin/ash without using letters or slashes. We can do this with $0 since it corresponds to the first argument here, /bin/ash. Then, we add a character that does not match the regex to avoid a timeout. We now have the payload: 0;$0 1, which we test:

We can see that $0 indeed corresponds to /bin/ash as indicated earlier. Next, to achieve something like /bin/ash flag.txt, we need to instruct /bin/ash to execute the file, still without matching the regex. For this, we can use the question mark. In the context of file matching, ? is a wildcard that represents exactly one character. It can be used to search for or manipulate files or strings with a character at a specific position, regardless of what it is. We then simply add as many ? as there are characters in flag.txt, and we’re done! The final payload is now 0;$0 ????????.

Exploitation
#

Result:

Flag: FLAG{W3lc0me_T0_Th3_Oth3r_S1de!}

Bonus: It was also possible to execute commands by converting characters to octal. For example, with ; $'\143\141\164' $'\146\154\141\147\056\164\170\164', which corresponds to ; cat flag.txt.

Reference
#

Related

Patriot CTF 2024: Secret Door
803 words·4 mins
PATRIOT CTF WEB MEDIUM
Patriot CTF 2024: DogDay
699 words·4 mins
PATRIOT CTF WEB CRYPTO MEDIUM
L4ugh CTF 2024: Micro
417 words·2 mins
L4UGH CTF WEB EZ
RSA for Dummies (like me)
538 words·3 mins
CRYPTO NOOB