2 ماه قبل

2 دیدگاه

Ulysse Carion

Ruby-SAML در معرض حملات XML signature wrapping قرار گرفت.

این مقاله به بررسی آسیب‌پذیری جدید CVE-2024-45409 می‌پردازد که از نوع حملات XML signature wrapping است و پیاده‌سازی اصلی SAML در Ruby را تحت تأثیر قرار داده است. این نقص امنیتی به مهاجمان اجازه می‌دهد تا به‌عنوان هر کاربر دلخواهی وارد سیستم شوند. همچنین، به تأثیرات این حمله بر پلتفرم‌های بزرگی مانند GitLab و دلایل تکرار این مشکل پرداخته و راهکارهایی برای مقابله با آن ارائه می‌شود.

در دنیای امروز که ارتباطات دیجیتال و تراکنش‌های آنلاین بخش جدایی‌ناپذیر زندگی ما شده‌اند، امنیت اطلاعات از اهمیت ویژه‌ای برخوردار است. یکی از فناوری‌هایی که در این زمینه به‌کار می‌رود، SAML (Security Assertion Markup Language) است که نقش مهمی در احراز هویت کاربران دارد. اما حتی این فناوری پیشرفته هم گاهی در برابر آسیب‌پذیری‌های امنیتی ایمن نیست. یکی از این آسیب‌پذیری‌ها، حملات XML signature wrapping است که به تازگی با شناسه CVE-2024-45409 شناسایی شده و تأثیرات گسترده‌ای بر اکوسیستم Ruby و پلتفرم‌های بزرگی مثل GitLab داشته است. در این مقاله، به بررسی این آسیب‌پذیری، دلایل تکرار آن و راه‌حل‌های موجود برای مقابله با این نوع حمله می‌پردازیم.

GitLab و دیگران تحت تأثیر قرار گرفته‌اند. مشکل اصلی در خود مشخصات SAML و مهندسانی است که بدون دقت کافی آن را پیاده‌سازی کرده‌اند.

CVE-2024-45409 در تاریخ ۱۰ سپتامبر ۲۰۲۴ منتشر شد. این آسیب‌پذیری یک حمله دیگر از نوع XML signature wrapping است که این بار پیاده‌سازی اصلی SAML در Ruby را تحت تأثیر قرار داده است. این نقص امنیتی به مهاجم امکان می‌دهد به‌عنوان هر کاربری که در سیستم آسیب‌دیده وجود دارد، وارد شود.

این نوع حمله بارها تکرار شده و هر بار بخش‌های وسیعی از اینترنت را تحت تأثیر قرار داده است. این بار، GitLab و بخش بزرگی از اکوسیستم Ruby دچار مشکل شده‌اند.

در ادامه به بررسی این مشکل، دلایل تکرار آن و راهکارهایی که می‌توان برای مقابله با آن به‌کار برد، می‌پردازیم.

XML Signature Wrapping

امضاهای XML در دهه ۲۰۰۰، همان پاسخی بودند که امروز JWTها ارائه می‌دهند. در سال ۲۰۲۴، JWTها به‌طور گسترده برای این استفاده می‌شوند که “چطور داده‌ها را امضا کرده و از طریق اینترنت ارسال کنیم”. این استاندارد شاید بی‌نقص نباشد، اما قابل استفاده است.

امضاهای XML هم دقیقاً همین کار را انجام می‌دهند، با این تفاوت که هر مرحله از آن بسیار پیچیده‌تر است.

امضای XML تنها این امکان را به شما می‌دهد که یک سند XML را به‌صورت رمزنگاری‌شده امضا کنید. همان کاری که JWTها با "alg:"RS256 انجام می‌دهند (نه ES256، چون به یاد داشته باشید: سال ۲۰۰۰ است).

  • برای امضای رمزنگاری‌شده داده‌ها، تنها یک روش منطقی وجود دارد:
  1. پیام خود را گرفته و آن را به بایت تبدیل می‌کنید.
  2. بایت‌ها را امضا می‌کنید که باعث تولید بایت‌های دیگری می‌شود.
  3. دو چیز را ارسال می‌کنید: بایت‌های پیام از مرحله (۱) و بایت‌های امضا از مرحله (۲).
  4. به هیچ وجه نباید خلاقیت به خرج دهید!

