NVD의 취약점 영향 정보, 그대로 믿어도 괜찮을까?### 부제: 잘못된 affected version 정보가 취약점 대응에 미치는 영향 ## 시작하기에 앞서.. NVD는 취약점 정보를 제공한다.\ 그 중에서도 실제 대응에 가장 많이 사용되는 정보 중 하나가 '어떤 제품과 버전이 영향을 받는가'이다.\ NVD는 이를 CPE(Common Platform Enumeration)라는 형식으로 표현한다.\ \ 예를 들어\ `cpe:2.3:a:apache:http_server:2.4.49:*:*:*:*:*:*:*`\ 는 Apache HTTP Server 2.4.49를 의미한다. 그리고 NVD는 이러한 CPE를 기반으로 다음을 기록한다.\\ - 어떤 버전이 취약한지\\ - 어떤 버전에서 수정됐는지 대부분의 취약점 스캐너와 자산 관리 도구는 이 정보를 주로 사용한다. --- ## 0. 왜 affected version 정보가 중요한가 취약점 대응은 보통 버전 확인에서 시작된다. ```text CVE 확인 -> 사용 중인 제품과 버전 확인 -> NVD 또는 벤더 권고문에서 affected version 확인 -> 패치 필요 여부 판단 -> 대응 우선순위 결정 ``` 이 과정에서 중요한 질문은 다음과 같다. ```text 우리가 쓰는 이 버전이 실제로 취약한가? ``` NVD의 CPE 기반 affected software configuration은 이 질문에 답하기 위한 가장 널리 쓰이는 단서 중 하나다. 보안 스캐너와 취약점 관리 시스템은 제품명과 버전을 기준으로 NVD 데이터를 조회하고, 해당 버전이 affected range 안에 있는지 확인한다. 문제는 이 정보가 실제 코드 상태와 항상 일치하지는 않는다는 점이다. 안전한 버전을 취약하다고 판단하면 불필요한 패치와 검증이 발생한다. 반대로 취약한 버전을 안전하다고 판단하면 패치 대상에서 빠진다. 전자는 운영 비용을 늘리고, 후자는 실제 위험을 남긴다. 이 글은 NVD를 쓰지 말자는 이야기가 아니다. NVD는 대부분의 취약점 관리 도구가 기반 정보로 사용하는 중요한 정보이다. 다만 NVD의 affected version 정보는 취약점 대응의 첫 번째 단서로 봐야 한다. 특히 중요한 시스템, 오래된 브랜치, 벤더 포크, LTS 패키지에서는 그 정보가 실제 코드와 맞는지 한 번 더 확인할 필요가 있다. --- ## 1. NVD CPE는 무엇을 표현하는가 NVD는 CVE별로 영향을 받는 소프트웨어 구성을 표현하기 위해 CPE를 사용한다. CPE는 벤더, 제품, 버전 등을 구조화된 이름으로 표현하는 방식이다. NVD의 Vulnerability Detail Page는 CPE Match String과 CPE Match String Range를 이용해 affected configuration을 보여준다. 특히 CPE Match String Range는 단일 버전뿐 아니라 시작 버전과 끝 버전을 이용해 ``, `=` 형태의 범위를 표현할 수 있다. 예를 들어 특정 제품의 `1.2.0` 이상, `1.2.5` 미만 버전을 affected range로 표현하는 식이다. [NVD, Understanding Vulnerability Detail Pages](https://nvd.nist.gov/vuln/vulnerability-detail-pages) ```text product: libfoo affected: >= 1.2.0 and < 1.2.5 ``` 이 방식은 대규모 취약점 관리에서 매우 유용하다. 모든 CVE마다 소스코드와 패치 이력을 직접 확인하는 것은 현실적으로 어렵다. 이름과 버전으로 먼저 후보를 줄일 수 있어야 한다. 하지만 실제 소프트웨어 배포 구조는 단순한 버전 범위보다 복잡하다. ```text upstream release -> release branch -> LTS branch -> vendor fork -> distro package -> backported patch -> appliance / firmware build ``` 같은 버전 문자열이라도 실제 포함된 코드가 다를 수 있다. 반대로 서로 다른 버전명이 같은 취약 코드를 포함할 수도 있다. 이 간극 때문에 affected version 정보가 실제 코드 상태와 어긋난다. >  --- ## 2. affected version 정보가 어긋나는 대표적인 상황 affected version 정보가 실제 코드와 맞지 않는 방식은 여러 가지다. 실무에서 특히 문제가 되는 경우는 다음과 같다. ### 2.1 영향 범위를 너무 넓게 잡는 경우 어떤 취약점이 `1.5.0`에서 발견됐다고 하자. 데이터베이스에는 다음처럼 기록될 수 있다. ```text affected: 불필요한 패치 검토 -> 알림 피로 증가 -> 취약점 대응 우선순위 혼란 ``` ### 2.2 영향 범위를 너무 좁게 잡는 경우 더 위험한 상황은 반대다. ```text affected: max_len) + return ERROR; memcpy(buf, input, len); ``` 이 경우 `memcpy`는 그대로 남아 있다. 달라진 것은 그 전에 입력 길이를 검사하는 흐름이다. 따라서 “패치에서 삭제된 라인 = 취약 코드”라는 가정은 모든 취약점에 적용되기 어렵다. 취약점은 특정 한 줄이 아니라, 검증되지 않은 입력이 위험한 연산까지 도달하는 흐름일 수 있다. ### 4.2 브랜치와 backport는 단순 버전 비교를 어렵게 만든다 취약점은 보통 upstream에서 먼저 패치된다. 그러나 실제 운영 환경은 LTS branch, vendor fork, distro package, appliance firmware를 사용할 수 있다. 확인해야 할 것은 단순한 버전 번호가 아니다. ```text upstream fixed commit이 있는가? 그 commit이 내가 쓰는 release branch에 포함되었는가? backport patch가 별도로 적용되었는가? 배포 패키지에 실제로 반영되었는가? ``` 버전 번호가 높다고 항상 안전한 것도 아니고, 버전 번호가 낮다고 항상 취약한 것도 아니다. ### 4.3 코드가 변형되면 단순 매칭이 흔들린다 소프트웨어는 계속 바뀐다. 함수 이름이 바뀌고, 코드 위치가 이동하고, 변수명이 바뀌고, 로직이 리팩토링된다. 문자열로는 달라 보이지만 취약한 동작은 그대로 남을 수 있다. 반대로 코드 일부가 비슷해 보여도 실제 취약 조건은 사라졌을 수 있다. 그래서 affected version 식별은 단순한 문자열 검색이 아니다. 패치의 의미, 코드 이력, 릴리즈 구조를 함께 봐야 한다. ## 5. 연구들은 이 문제를 어떻게 완화하려 했나 affected version 문제를 다루는 연구들은 서로 다른 관점에서 같은 질문을 다룬다. ```text 이 CVE가 실제로 영향을 주는 버전은 어디까지인가? ``` 여기서 중요한 것은 특정 연구 하나가 모든 문제를 해결했다는 뜻이 아니다. 각 연구는 NVD affected version 정보만으로 판단하기 어려운 부분을 다른 방식으로 보완하려 했다. ### 5.1 SZZ 계열: 취약점이 언제 들어왔는가 SZZ는 원래 bug-fixing commit을 보고 bug-introducing commit을 찾기 위해 쓰이던 아이디어다. 취약점 분석에서는 이를 “취약점이 처음 들어온 commit을 찾자”는 문제로 확장할 수 있다. ICSE 2022의 *V-SZZ: Automatic Identification of Version Ranges Affected by CVE Vulnerabilities*는 이 방향의 대표적인 연구다. V-SZZ는 vulnerability-fixing commit에서 취약점과 관련된 라인을 추적해 vulnerability-inducing commit을 찾고, 이를 기반으로 affected version range를 다시 계산하려 했다. [Bao et al., ICSE 2022](https://dl.acm.org/doi/10.1145/3510003.3510113) 이 접근은 NVD의 버전 범위를 그대로 따르는 대신, 코드 이력에서 취약점의 시작점을 찾으려 한다는 점에서 의미가 있다. 다만 한계도 있다. 패치가 add-only 형태이거나, 취약 로직이 한두 줄로 명확히 남아 있지 않거나, 리팩토링으로 코드가 크게 바뀐 경우에는 추적이 어려워진다. SZZ 계열은 “언제 들어왔는가”를 보는 데 강하지만, 모든 형태의 패치와 코드 변화를 포괄하기는 어렵다. ### 5.2 origin 추적: 취약점은 어디서 시작됐는가 USENIX Security 2021의 *V0Finder*는 “Vulnerability Zero”라는 개념을 제시했다. 관심사는 특정 취약점이 어떤 소프트웨어와 버전에서 처음 시작되었는가다. V0Finder는 코드 재사용 관계를 분석해 취약점이 어떤 방향으로 전파되었는지 추적한다. [Woo et al., USENIX Security 2021](https://www.usenix.org/conference/usenixsecurity21/presentation/woo) 이 관점은 CPE 문제와 연결된다. 취약점은 한 프로젝트 안에서만 움직이지 않는다. ```text upstream library -> fork -> downstream project -> vendor SDK -> product firmware ``` 제품 이름과 버전만 보면 이런 전파 경로를 놓칠 수 있다. V0Finder 같은 연구는 취약점의 출처와 재사용 관계를 코드 관점에서 보려는 시도다. 다만 origin을 찾는 것과 모든 release branch의 affected 여부를 판단하는 것은 다르다. 취약점이 어디서 시작됐는지 알아도, 각 downstream 버전이 실제로 패치됐는지는 다시 확인해야 한다. ### 5.3 affected / unaffected version을 직접 판단하려는 연구 다음 흐름은 더 직접적이다. ```text 이 특정 버전이 affected인가, unaffected인가? ``` ASE 2022의 *Precise (Un)Affected Version Analysis for Web Vulnerabilities*는 웹 취약점을 대상으로 patch commit과 vulnerability fingerprint를 이용해 특정 버전이 affected인지, unaffected인지 구분하려 했다. 여기서 중요한 점은 단순히 “취약한 버전을 더 많이 찾는 것”이 아니다. 취약하지 않은 버전까지 정확히 구분해야 불필요한 대응을 줄일 수 있다. [Shi et al., ASE 2022](https://conf.researchr.org/details/ase-2022/ase-2022-research-papers/54/Precise-Un-Affected-Version-Analysis-for-Web-Vulnerabilities) Java 생태계에서는 *VERJava*가 비슷한 문제를 다룬다. VERJava는 Java OSS 취약점에 대해 function-level과 patch-level 분석을 결합해 실제 취약 버전을 식별하려 한다. 특정 언어와 생태계에 맞게 패치 라인, 함수 단위 변화, 버전 이력을 함께 보려는 접근이다. [Sun et al., ICSME 2022](https://github.com/sunSUNQ/VERJava) ASE 2024의 *VISION*은 open-source vulnerability에 대해 affected library versions를 식별하려는 연구다. 이 연구는 취약점 설명이나 패치 정보에서 단서를 얻고, 라이브러리 버전별로 취약 로직이 남아 있는지 판단하려 한다. 즉 “이 라이브러리의 어느 버전까지 영향을 받는가”에 더 직접적으로 다가간다. [Wu et al., ASE 2024](https://dl.acm.org/doi/10.1145/3691620.3695516) C/C++ 취약점에서도 유사한 흐름이 이어진다. 예를 들어 *Clovery*는 public vulnerability report의 affected version 정보를 확인하기 위해 patch와 repository를 함께 보고, 함수 이력과 코드 의미 관계를 이용해 버전별 영향을 판단하려 한다. 핵심은 특정 버전 문자열만 보는 것이 아니라, 실제 코드에 취약한 흐름이 남아 있는지 확인하려는 데 있다. [Kim et al., IFIP SEC 2026](https://link.springer.com/chapter/10.1007/978-3-032-27993-4_19) 이 연구들의 공통점은 분명하다. ```text 문서에 적힌 affected range만 보지 않고, 패치와 코드 상태를 이용해 버전별 영향을 다시 판단한다. ``` 다만 이 방향도 쉬운 문제는 아니다. patch commit을 정확히 찾기 어려울 수 있고, 취약점 설명이 충분하지 않을 수 있으며, 언어마다 분석 가능한 정보가 다르다. 함수 이름이 바뀌거나 코드가 리팩토링되면 단순 매칭은 쉽게 흔들린다. add-only patch처럼 취약 코드를 삭제하지 않고 검증 로직만 추가하는 패치도 많다. 결국 affected version 식별은 “패치가 있는가”만 확인하는 문제가 아니라, 패치가 어떤 취약한 동작을 막는지 이해해야 하는 문제에 가깝다. ### 5.4 현 연구 상황 여러 연구가 등장했지만, 결론을 “자동 도구가 NVD affected range 문제를 해결해준다”로 가져가면 안 된다. 최근 연구들은 오히려 이 문제가 아직 충분히 어렵다는 점을 보여준다. ASE 2025의 *Vulnerability-Affected Versions Identification: How Far Are We*?는 이 분야의 현재 상황을 정리하는 연구다. 이 논문은 vulnerability-affected versions identification 도구들을 크게 tracing 계열과 matching 계열로 나누고, C/C++ 취약점 benchmark에서 여러 도구를 비교한다. tracing 계열은 SZZ처럼 취약점이 들어온 commit을 역추적해 affected version을 추론하고, matching 계열은 취약 로직이나 패치 패턴을 여러 버전에서 직접 찾아보는 방식이다. [Chen et al., ASE 2025](https://conf.researchr.org/details/ase-2025/ase-2025-papers/106/Vulnerability-Affected-Versions-Identification-How-Far-Are-We-) 도구가 많아졌고 접근도 다양해졌지만, 실제 취약점 이력은 여전히 자동화하기 어렵다. add-only patch, cross-file change, multi-branch development, 제한적인 semantic reasoning, rigid matching logic 같은 요소가 계속 발목을 잡는다. 즉, NVD affected version 정보가 틀릴 수 있다는 문제를 완화하려는 연구는 분명히 진행되고 있지만, 아직 하나의 방식으로 모든 상황을 처리하기는 어렵다. 그래서 이 분야의 흐름은 단순히 더 빠른 매칭으로 가지 않는다. 패치가 어떤 의미를 갖는지, 취약 로직이 버전 이력에서 어떻게 변했는지, 브랜치와 backport가 어떻게 반영되었는지, 생태계별 버전 표현이 어떤 차이를 갖는지를 함께 보려는 방향으로 이동하고 있다. 결국 연구 흐름을 한 문장으로 정리하면 이렇다. ```text NVD의 affected version 정보는 출발점으로 유용하지만, 중요한 판단에는 패치와 코드 이력을 함께 확인해야 한다. ``` ## 6. 실무에서는 어떻게 봐야 하나 NVD CPE를 버릴 필요는 없다. 오히려 대규모 취약점 대응에서는 반드시 필요하다. 다만 역할을 다르게 봐야 한다. ```text NVD CPE = 빠른 후보 탐색 패치, 브랜치, 벤더 권고문, 실제 코드 = 판단 보강 ``` 특히 다음 상황에서는 affected range만 보고 결론을 내리기 어렵다. ```text - LTS branch를 사용한다 - vendor fork 또는 distro package를 사용한다 - upstream과 다른 버전명을 사용한다 - backport patch가 적용되었다 - firmware나 appliance 내부에 OSS가 번들되어 있다 - CVE description과 vendor advisory가 서로 다르게 말한다 - fixed version으로 올렸지만 실제 patch commit 포함 여부가 불명확하다 ``` 이런 경우에는 다음 질문을 확인해야 한다. ```text 1. NVD가 말하는 affected range는 어떤 CPE match 기준인가? 2. vendor advisory도 같은 범위를 말하는가? 3. fixed version이라고 표시된 릴리즈에 실제 patch commit이 포함되어 있는가? 4. 우리가 쓰는 release branch나 패키지에도 그 패치가 들어왔는가? 5. backport가 있다면 version range보다 patch presence를 우선해서 봐야 하는가? 6. 제품 안에 포함된 코드가 upstream release와 다른가? ``` OSV 같은 생태계 중심 취약점 데이터베이스를 함께 보는 것도 도움이 된다. OSV는 package ecosystem, introduced/fixed event, commit 또는 version range를 구조화해서 표현하려 한다. OpenSSF의 OSV schema 문서도 이를 open source package vulnerability를 위한 표준 interchange format으로 설명한다. [OpenSSF OSV Schema](https://ossf.github.io/osv-schema/) 좋은 취약점 대응은 하나의 데이터 소스만 보는 것이 아니다. ```text NVD CPE + vendor advisory + OSV / ecosystem advisory + patch commit + release branch + 실제 배포 artifact ``` 이 정보들을 함께 봐야 한다. --- ## 7. 결론: NVD만 보기에는 소프트웨어가 너무 복잡하다 NVD의 CPE 기반 affected version 정보는 취약점 관리에서 매우 유용하다. 대규모 환경에서 위험 후보를 빠르게 찾는 데 필요하다. 하지만 그 정보가 항상 실제 코드 상태와 맞는 것은 아니다. 영향 범위가 너무 넓게 잡힐 수도 있고, 너무 좁게 잡힐 수도 있다. backport와 release branch를 놓칠 수 있고, 제품 이름과 패키지 이름이 어긋날 수도 있다. fixed version이라고 표시되어 있어도 실제 운영 환경의 코드에는 패치가 없을 수 있다. 이 문제를 줄이기 위해 여러 연구가 등장했다. V-SZZ는 취약점이 언제 들어왔는지 코드 이력에서 찾으려 했고, V0Finder는 취약점의 origin과 전파 관계를 보려 했다. AFV, VERJava, VISION, Clovery 같은 연구들은 패치와 코드 상태를 이용해 특정 버전이나 라이브러리가 실제로 affected인지 판단하려는 흐름에 있다. 최근의 *Vulnerability-Affected Versions Identification: How Far Are We*?는 이런 도구들을 tracing 계열과 matching 계열로 정리하며, affected version 식별이 여전히 패치 구조, 브랜치, 코드 변화, 제한적인 의미 분석 때문에 어려운 문제임을 보여준다. 결국 핵심은 간단하다. ```text NVD를 믿지 말자는 것이 아니다. NVD만 보고 끝내지 말자는 것이다. ``` 취약점 대응에서 중요한 판단을 내릴 때는 한 번 더 확인해야 한다. ```text 이 버전이 안전하다는 판단은 단순한 버전 범위에서 온 것인가, 아니면 실제 패치와 코드 상태까지 확인한 것인가? ``` 그 차이가 불필요한 패치 비용을 줄이고, 놓친 취약점이 운영 환경에 남는 일을 줄인다.