محققای akamai یه گزارش فنی و PoC برای آسیب پذیری CVE-2022-34689 که با نام Windows CryptoAPI spoofing هم شناخته میشه، منتشر کردن.
این آسیب پذیری توسط NSA و NCSC بریتانیا کشف و گزارش شده. مایکروسافت این آسیب پذیری رو در بروزرسانی آگوست 2022 اصلاح و در اکتبر اونو عمومی کرد.
CryptoAPI چیه؟
CryptoAPI یه API ویندوزی هستش که برای مدیریت هر چیزی که مربوط به رمزنگاری هستش ، بکار میره. مرورگرها از این API برای اعتبارسنجی گواهی TLS استفاده میکنن. همون فرایندی که باعث ایجاد علامت قفل در کنار آدرس در مروگرها میشه.
البته این تایید گواهی منحصر به مرورگرها نیست و توسط سایر برنامه ها مانند احرازهویت وب پاورشل ، curl ، wget ، مدیریت FTP و EDRها و … هم مورد استفاده قرار میگیره.
همچنین از این API برای تایید گواهی امضای کد در برنامه های اجرایی ، کتابخونه ها و درایورها استفاده میشه.
با توجه به مطالب بالا وجود آسیب پذیری در فرایند تایید گواهی میتونه برای مهاجمین بسیار مفید باشه ، چون میتونن هویت خودشون رو جعل کنن و سیستم های امنیتی رو دور بزنن.
این اولین باری نیست که NSA در CryptoAPI آسیب پذیری پیدا میکنه. در سال 2020 اونا یه آسیب پذیری با شناسه CVE-2020-0601 که با نام CurveBall هم معروفه رو کشف و گزارش کرده بودن. اکسپلویت موفق CurveBall و CVE-2022-34689 منجر به جعل هویت میشه . CurveBall برنامه های زیادی رو تحت تاثیر قرار میداد اما CVE-2022-34689 به دلیل اینکه اکسپلویتش نیاز به یسری پیش نیاز داره ، دامنه کمتری رو تحت تاثیر قرار میده.
جزییات آسیب پذیری:
محققای akamai این آسیب پذیری رو از طریق patch diff بررسی کردن. با این تکنیک متوجه شدن که در crypt32.dll یه تابع بنام CreateChainContextFromPathGraph تغییراتی رو داشته. بنابراین مشکل تو این تابع هستش.
در این تابع یه مقایسه برای دو گواهی انجام شده. در یکی از این مقایسه ها ، گواهی بعنوان ورودی تابع هستش و در دیگری گواهی ذخیره شده در کش گواهی برنامه، برای بررسی به تابع داده میشه.
با بررسی patch diff محققا متوجه شدن که یسری بررسی ها با memcmp به هر دو مکان اضافه شده. در حقیقت مایکروسافت با اضافه کردن یسری بررسی با memcmp این آسیب پذیری رو وصله کرده.
قبل از وصله ، تابع صرفا MD5 گواهی ذخیره شده در کش رو بررسی میکرد که ،بدونه همون گواهی هست یا نه. چون گواهی تو کش ذخیره شده بنابراین از دید برنامه تایید شده حساب میشده و این مورد رو بررسی نمیکرده.
بنابراین اگه مهاجم بتونه یه گواهی درست کنه که با هش MD5 ذخیره شده در کش گواهی برنامه، مطابقت داشته باشه ، میتونه فرایند تایید رو دور بزنه و اصطلاحا هویت خودش جعل کنه.
مایکروسافت با اضافه کردن memcmp ، بررسی میکنه که محتویات هر دو گواهی هم یکی باشه.
کش گواهی برنامه چیه ؟
برای اینکه عملکرد و کارایی تابع CryptoAPI رو بهبود بدن از حافظه کش برای آخرین گواهی های دریافتی ، استفاده میکنن. این مکانیسم بصورت پیش فرض غیرفعال هستش و برای فعاسازی اون ، برنامه نویس باید یسری پارامتر به تابع CertGetCertificateChain ارسال کنه.
در این تابع پارامتر dwFlags قابلیت فعال سازی حافظه کش رو داره. محققا برای درک فرایند این مکانیسم ، تابع FindIssuerObject که مسئول گرفتن گواهی از حافظه کش هستش رو بررسی کردن :
- تابع 4 بایت آخر MD5 گواهی ورودی رو با گواهی کش بررسی میکنه.
- اگر با مورد مطابقت داشت ، تابع کل هش رو بررسی میکنه.
- اگه موردی مطابقت داشته باشه ، گواهی مورد اعتماد هستش و از این به بعد گواهی ورودی بجای گواهی کش شده مورد استفاده قرار میگیره.
- اگه مورد مطابقت نداشته باشه ، این روند روی سایر گواهی ها تکرار میشه.
این مکانیسم دارای باگ هستش. یکی اینکه مقایسه بیشتری روی گواهی ها انجام نمیشه و دوم اینکه مقایسه صرفا با تطابق هش MD5 ها هستش.
محققا برای اینکه صحت فرض خودشون رو بررسی کنن یه برنامه با CertGetCertificateChain نوشتن و همونطور که در شکل زیر مشاهده میکنید، گواهی مخرب مورد تایید هستش.
چطوری این آسیب پذیری رو اکسپلویت کنیم؟
در این آسیب پذیری ما در کل از MD5 thumbprint سوء استفاده میکنیم.ساخت یک گواهی که MD5 اون، با یه مقدار MD5 دیگه مطابقت داشته باشه حمله preimage گفته میشه و عملا غیرممکن هستش. اما میشه دو گواهی با پیشوند یکسان ساخت که هش MD5 اشون یکسان هستش. به این حمله chosen prefix collision گفته میشه که نوعی از حملات Collision هستش.
بنابراین مهاجم باید دو گواهی داشته باشه. یکی از گواهی ها باید مورد تایید باشه و طوری باید انتخاب بشه که حمله chosen prefix collision رو راحتتر کنه. گواهی دوم هم که گواهی مخرب هستش و باید با هش MD5 گواهی اول (مورد تایید) مطابقت داشته باشه.
جعل گواهی با MD5 collisions :
طبق RFC 5280 بخش 4.1 ، یه گواهی دنباله ای از ASN.1 هاست که شامل دو بخش هستش :
- بخش tbsCertificate : این بخشی هستش که تمام جزییات هویت (از جمله subject و public key و serial number و … ) شامل میشه و امضا شده هستش.
- بخش signatureAlgorithm و signatureValue : این فیلدها شامل امضای TBS هستن.
1 2 3 4 |
Certificate ::= SEQUENCE { tbsCertificate TBSCertificate, signatureAlgorithm AlgorithmIdentifier, signatureValue BIT STRING } |
با توجه به موارد بالا ، قسمت TBS فقط امضا میشه ولی هش متعلق به کل گواهی هستش. یعنی اگه ما تو یه گواهی بخش خارج از TBS رو تغییر بدیم ، این تغییر جوری باشه که گواهی رو خراب نکنه ، ما هش کامل گواهی رو تغییر دادیم.
از طرفی اگه تجزیه کننده گواهی ، گواهی رو به درستی تجزیه کنه و TBS هم دست نخورده باشه ، گواهی معتبر و امضا شده در نظر گرفته میشه، حتی اگه سایر بخش ها تغییراتی رو داشته باشن. شکل زیر به این مسئله اشاره میکنه .
مروری بر MD5 chosen prefix collisions :
اگر ما دو تا رشته A و B با طول یکسان داشته باشیم و دو تا رشته C و D رو طوری محاسبه کنیم که فرمول زیر صادق باشه :
MD5(A || C) = MD5(B || D)
بنابراین اگه ما یه پسوند E به هر دو طرف اضافه کنیم ، شرط همچنان درسته.
MD5(A || C || E) = MD5(B || D || E)
ایجاد collision block :
براساس چیزهایی که بالا گفته شد ، مهاجم باید یه گواهی تولید کنه که به نظر معتبر بیاد ، بعلاوه اینکه دارای collision block (همون رشته های C و D ) داشته باشه تا بشه گواهی مخرب ایجاد کرد.
طبق RFC 5280 و بخش 4.1.1.2 ، ساختار signatureAlgorithm مانند زیر هستش :
1 2 3 |
AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY DEFINED BY algorithm OPTIONAL } |
فیلد parameters برای الگوریتم RSA مقدار NULL رو داره. پس احتمالا CryptoAPI این مقدار برای RSA نادیده میگیره. بنابراین میتونیم از این فیلد استفاده کنیم.
برای اینکه بتونیم collision block رو آماده کنیم ، میتونیم از این پارامتر بعنوان جا نگهدار یا placeholder استفاده کنیم. فقط نوع NULL رو به BIT STRING باید تغییر بدیم. این تکنیک ،روی CryptoAPI و OPENSSL جواب داده. بنابراین چون ما به TBS دست نمیزنیم ، یه گواهی معتبر داریم. البته مقدار هش فرق میکنه.
ایجاد گواهی مخرب MD5 thumbprint collisions :
خب با توجه به مطالب بالا ، حالا میتونیم یه گواهی موجود و مورد تایید رو دستکاری کنیم تا بتونیم از تکنیک MD5 thumbprint collisions استفاده کنیم. برای این کار مراحل زیر رو طی میکنیم :
- یه گواهی معتبر با امضای RSA مانند گواهی TLS سایت ایجاد میکنیم.
- فیلدهای جالب در بخش TBS رو دستکاری میکنیم تا یه گواهی مخرب داشته باشیم. ما کاری با امضا نداریم.
- فیلد parameters در signatureAlgorithm هر دو گواهی رو تغییر میدیم طوری که فضای کافی برای قرار دادن MD5 collision blocks (C و D در توضیحات بالا) وجود داشته باشه که از همون افست در هر دو گواهی شروع بشه.
- هر دو گواهی رو از جایی که قراره MD5 collision blocks قرار بگیره، Truncate میکنیم.
- MD5 chosen prefix collision مخاسبه میکنیم و در گواهی ها قرار میدیم
- مقدار امضای گواهی معتبر (پسوند E در توضیحات بالا) رو به هر دو گواهی ناقص Concatenate کنید.
استفاده از این آسیب پذیری در یک مثال واقعی:
حالا میتونیم از این آسیب پذیری در یک مثال واقعی استفاده کنیم. ابتدا نیاز هستش که یه برنامه آسیب پذیر رو پیدا کنیم. برای این منظور محققا برنامه کروم نسخه 48 انتخاب کردن. دلیلشونم اینه که این برنامه فلگ CERT_CHAIN_CACHE_END_CERT رو به CertGetCertificateChain میده.
بعد از پیدا کردن برنامه آسیب پذیر ، نوبت به ایجاد گواهی ها میرسه. همونطور که قبلا گفته شد باید دو تا گواهی ایجاد کنم. برای این منظور از ابزار HashClash استفاده کردن.
در مرحله بعد نیاز هستش روشی رو پیدا کنیم که یکی از گواهی ها رو به کش کروم تزریق کنیم. این کار به دلیل اینکه کلید خصوصی رو نداریم دشوار هستش.
در TLS 1.2 دو مرحله برای تایید گواهی وجود داره :
- پیام Server Key Exchange : این پیام توسط فردی که کلید خصوصی داره ساخته میشه ، چون توسط گواهی امضا شده.
- پیام Server Handshake Finished : این پیام حاوی یه تاییدیه ضد دستکاری برای همه پیامهای Handshake شده قبلی هستش.
برای تزریق گواهی در کش ، ابتدا از یه اسکریپت پایتون بعنوان پروکسی استفاده میکنیم تا حملات MITM رو ایجاد کنیم:
- سرور مخرب MITM ما با سرور واقعی ارتباط برقرار کرده و اولین پیام TLS handshake رو بهش ارسال میکنه.
- در پیام Server Certificate ، سرور مخرب MITM ما ، پیام سرور واقعی رو تغییر میده و گواهی دستکاری شده رو جایگزین گواهی واقعی میکنه.
- پیام Server Key Exchange بدون تغییر میتونه ارسال بشه
- سرور مخرب نمیتونه پیام Server Handshake Finished رو ارسال کنه چون پیام دستکاری شده بنابراین ما اتصال رو قطع میکنیم.
کروم برای تایید پیام Server Key Exchange باید اونو در CryptoAPI لوود کنه و بنابراین گواهی ما در کش کروم تزریق میشه. کروم اتصال قطع شده رو بعنوان یه مسئله امنیتی TLS لحاظ نمیکنه و تلاش میکنه تا دوباره وصل بشه. نکته اینجاست که چون گواهی دستکاری شده ، اینبار به جای اینکه به سایت واقعی وصل بشه ، سرور مخرب ما به سایت با گواهی مخرب وصلش میکنه. در این مرحله کروم از تایید کامل چشم پوشی میکنه چون گواهی تو کش اون هستش. در نتیجه کاربر بجای مشاهده یه وب سایت قانونی از یه سایت مخرب بازدید میکنه که در این مثال بجای مشاهده سایت ملیکروسافت به یه سایت مخرب هدایت شده. اینجا میتونید ویدیو حمله رو مشاهده کنید.
POC این آسیب پذیری رو میتونید از اینجا بدست بیارید.
تشخیص
در کل برای اینکه از این آسیب پذیری استفاده بشه دو چیز نیاز هستش ، یکی نسخه آسیب پذیری crypt32.dll و دیگری یه برنامه آسیب پذیر که در اینجا کروم نسخه 48 بود.
محققا برای اینکه بتونید نسخه آسیب پذیر crypt32.dll شناسایی کنید یه OSQuery براش نوشتن.
1 2 3 4 5 6 7 8 9 10 11 |
WITH product_version AS ( WITH os_minor AS ( WITH os_major AS ( SELECT substr(product_version, 0, instr(product_version, ".")) as os_major, substr(product_version, instr(product_version, ".")+1) as no_os_major_substr FROM file WHERE path = "c:\windows\system32\crypt32.dll" ) SELECT substr(no_os_major_substr, instr(no_os_major_substr, ".")+1) as no_os_minor_substr, substr(no_os_major_substr, 0, instr(no_os_major_substr, ".")) as os_minor, os_major FROM os_major ) SELECT |