
CVE-2026-0073 es una RCE con una puntuación de severidad CVSS de 8.3, y aquí explicaremos cómo funciona.
CVE-2026-0073 es un RCE con una puntuación de severidad CVSS de 8.3, y aquí explicaremos cómo funciona.
Al principio, lo único que podría ser interesante es aprender más sobre el propio fallo.
Mi primera fuente es: https://www.tenable.com/cve/CVE-2026-0073
Esta fuente nos dice que el archivo auth.cpp muestra una función llamada adbd_tls_verify_cert que tiene un flujo que permite un RCE no autenticado.
El objetivo principal ahora es obtener el código fuente de auth.cpp.
Como no pude encontrarlo en ningún repositorio oficial, encontré este repositorio creado por Project-Awaken en: https://github.com/Project-Awaken/android_packages_modules_adb/blob/12/daemon/auth.cpp
¡y ahí está! nuestra misteriosa función:
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;
}
Pero la parte interesante de la función es la siguiente:
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 << "]";
}
¿Por qué? porque verified se establece como un booleano que permite o no la conexión, y solo se modifica con la declaración "if". ¿El problema? EVP_PKEY_cmp(known_evp.get(), evp_pkey.get()) no siempre devuelve 1 o 0, sino también -1 y -2 porque EVP_PKEY_cmp() devuelve 1 si las claves coinciden, 0 si no coinciden, -1 si los tipos de clave son diferentes y -2 si la operación no es compatible.
Por lo tanto, valida los certificados de claves diferentes o si la operación no es compatible, incluso cuando la clave no coincide.
Para validar nuestra sesión tendremos que forzar una de estas dos opciones.
Para intentar explotar esta falla, necesitaremos establecer una sesión TLS.
Esto solo funciona si tanto el atacante como el dispositivo objetivo pueden usar TLS. Sin embargo, la decisión de usar TLS solo la toma el dispositivo objetivo.
Si el dispositivo objetivo permite TLS, puedes establecer una sesión TLS.
Todo ocurre aquí:
Intentemos hacer que funcione