مراحل ۱ تا ۳ همان کاری است که JWT انجام می‌دهد. مرحله (۳) را با جدا کردن پیام و امضا توسط یک نقطه (.) انجام می‌دهد، که این روش کار می‌کند، چون JWT همچنین پیام و امضا را به base64 تبدیل می‌کند و نقطه (.) در base64 ظاهر نمی‌شود. مردم از JWT انتقاد می‌کنند، چون مرحله (۴) را نادیده گرفته‌اند؛ این بخشی از تلاشی گسترده‌تر برای استانداردسازی همه‌ی رمزنگاری‌ها تحت چیزی به نام JOSE COSE) است، که ایده خوبی نیست. اما در کل، JWTها خوب کار می‌کنند. قابل استفاده هستند و عمدتاً می‌توانید از زیاده‌روی‌های نویسندگان چشم‌پوشی کنید.

امضاهای XML رویکرد متفاوتی دارند. در این روش:

  • به شما اجازه می‌دهند بخش‌هایی از یک پیام را امضا کنید،
  • یا حتی هیچ بخشی از پیام را امضا نکنید،
  • چون به‌جای ارسال امضا همراه با پیام، خود پیام را ویرایش کرده و عناصری مثل <ds:Signature> را به آن اضافه می‌کنید،
  • هر کدام از این امضاها، بخشی متفاوت از پیام را امضا می‌کنند. عنصر dsاز یک URI استفاده می‌کند تا به بخش‌های دیگر پیام اشاره کند و بگوید: “این امضا مربوط به آن بخش از پیام است.”

اگر قسمت XML بودن آن را نادیده بگیرید، معادل این است که بخواهید {“email”: “bob@company.com“} را امضا کنید و آن را به این شکل تغییر دهید:

{
   "email": "bob@company.com", 
   "__sig": {
      "uri": "/email", 
      "sig": "... signature for the string bob@company.com ..."
   }
}

ولی این اصلا ایده خوبی نیست. این روش نیاز به یک مرحله “کشف امضا” ایجاد می‌کند، جایی که باید در کل دنبال سند امضاها بگردید (در مثال JSON من، __sig و در امضاهای XML، عناصر <ds:Signature>). این مورد اساساً از مهندسان می‌خواهد که این کار را انجام دهند:

