این روزها خبر در مورد گاف بزرگ اپل زیاد بوده. احتمالا هم خبر رو شنیدین و هم می دونین که با این ضعف امنیتی چه خطراتی ممکنه برای کاربرها پیش بیاد. در این پست من یه قدم عقب تر می رم تا ببینیم که مشکل از کجا منشا گرفته.
اپل در آپدیتی که برای iOS ارائه کرده به صورت خلاصه هم مشکل رو گفته و هم خطر احتمالی رو.
اپل به وضوح در مورد a privileged network position و the authenticity of the connection توضیح نداده اما می شه فهمید که خطر چیه: MiTM
حمله MiTM (فردی در میانه) علیه سایت های رمزگذاری نشده، ساده است. مثلا اگر ما یه اکسس پوینت داشته باشیم، می تونیم استفاده کننده ها رو فریب بدیم و وقتی خواستن سایت http://example.com رو ببینن، اونها رو به یک سایت تقلبی منتقل کنیم. می تونیم اطلاعات سایت اصلی رو بگیریم و تغییراتی توش بیدم و حتی توش بدافزار تزریق کنیم و برای کاربر بفرستیم و اون هم متوجه نشه.
اما اگر کاربر بخواد یه سایت رمزگذاری شده رو ببینه، مثلا https://example.com، اونوقت انجام حمله MiTM به سادگی قبل نیست چرا که سایت تقلبی نمی تونه سرتیفیکیتی رو ارائه کنه که باید از طرف سایت اصلی ارائه بشه. چرا نمی تونه اون سرتیفیکیت رو بده؟ چون کلید خصوصی معتبر اون سایت رو نداره. و اگر کسی سعی کنه با سرتیفیکیت تقلبی و یا مشکل دار، کاربر رو فریب بده اونوقت مرورگر بهش یه پیام اخطار می ده.
شرط: مرورگر کاربر باید این توانایی رو داشته باشه و بتونه سرتیفیکیت رو بررسی کنه. مرورگرهای جدید این امکان رو دارند اما اپلیکیشن های موبایل هنوز یه سری مشکل در پیاده سازی این قضیه دارند.
خب بریم سر اصل قضیه. مشکل برنامه نویسی که منجر به این اشتباه بزرگ شد چی بوده؟
اپل علاقه زیادی نداشت که موشکافی بکنه و به مردم توضیح بده چی شد که این اتفاق افتاد. بذارید خودمون نگاهی به متن کدها بندازیم. متن کدهای درگیر در پروسه برقراری اتصال امن رو می تونید در این لینک ها ببینید:
1 2 3 4 5 | http://opensource.apple.com/source/Security/Security-55471/libsecurity_ssl/lib/SecureTransport.h http://opensource.apple.com/source/Security/Security-55471/libsecurity_ssl/lib/sslHandshake.c http://opensource.apple.com/source/Security/Security-55471/libsecurity_ssl/lib/sslKeyExchange.c |
و در صورتیکه بخواید مسیر فراخوانی توابع رو تا رسیدن به جایی که اشتباه رخ می ده دنبال کنید، می تونید این توابع رو چک کنید:
1 2 | SSLProcessHandshakeRecord() -> SSLProcessHandshakeMessage() |
که تابع ProcessHandshakeMessage نیز مراحل مختلف برقراری اتصال امن رو انجام می ده:
1 2 3 4 | -> SSLProcessClientHello() -> SSLProcessServerHello() -> SSLProcessCertificate() -> SSLProcessServerKeyExchange() |
تابع SSLProcessServerKeyExchange برای اتصال های TLS مشخصی فراخوانی می شه، مخصوصا وقتی forward secrecy استفاده می شه. forward secrecy روشیه که در اون به جای استفاده از جفت کلید همیشگی سایت، از جفت کلیدهای موقت استفاده می شه که بعد از بسته شدن صفحه، از بین می رن. نتیجه استفاده از forward secrecy اینه که بعد از بسته شدن صفحه (در اصل بعد از بسته شدن session) اماکن رمزگشایی از ترافیک ارسال وجود نخواهد داشت، حتی برای خود سایت و کاربر.
کد به این شکله:
1 2 3 4 5 | SSLProcessServerKeyExchange() -> SSLDecodeSignedServerKeyExchange() -> SSLDecodeXXKeyParams() IF TLS 1.2 -> SSLVerifySignedServerKeyExchangeTls12() OTHERWISE -> SSLVerifySignedServerKeyExchange() |
و نهایتا تابع SSLVerifySignedServerKeyExchange در فایل sslKeyExchange.c که لینکش رو بالاتر گذاشتم، به شکل زیره:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | . . . hashOut.data = hashes + SSL_MD5_DIGEST_LEN; hashOut.length = SSL_SHA1_DIGEST_LEN; if ((err = SSLFreeBuffer(&hashCtx)) != 0) goto fail; if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0) goto fail; if ((err = SSLHashSHA1.update(&hashCtx, &clientRandom)) != 0) goto fail; if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0) goto fail; if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) goto fail; goto fail; /* MISTAKE! THIS LINE SHOULD NOT BE HERE */ if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0) goto fail; err = sslRawVerify(...); . . . |
خب با یه نگاه به کد بالا می بینید که کجا اشتباه شده. اگر کد بالا رو به صورت ساده بخوایم بنویسیم می شه یه چیزی شبیه این:
1 2 3 4 5 | [1] if (certificate check is not valid) [2] goto fail; [3] goto fail; [4] if (next certificate check is not valid) [5] goto fail; |
برنامه نویس از {} استفاده نکرده که اگر با دستورات شرطی آشنا باشید می دونید که معنی کدها بالا اینه:
1 2 3 4 5 6 7 | [1] if (certificate check is not valid) { [2] goto fail; [3] } [3] goto fail; [4] if (next certificate check is not valid) { [5] goto fail; [6] } |
در واقع هیچ وقت به دستور شرطی آخر و بعدش دستور sslRawVerify نمی رسیم و قبل از اون دستور goto fail اجرا می شه. دستور sslRawVerify خیلی مهمه و درواقع با اجرای اون و اگر خطایی وجود نداشته باشه، متغیر err برابر صفر می شه و تابع SSLVerifySignedServerKeyExchange اعلام می کنه که همه چیز درسته.
در دستورهای شرطی، اگر خطایی وجود داشته باشه، متغیر err برابر مقدار خطا می شه و بعد دستور goto fail اجرا می شه. اما در اون خطی که goto fail به اشتباه تکرار شده، بدون اینکه مقدار متغییر err برابر با کد خطا بشه، دستور goto fail انجام می شه و بقیه بررسی هم انجام نمی شه. یعنی اگر خطایی وجود داشته باشه که در بررسی های بعدی قرار بوده پیدا بشه، هیچ وقت پیدا نمی شه.
نتیجه اینه که بدون انجام شدن بررسی های کامل، از این تابع خارج می شیم و با توجه به اینکه متغیر err که نشان دهنده خطاست برابر با صفره، به مرورگر می گیم مشکلی وجود نداره و اتصال امن برقرار شده در حالیکه الزاما اینجوری نیست.
هکر چطوری می تونه از این ضعف امنیتی استفاده کنه؟
- کاربر رو فریب بده که از یک سایت HTTPS تقلبی بازدید کنه.
- مرورگر یا نرم افزار رو مجبور کنه که از forward secrecy استفاده کنه چرا که سروره که مشخص می کنه چه الگوریتم رمزنگاری استفاده بشه.
- مرورگر یا نرم افزار رو مجبور کنه که از TLS نسخه ۱.۱ استفاده کنه چرا ه سروره که مشخص می کنه چه نسخه ای از TLS رو می پذیره.
- سرتفیکیت TLS رو ارائه بده که به نظر واقعی میاد اما با کلید خصوصی اشتباه تولید شده باشه.
این کارها امکان پذیره و در این پست می تونید نمونه اجرا شده اش رو ببینید.
چکار می شه کرد؟
اپل برای iOS نسخه به روز رسانی داده، پس آپدیتش کنید.
متاسفانه برای OS X تا کنون نسخه به روز رسانی نیومده و اپل فقط گفته “به زودی” مشکل رو برطرف می کنه. پس تا اون موقع به هیچ عنوان از شبکه های غیر مطمئن (مثل وایفای عمومی) استفاده نکنید.
اگر می تونید از VPN های امن استفاده کنید.
از مرورگرهایی مثل فایرفاکس و کروم استفاده کنید چون اونها از کتابخونه های خودشون استفاده می کنند، نه از کتابخونه های مشکل دار سیستم عامل.
به روز رسانی: نسخه جدید OS X ارائه شده. می تونید آپدیت کنید
به نظر شما عمدا این backdoor در کد گذاشته شده؟ جالبه که یک خط کد میتونه به راحتی امنیت یک سیستم رو زیر سوال ببره.
نمی تونم با اطمینان بگم عمدی بوده و یا غیرعمدی ولی من اگر قرار بود یه بکدور بذارم که بعدا بتونم منکرش بشم و مسئولیتش رو نپذیرم، اینجوری انجامش می دادم 🙂