امنیت JWT بخش دوم: حملات شناخته شده JWT
JSON Web Token (JWT) یک راهکار امن و فشرده به منظور انتقال اطلاعات در قالب JSON Object تعریف مینماید. قبل از شروع معرفی حملات شناخته شده در JWT باید این نکته را در نظر داشت که این حملات عموما وابسته به نوع پیاده سازی JWT بوده و ارتباطی به ساختار اصلی آن ندارند. در این متن، به معرفی دو نمونه از رایجترین حملات JWTها پرداخته خواهد شد.
۱ حملات شناخته شده JWT
قبل از شروع معرفی حملات شناخته شده در JWT باید این نکته را در نظر داشت که این حملات عموما وابسته به نوع پیاده سازی JWT بوده و ارتباطی به ساختار اصلی آن ندارند. برای درک بهتر این حملات باید یک دید کلی راجع به JWS (json web signatures) یا همان امضاهای دیجیتال استفاده شده در این مکانیزم داشته باشیم.
هدف اصلی JWS کسب اطمینان از صحت اطلاعات ارسالی در یک فرمت Serializable و قابل درک برای سیستمهای رایانهای میباشد. منظور از صحت اطلاعات، عدم تغییر دادههای ارسالی بعد از امضاء دیجیتال میباشد.
همانطور که در بخش اول (مقدمهای بر JWT) ذکر گردید، یک توکن JWT دارای دو بخش اصلی Header و Payload میباشد که با استفاده از الگوریتم Base64 کدگذاری شده و با کاراکتر (.) به هم الحاق میشوند.
- بخش Header حاوی اطلاعاتی مربوط به خود JWT از قبیل نوع امضاء و الگوریتم میباشد.
- بخش Payload شامل تمامی اطلاعات مربوط به توکن از قبیل sub، iat و یا هر Claim دیگر میباشد.
به عنوان نمونه؛
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2
۱-۱ حمله “alg: none”
همانطور که پیش اشاره کردیم ، JWT ها دارای دو شی JSON با اطلاعات مهم، به نامهای Header و Payload میباشند. در JWTهای امضا شده، هر دو بخش header و payload میگردد. اما در JWTهای رمز شده، تنها بخش Payload رمز شده و بخش Header بایستی به صورت متن واضح باقی بماند.
در توکنهای امضا شده، علیرغم اینکه بخش امضا از دستکاری بخشهای Header و Payload محافظت به عمل میآورد، اما امکان بازنویسی این بخشها بدون نیاز به امضا و همچنین اعمال تغییرات در محتوای آن وجود دارد.
برای مثال به نمونه JWT زیر قبل از Serialize شدن توجه کنید:
header: {
alg: “HS256”,
typ: “JWT”
},
payload: {
sub: “joe”
role: “user”
}
فرض کنید پس از افزودن بخش امضا (با استفاده یک کلید Secret) و کدگذاری Base64 بخشهای مختلف، رشته JWT به صورت زیر خواهد بود.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2UiLCJyb2xlIjoidXNlciJ9.vqf3WzGLAxHW-X7UP-co3bU_lSUdVjF2MKtLtSU1kzU
حال، از آنجایی که این یک توکن امضا شده است، میتوانیم آن را خوانده و همچنین یک توکن مشابه با آن (با تغییرات مورد نظر در دادهای آن) ایجاد نماییم. شایان ذکر است تا زمانی که کلید امضا (Secret) را نداشته باشیم، نمیتوانیم آن را امضا نماییم. سوال اینست که مهاجمین علیرغم نداشتن کلید Secret، چگونه میتوانند توکن را دستکاری نمایند.
پاسخ ساده است. مهاجمان میتوانند از توکن بدون امضا استفاده کنند!
مثال :
header: {
alg: “none”,
typ: “JWT”
},
payload: {
sub: “joe”
role: “admin”
}
که مقدار Serialize شده آن به صورت زیر خواهد بود.
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJqb2UiLCJyb2xlIjoiYWRtaW4ifQ.
در مثال فوق، بخش Signature یا امضاء توکن به صورت کامل حذف شده است و مقدار “alg” به “none” و همچنین مقدار “role” از “user” به “admin” تغییر داده شده است. حال در صورت آسیب پذیر بودنJWT، فرد مهاجم قادر است به عنوان یک مدیر (با دسترسی بیشتر) به سامانه وارد شود. در ادامه توضیح خواهیم داد که دلیل امکان پذیر بودن این حمله چه چیزی میتواند باشد.
بدین منظور بایستی نگاهی به چگونگی کارکرد برخی از کتابخانه های فرضی JWT بیندازیم. فرض کنید یک تابع رمزگشایی همانند ذیل را داریم.
function jwtDecode(token, secretOrPublicKey) {
// (...)
}
بدین صورت که این تابع، یک توکن رمزگذاری شده به همراه کلید Secret را دریافت نموده و پس از تایید توکن، محتوای رمزگشایی شده آن را باز میگرداند. همچنین در زمانی که توکن تایید نشود، یک خطا نمایش میدهد. حال در صورتی که این تابع برای انتخاب الگوریتم مناسب برای تایید توکن، از پارامتر “alg” در بخش Header استفاده نماید، باعث به وجود آمدن آسیبپذیری ذکر شده میگردد. زمانی که فرد مهاجم مقدار پارامتر را با “None” جایگزین میکند، در واقع بدین معنی است که هیچ الگوریتمی برای صحتسنجی توکن وجود ندارد. در نتیجه تابع از مرحله تایید گذشته و فرد مهاجم به راحتی به عنوان “admin” وارد سامانه میشود.
همانطور که مشاهده می کنید ، این یک نمونه کلاسیک از حملات JWT است که به ابهام خاصی از API یک کتابخانه خاص متکی است، و نه یک آسیبپذیری در خود JWT. با این حال، این یک حمله واقعی است که در چندین اجرای مختلف در گذشته امکان پذیر بود. از اینرو، امروزه بسیاری از کتابخانه ها، مقدار “none” را برای پارامتر “alg” نامعتبر قلمداد مینمایند.
مهمترین روش جلوگیری از اینگونه حملات این است که همیشه قبل از صحتسنجی توکن، الگوریتم مشخص شده در Header اعتبارسنجی گردد. روش دیگر ارسال نوع الگوریتم مورد استفاده به عنوان یک پارامتر به تابع jwtDecode میباشد.
۱-۲ کلید های HMAC ضعیف
الگوریتمهای “HMAC” به منظور تولید و اعتبارسنجی امضاها، به یک کلید مشترک متکی میباشند. کلیدهای مشترک مشابه کلمات عبور میباشند. این کلیدها بایستی دارای طول نسبتا زیادی بوده تا در مقابل حملات Brute force مقاوم باشند. از سوی دیگر، تعداد کاراکترهای خیلی زیاد کلید Secret میتواند سرعت پردازشهای انجام شده را تحت تاثیر قرار دهد. از اینرو بایستی طول کلید به وصرت معقول و منطقی انتخاب شود. در واقع، طول کلید از نظر تعداد بیت حداقل بایستی هم اندازه تعداد بیتهای خروجی تابع درهمسازی باشد (برای مثال ۲۵۶ بیت در الگوریتم “HS256”). به عبارت دیگر، بسیاری از کلمههای عبور که میتوانند در هرجایی استفاده شوند، برای کلیدهای مشترک در JWTهای امضا شده توسط HMAC مناسب نیستند.
گزینه خوب دیگر، استفاده از الگوریتم “RS256” یا الگوریتمهای Public-Key دیگر است که بسیار مقاوم و انعطاف پذیر میباشند. شایان ذکر است که این فقط یک حمله فرضی نیست. به عبارت دیگر، در صورتیکه طول کلید Secret کوتاه باشد، حملات “Brute Force” برای “HS256” به اندازه کافی ساده میباشد.
تدوین: امیر حسین بابایی