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
.