# Hack The Box - CTF

## Quick Summary

Hey guys today CTF retired and here’s my write-up about it. CTF was a very cool box, it had an ldap injection vulnerability which I have never seen on another box before, and the way of exploiting that vulnerability to gain access was great. A really unique box, I had fun solving it and I hope you have fun too reading my write-up. It’s a Linux box and its ip is 10.10.10.122, I added it to /etc/hosts as ctf.htb. Let’s jump right in !

## Nmap

As always we will start with nmap to scan for open ports and services :
nmap -sV -sT -sC ctf.htb

Only http on port 80 and ssh on port 22

## HTTP Initial Enumeration

http://ctf.htb

It’s pretty straightforward that we will get banned for 5 minutes if we tried to bruteforce anything, like sub directories for example. It’s also saying that they handle authentication with tokens, There’s a login page so let’s take a look at it.

We need a username and an OTP (one-time password). An OTP is time limited which means that even if we could get a valid one it will give us access only once because it expires in a short time (usually 60 seconds). So we need to gain access to a place that generates valid OTPs or to be able to generate valid OTPs ourselves. Let’s take a look at the source code of the login page, maybe something is there :

So now we know that they are using a token to generate the OTPs, we also know that the length of the token is 81 digits. But I still couldn’t figure out that part about the attribute they’re using to store the token. I decided to bruteforce the username then return to the OTP thing again.
I know what you’re thinking, how will I bruteforce the username without getting banned ? Well I tried to pass any credentials to see how will the application respond :

Here I noticed 2 things. First thing is that it’s actually telling us if the user exists or not, which means that we can enumerate users. The other thing is that it responded normally with a 200 OK response, so If the server identifies a bruteforce attack by monitoring how many times an ip causes errors like 404 for example, as long as we are not causing errors we won’t get banned. I gave it a try to see if it will actually work. I used wfuzz and multiplesources-users-fabian-fingerle.de.txt from seclists :

All “user not found” responses have 233 words so I filtered them :

It worked and I didn’t get banned, after some time I got a result which was ldapuser, that’s weird. I also noticed that payloads that had special characters in them caused different response length. I tried ldapuser to see what’s the other message :

Then I tried !@#%^&* and I got nothing, I just got the login page back again without any messages. I figured out that the username is being used in an ldap query, and it’s injectable (because of the special chars payloads). Also that existing attribute where the token is stored is an ldap attribute. With the injection we have we can extract the token and use it to generate valid OTPs. But because the injection is blind it will be kinda tricky to extract the token.

## LDAP Injection

As I said, it’s a blind injection which means that we won’t get any results. But a payload like this : *)(uid=*))(|(uid=* should result in “Cannot login”. However when I tried it I didn’t get any message, So I tried to URL encode the payload and it worked. So the injection works when the payload is double URL encoded (I only encoded the payload once because the browser automatically encodes POST data). I switched to burp, here are the results :
Request :

Response :

Now we need to know which attribute the token is stored in. We know it’s an existing attribute so we just need to choose the right one. I checked ldap attributes and chose some of them to test (comment, pager and info), the payload will be like this : *)(uid=*))(|(ATTRIBUTE=* (instead of the second uid attribute we will use the attribute we are testing). We also know that the token is numeric so we can remove * and replace it with numeric values from 0 to 9 and monitor the responses (I used burp intruder to do this). So the final payload will be like this : *)(uid=*))(|(ATTRIBUTE=N. After some testing this payload with the attribute pager and value of 2 : *)(uid=*))(|(pager=2 resulted in “Cannot login” message. Great so our payload will be *)(uid=*))(|(pager=2N and we will bruteforce the second number again until we get “Cannot login”. We will keep repeating this until we reach the 81st number, but doing this manually is lame and boring so I wrote a python script.

## Exploitation, Token Extraction

I created 3 functions :

• send_payload (to send the injection payload and receive the response)
• check_response (to check whether the response contains “Cannot login” or not)
• exploit : This function creates a list of numbers from 0 to 9, then by looping through that list it creates the payload which is : %2A%29%28uid%3D%2A%29%29%28%7C%28pager%3D + token + number + %2A (encoded only once because python requests automatically encodes POST data). Then it calls send_payload and check_response, if check_response returned True it adds the valid number to the token.

Then I wrote a while loop to keep calling exploit() as long as len(token) is not 81

extract_token.py :

It took some minutes to finish and now we have the token :

I installed stoken (apt-get install stoken), Then I imported the token :

I didn’t type any password I left it blank. We can either use the cli or the gui, for the cli you have to start stoken and enter the pin then you will get the OTP, and for another OTP you’ll need to start stoken again.

So I just used the gui as it’s better :

## RCE, User Flag

Let’s login and see what’s there :

%2a%29%28uid%3d%2a%29%29%28%7c%28uid%3d%2a is *)(uid=*))(|(uid=* url-encoded.
It redirected me to /page.php which I can use to execute commands :

I tried whoami and it worked fine:

I switched to burp to make things easier, I wanted to read /etc/passwd to know the users :
Request :

Response :

We can see that ldapuser is an actual user on the box, I tried to get a reverse shell but for some reason I couldn’t get a reverse shell at all so I started to look in the web files. I was already in the web directory :

I listed the files :

I started looking for any hardcoded credentials in the php files, in login.php I found credentials for ldapuser :

ldapuser : e398e27d5c4ad45086fe431120932a01
ssh :

We owned user.

## 7z List Files and Wildcards, Root Flag

Before enumerating anything I just checked the directories and stuff like that, in / I saw a directory called backup

It had a lot of archives, an error log and a script called honeypot.sh :

honeypot.sh :

Obviously this script runs from time to time to backup files, also it’s running as root. I didn’t check cronjobs or use pspy, it was obvious since it’s accessing /root/root.txt to create the password and only root can access that.
Basically one of the things that this script is doing is that it’s backing up all the files in /var/www/html/uploads by using 7za to put all the uploads in one archive. Let’s look at the command again :
7za a /backup/$filename.zip -t7z -snl -p$pass -- *
It’s using the wildcard asterisk (*) to get all files. This means that if we can write to /var/www/html/uploads our file will be included in the command. If we can create a malicious file name then we can somehow manipulate the 7za command.
It’s also using this option : -snl, I checked the manual page for 7za :

Note that we can’t unzip the created backups, so even if we created a symlink to root.txt in /var/www/html/uploads we won’t be able to read it because the archive is password protected. The only way to actually get anything is through the error log, we need to cause an error that somehow leaks the flag.
After searching for some time I found this page which talks about a feature in 7z called list files. I thought if I created 2 files, root.txt and @root.txt, root.txt is a symlink to /root/root.txt, when the command is executed and gets to @root.txt it will treat that as a list file option then it will search for root.txt to use it as a list file. However that file isn’t a real list file (that may cause an error), also it’s a symlink to /root/root.txt.
I went to /var/www/html/uploads and I didn’t even have read access.

so I went back to the RCE requests in burp and tried as apache.
I created a symlink to /root/root.txt as root.txt :

Then I created an empty file and called it @root.txt :

Let’s check the directory listing now :

Everything is fine let’s check the error log :

And we owned root !
That’s it , Feedback is appreciated !