
CVE-2026-0073은 CVSS 심각도 점수 8.3의 RCE이며, 여기서는 이것이 어떻게 작동하는지 설명하겠습니다.
CVE-2026-0073은 CVSS 심각도 점수 8.3의 RCE이며, 여기서는 이것이 어떻게 작동하는지 설명합니다.
처음에, 흥미로울 수 있는 유일한 것은 버그 자체에 대해 더 배우는 것입니다.
제 첫 번째 출처는 : https://www.tenable.com/cve/CVE-2026-0073 입니다.
이 출처는 auth.cpp 파일에 adbd_tls_verify_cert 라는 함수가 있으며, 이 함수는 인증되지 않은 RCE를 허용하는 흐름을 가지고 있다고 알려줍니다.
이제 주요 목표는 auth.cpp의 소스 코드를 얻는 것입니다.
공식 저장소에서 찾을 수 없었기 때문에, Project-Awaken이 만든 이 저장소를 https://github.com/Project-Awaken/android_packages_modules_adb/blob/12/daemon/auth.cpp 에서 찾았습니다.
그리고 거기에 있습니다! 우리의 수수께끼 같은 함수:
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;
}
하지만 함수의 흥미로운 부분은 다음과 같습니다:
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 << "]";
}
왜일까요? verified는 연결을 허용하거나 허용하지 않는 부울 값으로 설정되며, "if" 문으로만 변경되기 때문입니다. 문제는? EVP_PKEY_cmp(known_evp.get(), evp_pkey.get())가 항상 1 또는 0을 반환하지 않고, -1과 -2도 반환한다는 것입니다. EVP_PKEY_cmp()는 키가 일치하면 1을, 일치하지 않으면 0을, 키 유형이 다르면 -1을, 작업이 지원되지 않으면 -2를 반환하기 때문입니다.
따라서 키가 일치하지 않더라도 다른 키의 인증서 또는 지원되지 않는 작업을 검증합니다.
세션을 검증하기 위해 이 두 가지 옵션 중 하나를 강제해야 합니다.
이 결함을 악용하려면 TLS 세션을 시작해야 합니다.
이것은 공격자와 대상 장치 모두 TLS를 사용할 수 있을 때만 작동합니다. 그러나 TLS 사용 결정은 대상 장치에 의해서만 결정됩니다.
대상 장치가 TLS를 허용하면 TLS 세션을 시작할 수 있습니다.
모든 것은 여기서 발생합니다:
작동하도록 만들어 봅시다.