
CVE-2026-0073 è una RCE con un punteggio di gravità CVSS di 8,3, e qui spiegheremo come funziona.
CVE-2026-0073 è una RCE con un punteggio di gravità CVSS di 8,3, e qui spiegheremo come funziona.
All'inizio, l'unica cosa che potrebbe essere interessante è saperne di più sulla vulnerabilità stessa.
La mia prima fonte è: https://www.tenable.com/cve/CVE-2026-0073
Questa fonte ci dice che il file auth.cpp mostra una funzione chiamata adbd_tls_verify_cert che ha un flusso che consente una RCE non autenticata.
L'obiettivo principale ora è ottenere il codice sorgente di auth.cpp.
Poiché non sono riuscito a trovarlo in nessun repository ufficiale, ho trovato questo repository creato da Project-Awaken su: https://github.com/Project-Awaken/android_packages_modules_adb/blob/12/daemon/auth.cpp
ed eccola lì! la nostra misteriosa funzione:
int adbd_tls_verify_cert(X509_STORE_CTX* ctx, std::string* auth_key) {
if (!auth_required) {
// Qualsiasi chiave andrà bene.
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: dobbiamo davvero supportare sia ' ' che '\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;
}
Ma la parte interessante della funzione è la seguente:
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 << "]";
}
Perché? perché verified è impostato come un booleano che consente o meno la connessione, e viene modificato solo con l'istruzione "if". Il problema? EVP_PKEY_cmp(known_evp.get(), evp_pkey.get()) non restituisce sempre 1 o 0, ma anche -1 e -2 perché EVP_PKEY_cmp() restituisce 1 se le chiavi corrispondono, 0 se non corrispondono, -1 se i tipi di chiave sono diversi e -2 se l'operazione non è supportata.
Quindi valida i certificati di chiavi diverse o se l'operazione non è supportata, anche se la chiave non corrisponde.
Per validare la nostra sessione dovremo forzare una di queste due opzioni.
Per cercare di sfruttare questa falla, dovremo avviare una sessione TLS.
Questo funziona solo se sia l'attaccante che il dispositivo target sono in grado di utilizzare TLS. Tuttavia, la decisione di utilizzare TLS è presa solo dal dispositivo target.
Se il dispositivo target consente TLS, puoi avviare una sessione TLS.
Tutto accade qui:
Proviamo a farlo funzionare