
CVE-2026-0073 é uma RCE com pontuação de severidade CVSS de 8,3, e aqui explicaremos como ela funciona.
CVE-2026-0073 é um RCE com pontuação de severidade CVSS de 8,3, e aqui explicaremos como ele funciona.
No início, a única coisa que poderia ser interessante é aprender mais sobre o bug em si.
Minha primeira fonte é: https://www.tenable.com/cve/CVE-2026-0073
Esta fonte nos diz que o arquivo auth.cpp mostra uma função chamada adbd_tls_verify_cert que possui um fluxo que permite um RCE não autenticado.
O principal objetivo agora é obter o código-fonte do auth.cpp.
Como não consegui encontrá-lo em nenhum repositório oficial, encontrei este repositório feito pela Project-Awaken em: https://github.com/Project-Awaken/android_packages_modules_adb/blob/12/daemon/auth.cpp
e aí está! nossa misteriosa função:
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;
}
Mas a parte interessante da função é a seguinte:
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 é definido como um booleano que permite ou não a conexão, e ele só é alterado com a instrução "if". o problema? o EVP_PKEY_cmp(known_evp.get(), evp_pkey.get()) nem sempre retorna 1 ou 0, mas também -1 e -2 porque EVP_PKEY_cmp() retorna 1 se as chaves correspondem, 0 se não correspondem, -1 se os tipos de chave são diferentes e -2 se a operação não é suportada.
Então ele valida os certificados de chaves diferentes ou se a operação não é suportada, mesmo que a chave não seja uma correspondência.
Para validar nossa sessão, teremos que forçar uma dessas duas opções.
Para tentar explorar essa falha, precisaremos estabelecer uma sessão TLS.
Isso só funciona se tanto o atacante quanto o dispositivo alvo forem capazes de usar TLS. No entanto, a decisão de usar TLS é tomada apenas pelo dispositivo alvo.
Se o dispositivo alvo permitir TLS, você pode estabelecer uma sessão TLS.
Tudo acontece aqui:
Vamos tentar fazer funcionar