
CVE-2026-0073 هي ثغرة تنفيذ تعليمات برمجية عن بُعد (RCE) بدرجة خطورة CVSS تبلغ 8.3، وسنشرح هنا كيفية عملها.
CVE-2026-0073 هو ثغرة تنفيذ كود عن بُعد (RCE) بدرجة خطورة CVSS تبلغ 8.3، وهنا سنشرح كيف تعمل.
في البداية، الشيء الوحيد الذي قد يكون مثيرًا للاهتمام هو معرفة المزيد عن الثغرة نفسها.
مصدرتي الأولى هي: https://www.tenable.com/cve/CVE-2026-0073
يخبرنا هذا المصدر أن ملف auth.cpp يعرض دالة تسمى adbd_tls_verify_cert والتي تحتوي على تدفق يسمح بتنفيذ كود عن بُعد غير مصادق عليه.
الهدف الرئيسي الآن هو الحصول على الكود المصدري لملف auth.cpp.
نظرًا لأنني لم أتمكن من العثور عليه في أي مستودع رسمي، وجدت هذا المستودع الذي أنشأه Project-Awaken على: https://github.com/Project-Awaken/android_packages_modules_adb/blob/12/daemon/auth.cpp
وها هي! دالتنا الغامضة:
int adbd_tls_verify_cert(X509_STORE_CTX* ctx, std::string* auth_key) {
if (!auth_required) {
// Any key will do.
LOG(INFO) << __func__ << ": auth not required";
return 1;
}
bool authorized = false;
X509* cert = X509_STORE_CTX_get0_cert(ctx);
if (cert == nullptr) {
LOG(INFO) << "got null x509 certificate";
return 0;
}
bssl::UniquePtr<EVP_PKEY> evp_pkey(X509_get_pubkey(cert));
if (evp_pkey == nullptr) {
LOG(INFO) << "got null evp_pkey from x509 certificate";
return 0;
}
IteratePublicKeys([&](std::string_view public_key) {
// TODO: do we really have to support both ' ' and '\t'?
std::vector<std::string> split = android::base::Split(std::string(public_key), " \t");
uint8_t keybuf[ANDROID_PUBKEY_ENCODED_SIZE + 1];
const std::string& pubkey = split[0];
if (b64_pton(pubkey.c_str(), keybuf, sizeof(keybuf)) != ANDROID_PUBKEY_ENCODED_SIZE) {
LOG(ERROR) << "Invalid base64 key " << pubkey;
return true;
}
RSA* key = nullptr;
if (!android_pubkey_decode(keybuf, ANDROID_PUBKEY_ENCODED_SIZE, &key)) {
LOG(ERROR) << "Failed to parse key " << pubkey;
return true;
}
bool verified = false;
bssl::UniquePtr<EVP_PKEY> known_evp(EVP_PKEY_new());
EVP_PKEY_set1_RSA(known_evp.get(), key);
if (EVP_PKEY_cmp(known_evp.get(), evp_pkey.get())) {
LOG(INFO) << "Matched auth_key=" << public_key;
verified = true;
} else {
LOG(INFO) << "auth_key doesn't match [" << public_key << "]";
}
RSA_free(key);
if (verified) {
*auth_key = public_key;
authorized = true;
return false;
}
return true;
});
return authorized ? 1 : 0;
}
لكن الجزء المثير للاهتمام في الدالة هو التالي:
bool verified = false;
bssl::UniquePtr<EVP_PKEY> known_evp(EVP_PKEY_new());
EVP_PKEY_set1_RSA(known_evp.get(), key);
if (EVP_PKEY_cmp(known_evp.get(), evp_pkey.get())) {
LOG(INFO) << "Matched auth_key=" << public_key;
verified = true;
} else {
LOG(INFO) << "auth_key doesn't match [" << public_key << "]";
}
لماذا؟ لأن verified تُعيّن كقيمة منطقية (boolean) تسمح بالاتصال أو تمنعه، ولا تتغير إلا من خلال عبارة "if". المشكلة؟ أن EVP_PKEY_cmp(known_evp.get(), evp_pkey.get()) لا تُرجع دائمًا 1 أو 0، بل تُرجع أيضًا -1 و -2 لأن EVP_PKEY_cmp() تُرجع 1 إذا تطابقت المفاتيح، و0 إذا لم تتطابق، و-1 إذا كانت أنواع المفاتيح مختلفة، و-2 إذا كانت العملية غير مدعومة.
لذا فهي تتحقق من صحة الشهادات ذات المفاتيح المختلفة أو إذا كانت العملية غير مدعومة، حتى لو لم يكن المفتاح مطابقًا.
من أجل التحقق من صحة جلستنا، سيتعين علينا فرض أحد هذين الخيارين.
من أجل محاولة استغلال هذا الخلل، سنحتاج إلى بدء جلسة TLS.
يعمل هذا فقط إذا كان كل من المهاجم والجهاز المستهدف قادرين على استخدام TLS، ومع ذلك فإن قرار استخدام TLS يُتخذ فقط من قبل الجهاز المستهدف.
إذا كان الجهاز المستهدف يسمح بـ TLS، يمكنك بدء جلسة TLS.
كل شيء يحدث هنا:
لنحاول جعل الأمر يعمل