باگ ۹۰ هزار دلاری تو سایتهای داکیومنت گوگل
خلاصهٔ کاملتر
به گفتهٔ نویسنده، ماجرا از جایی شروع شد که داشت فلو OAuth مشترک بین سایتهای داکیومنت گوگل رو بررسی میکرد. وقتی یه درخواست ساده به مسیر oauth2authorize میزدی، سرور یه کوکی سشن Flask ست میکرد، حتی بدون اینکه لازم باشه اکانتی داشته باشی. چیزی که توجهشو جلب کرد اندازهٔ غیرعادی کوکی بود، حدود ۱۲۰۰ بایت، که برای یه توکن CSRF ساده خیلی بزرگ به نظر میرسید.
نکتهٔ کلیدی اینه که کوکیهای سشن Flask فقط امضا میشن، نه رمزگذاری؛ یعنی محتواشون برای هرکی کوکی رو داره قابل خوندنه. وقتی نویسنده محتوا رو باز کرد، دید که داخلش یه آبجکت از نوع OAuth2WebServerFlow بهصورت pickleشده نشسته و مهمتر از اون، client_id و client_secret اپلیکیشن OAuth بهصورت متن ساده (plaintext) داخل کوکی بودن. یعنی یه کلید حساس که اصلاً نباید سمت کلاینت بره، همونجا لو میرفت.
مشکل دوم و جدیتر این بود که همون client_secret لورفته، بهعنوان کلید امضای سشن (SECRET_KEY) Flask هم استفاده میشد. نویسنده با بازسازی امضا بهصورت آفلاین ثابت کرد که امضای کوکی دقیقاً با همین سکرت میخونه. یعنی یه کلید دوتا کار میکرد و کلیدی که باید محرمانه میموند، عملاً عمومی شده بود. نتیجه این بود که مهاجم میتونست هر مقداری تو سشن بذاره و یه امضای معتبر هم روش بسازه.
اینجا زنجیره به نقطهٔ خطرناکش میرسه. مسیر callback، آبجکت flow رو از سشن میخوند و با pickle.loads بازش میکرد. باز کردن داده با pickle فقط تا وقتی امنه که هیچکس نتونه کوکی جعلی معتبر بسازه؛ ولی وقتی کلید امضا عمومی شده، این فرض کاملاً بیمعنی میشه. چون عملگر بازسازی pickle میتونه یه تابع دلخواه رو صدا بزنه، ترکیب «کلید امضای عمومی» و «باز کردن داده با pickle» میشه اجرای کد از راه دور بدون احراز هویت. نویسنده برای اینکه روی سرور واقعی گوگل دستور اجرا نکنه، فقط با یه تأخیر زمانی کنترلشده ثابت کرد که کد روی سرور اجرا میشه: زمان پاسخ دقیقاً با عددی که فرستاده بود جلو میرفت.
نویسنده میگه همین کلید روی چند هاست دیگهٔ DevSite هم کار میکرد، از جمله firebase.google.com، developer.android.com و source.android.com. برای رفع مشکل چهار کار پیشنهاد داد: چرخوندن (rotate) هر دو سکرت لورفته و باطلکردن سشنهای امضاشده باهاشون، دادن یه SECRET_KEY مستقل و با آنتروپی بالا به هر دیپلوی که هیچ ربطی به اعتبارنامهٔ OAuth نداشته باشه، نگهداشتن آبجکت flow سمت سرور بهجای کوکی، و کنار گذاشتن pickle برای هر دادهای که از مرز کلاینت رد میشه. طبق تایملاین، گزارش ۶ آگوست ۲۰۲۶ ثبت شد، جایزه ۹۰ هزار دلار تعیین شد (پایهٔ ۷۵ هزار دلار برای RCE بحرانی ضرب در ۱.۲ بابت کیفیت استثنایی گزارش) و ۷ اکتبر ۲۰۲۶ وصله شد.
نکات کلیدی:
- کوکی سشن Flask سایتهای DevSite گوگل، client_id و client_secret رو بهصورت plaintext نگه میداشت
- کوکیهای Flask فقط امضا میشن نه رمزگذاری، پس محتواشون برای دارندهٔ کوکی قابل خوندنه
- همون client_secret بهعنوان SECRET_KEY امضای سشن هم استفاده میشد؛ یه کلید، دو نقش
- مسیر callback داده سشن رو با pickle.loads باز میکرد که با کلید عمومیشده، به RCE بدون احراز هویت میرسید
- همون کلید روی firebase.google.com، developer.android.com و source.android.com هم کار میکرد
- جایزه: ۹۰٬۰۰۰ دلار (۷۵٬۰۰۰ پایه برای RCE بحرانی × ۱.۲)؛ گزارش ۶ آگوست ۲۰۲۶، وصله ۷ اکتبر ۲۰۲۶




