توضیح دادیم که در بررسی تساوی عادی دو رشته توی جاوا، اول از همه طول رشتهها بررسی میشه، اگر برابر نبود دو رشته یکسان نیستن. و اگر طول رشتهها برابر بود، بررسی تساوی به محض این که به اولین کارکتر نابرابر برسیم، متوقف میشه و false برگردونده میشه.
این بهینهسازی یک ریسک داره: Timing Attack.
فرض کنین میخوایید این دو رشته رو با الگوریتم بالا تساویشون رو بررسی کنین:
a = "123abc"
b = "123bbc"
در اندیس سوم، چک کردن تساوی بهاتمام میرسه چون که a و b باهم برابر نیستند. اگر فرض کنیم عملیات بررسی تساوی ۱ میکروثانیه طول میکشه، با صرف ۴ میکروثانیه متوجه میشیم که این دو تا رشته باهم برابر نیستن.
فرض کنین یک جعبه سیاه دارید. این جعبه یک ورودی میگیره و اون هم رشتهی شماست. میدونین که این جعبه داخلش یک کار انجام میشه: «تساوی ورودی رو با رشتهای که اون تو مخفی شده چک میکنه و تنها مدت زمان انجام این عملیات رو برمیگردونه.»
حالا شما قصد دارید که حدس بزنین اون رشتهی مخفی شده توی جعبه چیه. از خوش اقبالی شما هم تا اینجا میدونین که اون رشته ۶ کارکتر بیشتر نداره (البته نیازی به از قبل دونستن این ماجرا نیست. برای سادهسازی اینطور فرض میکنیم). احتمالا حدس میزنین که باید یه جوری از این مدتزمان انجام عملیات بهره ببرید.
پس شروع میکنید رشتههایی به طول ۶ تولید کردن (فرض کنین که رشتهی مخفی ما همون 123abc بود) :
111111
۲ میکروثانیه: اولین ۱ با ۱ مچ شد. دومین ۱ با ۲ در رشتهی اصلی مچ نشد. دو تا عملیات مچ و تمام. پس فهمیدیم رشتهی رمز ما با ۱ شروع میشه. حالا باید کارکتر دوم رو از یک عوض کنیم.
121111
۳ میکروثانیه: اولین ۱ با ۱ مچ شد. ۲ با ۲ مچ شد. ۱ با ۳ مچ نشد. سه مقایسه و تمام. پس فهمیدیم کارکتر دوم ۲عه. حالا باید سوم رو عوض کنیم. اون رو به ۳ تغییر میدیم (این درواقع بروت فورسه. میتونست هر کارکتر دیگهای باشه. برای سادهسازی کارکتر درست رو انتخاب کردم.)
123111
۴ میکروثانیه: ۱ با ۱، ۲ با ۲ و حالا ۳ با ۳ مچ شد. ۱ با ۴ مچ نشد. عملیات با ۴ مقایسه تموم میشه. حالا وقتشه که کارکتر چهارم رو عوض کنیم. a رو جاش قرار میدیم:
123a11
و با همین روند میشه حدس زد و تمام کارکترها تا آخر پیش برد:
123abc
حالا هرچند بار که به جعبه این ورودی رشته رو میدیم مدت زمان تغییر نمیکنه. یا وقتی که رشتهای با طول بیشتر از این میدیم با زمانی خیلی کمتر کار رو خاتمه میده.
با این روش، بدون اینکه هیچ دسترسی مستقیمی به داخل جعبه داشته باشیم، تونستیم رشتهی مخفی رو کارکتر به کارکتر بازسازی کنیم. به این حمله Timing Attack میگن. احتمالا هم حدس زدین که برای حدس کلیدها در صورتی که از الگوریتم بالا (یعنی یک تساوی ساده) برای مقایسه استفاده کنن میشه استفاده کرد. اینها نمونههای واقعی از بهرهبرداری از timing attack در دنیای واقعی بودن:
1. OpenSSL & RSA Private Key — Brumley & Boneh (2003)
Brumley and Boneh devised a timing attack against OpenSSL and successfully extracted a factor of the RSA modulus (and therefore the private key) over a local network. Before this attack, it was generally believed that timing attacks would not work against general-purpose servers because timing variations would mask the decryption times.
They reported using about a million queries to remotely extract a 1024-bit key from an OpenSSL 0.9.7 server in about two hours. The fix was enabling RSA blinding by default in OpenSSL.
2- Django — User Enumeration via Timing (2013)
A test setup showed that for a correct username, the login response was 2–10 times slower than for an incorrect one, even when only running 5 queries per candidate. The difference was described as immediately obvious and easy to detect through any modern network with even a basic script.
و خیلی مثالهای دیگه. حالا شاید بپرسین: «وقتی درخواست از طریق اینترنت میاد، تاخیر شبکه همه چیز رو خراب نمیکنه؟»
جواب اینه که نه، لزوماً. در همین مثالهای بالا نشون دادن که با تکنیکهای آماری روی هزاران درخواست، میشه سیگنال زمانی رو حتی از پشت تاخیر شبکه هم استخراج کرد. در شبکههای داخلی (مثل سرویسهای میکروسرویس که با هم روی یه VPC صحبت میکنن) این حمله خیلی راحتتره چون تاخیر شبکه خیلی کمه.
حالا چطور باید از این جلوگیری کرد؟ تو زبونهای مختلف توابعی هست برای مقایسهی رشته در زمان ثابت و غیروابسته به تعداد کارکتر مچ شده که برای مقایسه سکرتها باید استفاده بشن.
MessageDigest.isEqual()
subtle.ConstantTimeCompare()
hmac.compare_digest()
در ادامه سناریوای که با گولنگ پیاده کردم میذارم: