
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, которая имеет поток выполнения, позволяющий неаутентифицированный RCE.
Теперь основная цель — получить исходный код 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 устанавливается как логическое значение, которое разрешает или запрещает соединение, и оно изменяется только с помощью оператора "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-сессию.
Все происходит здесь:
Давайте попробуем заставить это работать