def validate_xml(xml_document):
    for signature in find_xml_signature_elements(xml_document):
        element = resolve_uri(signature["SignedInfo"]["Reference"]["URI"], xml_document)
        verify_signature(element, signature["SignatureValue"]

نکته: توجه داشته باشید که اگر سند هیچ امضایی نداشته باشد، این کد بالا عملاً کاری انجام نمی‌دهد.

بسیاری از باگ‌ها مربوط به همین مرحله “کشف امضا” هستند. هر یک از این باگ‌ها، چیزی که به آن “حمله XML Signature Wrapping” می‌گویند، تبدیل می‌شوند؛ یعنی کسی یک XML پیچیده نوشته که باعث می‌شود، کد شما نتواند تشخیص دهد که واقعاً چه چیزی امضا شده است.

رای پاسخ به پیشنهاد واضح: متأسفانه، شما نمی‌توانید به سادگی بررسی کنید که آیا سند اصلی XML امضا شده است یا خیر. چون این روشی نیست که SAML از آن استفاده می‌کند (در ادامه بیشتر توضیح می‌دهم)، و SAML تنها دلیلی است که مردم از امضاهای XML استفاده می‌کنند (باز هم در ادامه بیشتر به این موضوع می‌پردازم).

در عمل، به جای این روش، مردم معمولاً کد قبلی خود را به‌گونه‌ای تطبیق می‌دهند که مطمئن شوند بخشی از پیام که برایشان اهمیت دارد، واقعاً امضا شده است:

def validate_xml(xml_document, uri_checklist):
    checked_uris = []
    for signature in find_xml_signature_elements(xml_document):
        element = resolve_uri(signature["SignedInfo"]["Reference"]["URI"], xml_document)
        verify_signature(element, signature["SignatureValue"]
        checked_uris.append(signature["SignedInfo"]["Reference"]["URI"])
    
    for uri in uri_checklist:
        if uri not in checked_uris:
            throw MissingSignatureError()

راه‌های زیادی وجود دارد که این موضوع می‌تواند به مشکل بر بخورد، اما در اینجا مشکلی که ruby-saml با آن مواجه شد را توضیح می‌دهم: هیچ تضمینی وجود ندارد که URI که امضا به آن اشاره می‌کند، منحصر به فرد باشد.

بنابراین، کاری که می‌توانید انجام دهید، این است که یک پیام قانونی را گرفته و اطلاعات دیگری را به آن اضافه کنید، با استفاده مجدد از همان URI. سپس امیدوار باشید که کد قربانی شما دچار سردرگمی شود و نتواند تشخیص دهد که چه چیزی را امضا کرده است.

چیزی شبیه به این:

<Document>
    <!-- a faked message; this is never actually signed -->
    <ImportantStuff>
        <Message id="dead[...]beef">
            <Email>eve@evil.com</Email>
        </Message>
    </ImportantStuff>
    
    <!-- attacker-supplied message, copied out of the ImportantStuff from a legit message -->
    <Message id="dead[...]beef">
        <Email>alice@customer.com</Email>
    </Message>
    
    <!-- a signature for alice@customer.com -->
    <Signature>
        <SignedInfo>
            <Reference URI="dead[...]beef" />
        </SignedInfo>
        <SignatureValue>... the correct signature, but for alice@customer.com ...</SignatureValue>
    </Signature>
</Document>

کد قربانی امضا را پیدا کرده و URI را به پیام سطح بالا متصل می‌کند. اما بعداً ممکن است به‌جای پردازش پیام اصلی، بخش ImportantStuff را پردازش کند، به خصوص اگر پیام معمولاً در این بخش قرار داشته باشد.

SAML دلیل اهمیت این موضوع است

دیگر کسی به امضاهای XML توجهی نمی‌کند، نه اینکه در زمینه SAML باشد. SAML همان چیزی است که مردم به آن “احراز هویت تک‌امضایی سازمانی” می‌گویند، و به این صورت کار می‌کند، که یک ارائه‌دهنده هویت (مانند Okta، Microsoft Entra، Google Workspace و …) یک پیام XML امضا شده را به یک ارائه‌دهنده خدمات (یک محصول SaaS B2B) ارسال می‌کند. این پیام معمولاً فقط شامل آدرس ایمیل کاربر وارد شده است. این یک پروتکل پیچیده برای انتقال آدرس‌های ایمیل است.

به‌طور مشخص، درخواست‌های SAML یه POST از مرورگر کاربر شما هستند که شامل:

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response>
    <saml2:Assertion ID="id2829877824622019702853127">
        <saml2:Issuer>
            http://www.okta.com/exkig8gdo63cjI4OD5d7
        </saml2:Issuer>
        <ds:Signature>
            <ds:SignedInfo>
                <ds:Reference URI="#id2829877824622019702853127">
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>
                n744L/[...]mDruC1H9E0Lz7sbZg==
            </ds:SignatureValue>
        </ds:Signature>
        <saml2:Subject>
            <saml2:NameID>
                ulysse.carion@ssoready.com
            </saml2:NameID>
        </saml2:Subject>
    </saml2:Assertion>
</saml2p:Response>

و “پیاده‌سازی SAML” عمدتاً به این معناست که:

  • اعتبارسنجی پیام، با همه مشکلات و پیچیدگی‌هایی که به همراه داره.
  • و ورود کاربر به‌عنوان ulysse.carion@ssoready.com.

توجه کنید که این تقریباً همان ساختار پیامی است که در مثال قبلی توضیح دادم. نمی‌خواهم آن را اینجا فاش کنم چون این کار نادرست است، اما شما می‌توانید همان حمله را انجام دهید. می‌توانید من را مجبور کنید که پیام را در مرحله (۱) اعتبارسنجی کنم، و سپس چیزی دیگر در پیام قرار دهید که همان ID=”id2829877824622019702853127″ را داشته باشد و من از آن در مرحله (۲) استفاده کنم.

چگونه این مشکل را برطرف کنیم؟ به مشخصات توجه نکنید

نویسندگان کتابخانه‌های SAML، باید دیگر به مشخصات توجه نکنند.

وقتی یک مشخصات پر از مشکلات امنیتی است، مهندسان باید به آن توجهی نکنند. آنها باید آنچه که نویسندگان مشخصات SAML و امضاهای XML نوشته‌اند، نادیده بگیرند و به جای آن، روی اجرای اصول امنیتی واقعی تمرکز کنند.

به عبارت دیگر، دلیل این‌که حملات XML همچنان ادامه دارد، این است که افراد کدهایشان را به این شکل طراحی می‌کنند:

  • saml-ruby
  • xml-signatures-ruby

اما امضاهای XML بسیار پیچیده‌اند. آنها نمی‌توانند بگویند “بله، این پیام معتبر است”. آنچه که می‌توانند بگویند این است که، “این N زیرمجموعه از این سند XML به درستی امضا شده‌اند” (در واقع، وضعیت خیلی بدتر از این است) اما به هر حال، نمی‌توان بر روی امضاهای XML ساخت.

پس کارهای زیر که منطقی‌تر است را انجام دهید:

  1. ببینید، تقریباً همه ارائه‌دهندگان در دنیا به‌طور عملی توافق کرده‌اند، که بارهای SAML خود را به یک شکل مشابه طراحی کنند.
  2. فرض کنید تمام پیام‌هایی که به شکل دیگری طراحی شده‌اند، نامعتبر هستند.
  3. URI را در امضاهای XML را نادیده بگیرید.
  4. فرض کنید که آن‌ها، دقیقاً همان زیرمجموعه از بار SAML را که باید امضا کنند، امضا کرده‌اند. و این همان زیرمجموعه‌ای است که شما بعداً پردازش خواهید کرد.
  5. آن امضا را تایید کنید (تاکید میکنم، فقط آن امضا را. نه امضای دیگر را)

به عبارت دیگر، امضاهای XML را فراموش کنید. آن را به عنوان یک بقایای عجیب در نظر بگیرید. فقط به payload‌های SAML نگاه کنید و پروتکل واقعی که به طور عملی به وجود آمده را پیاده‌سازی کنید.

به توصیه‌های Postel توجه نکنید. در پردازش امضاهای رمزنگاری‌شده، شل بودن نه “liberal” است و نه مفید، بلکه بی‌قید و بند است.

نتیجه‌گیری

در دنیای پیچیده و در حال تحول امنیت دیجیتال، به‌ویژه در ارتباطات و تراکنش‌های آنلاین، مدیریت صحیح و ایمن اطلاعات اهمیت زیادی دارد. SAML (Security Assertion Markup Language) یکی از فناوری‌های کلیدی در این زمینه است که برای احراز هویت کاربران استفاده می‌شود. با این حال، آسیب‌پذیری‌های امنیتی همچنان تهدیدی جدی به شمار می‌روند و حملات XML Signature Wrapping یکی از این تهدیدات است که اخیراً با شناسه CVE-2024-45409 شناسایی شده و تاثیرات قابل توجهی بر اکوسیستم‌هایی مانند Ruby و GitLab داشته است.

این آسیب‌پذیری، که به دلیل مشکلات در مشخصات SAML و پیاده‌سازی‌های ناقص آن به وجود آمده است، به مهاجمان اجازه می‌دهد تا به سیستم‌ها نفوذ کرده و به‌عنوان هر کاربری در سیستم ظاهر شوند. برای مقابله با این نوع حملات، ضروری است که مهندسان و توسعه‌دهندگان به جای پیروی از مشخصات ناقص و پر از آسیب‌پذیری، بر اصول امنیتی واقعی تمرکز کنند و از روش‌های منطقی و کاربردی برای پردازش و اعتبارسنجی داده‌ها استفاده نمایند.

در نهایت، برای مقابله با این آسیب‌پذیری‌ها و کاهش خطرات، باید به سادگی از امضاهای XML صرف‌نظر کرد و به‌جای آن به پروتکل‌های معتبر و امن که در عمل به‌خوبی کار می‌کنند، اعتماد کرد. توجه به این نکات و پیاده‌سازی صحیح راه‌حل‌های امنیتی، می‌تواند به حفظ امنیت و سلامت سیستم‌های دیجیتال کمک کند.

امیدوارم از مطالعه این مقاله لذت برده باشید و اطلاعات مفیدی از آن به دست آورده باشید. برای شما منابع و مراجع استفاده شده در این مقاله را در زیر ارائه می‌دهم تا بتوانید به‌راحتی به آن‌ها دسترسی پیدا کنید و در صورت تمایل، اطلاعات بیشتری کسب نمایید.

2 پاسخ

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

پیشنهاد های کد اکسپلور