
CVE-2026-0073 est une RCE avec un score de sévérité CVSS de 8,3, et nous expliquerons ici comment elle fonctionne.
CVE-2026-0073 est une RCE avec un score de sévérité CVSS de 8,3, et ici nous expliquerons comment elle fonctionne.
Au départ, la seule chose qui pourrait éventuellement être intéressante est d'en apprendre davantage sur le bug lui-même.
Ma première source est : https://www.tenable.com/cve/CVE-2026-0073
Cette source nous indique que le fichier auth.cpp montre une fonction appelée adbd_tls_verify_cert qui possède un flux permettant une RCE non authentifiée.
L'objectif principal maintenant est d'obtenir le code source de auth.cpp.
Comme je n'ai pas pu le trouver dans un dépôt officiel, j'ai trouvé ce dépôt créé par Project-Awaken à l'adresse : https://github.com/Project-Awaken/android_packages_modules_adb/blob/12/daemon/auth.cpp
et le voilà ! notre mystérieuse fonction :
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;
}
Mais la partie intéressante de la fonction est la suivante :
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 << "]";
}
Pourquoi ? parce que verified est défini comme un booléen qui autorise ou non la connexion, et il n'est modifié qu'avec l'instruction "if". Le problème ? le EVP_PKEY_cmp(known_evp.get(), evp_pkey.get()) ne retourne pas toujours 1 ou 0, mais aussi -1 et -2 car EVP_PKEY_cmp() retourne 1 si les clés correspondent, 0 si elles ne correspondent pas, -1 si les types de clés sont différents et -2 si l'opération n'est pas prise en charge.
Il valide donc les certificats de clés différentes ou si l'opération n'est pas prise en charge, même si la clé ne correspond pas.
Afin de valider notre session, nous devrons forcer l'une de ces deux options.
Afin d'essayer d'exploiter cette faille, nous devrons engager une session TLS.
Cela ne fonctionne que si l'attaquant et l'appareil ciblé sont tous deux capables d'utiliser TLS. Cependant, la décision d'utiliser TLS n'est prise que par l'appareil ciblé.
Si l'appareil ciblé autorise TLS, vous pouvez engager une session TLS.
Tout se passe ici :
Essayons de faire fonctionner cela