
CVE-2026-0073 is an RCE with a CVSS severity score of 8.3, and here we will explain how it works.
CVE-2026-0073 is an RCE with a CVSS severity score of 8.3, and here we will explain how it works.
At first, the only thing that could possibly be interesting is learn more about the bug itself.
My first source is : https://www.tenable.com/cve/CVE-2026-0073
This source tells us that the auth.cpp file shows a function called adbd_tls_verify_cert which has a flow that allows an unauthenticated RCE.
The main objective now is to get the source code of auth.cpp.
As I couldn't find it in any official repo, I found this repository made by Project-Awaken at : https://github.com/Project-Awaken/android_packages_modules_adb/blob/12/daemon/auth.cpp
and there it is ! our misterious function :
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;
}
But the interesting part of the function is the following one :
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 << "]";
}
Why ? because verified is set as a boolean that allow or not the connection, and it is only changed with the "if" statement. the issue ? the EVP_PKEY_cmp(known_evp.get(), evp_pkey.get()) don't always returns 1 or 0, but also -1 and -2 because EVP_PKEY_cmp() return 1 if the keys match, 0 if they don't match, -1 if the key types are different and -2 if the operation is not supported.
So it validates the certificates of different keys or if the operation is not supported, even though the key is not a match.
In order to validate our session we will have to force one of these two options.
In order to try to exploit that flaw, we will need to engage a TLS session.
That works only if both the attacker and targeted device are able to use TLS, However the decision to use TLS is only decided by the targeted device.
If the targeted device allows TLS, you can engage into a TLS session.
It all happens here :
Let's try to get to work