뒤로

2026/11/10

Kyverno CVE-2026-124440: 정책 엔진이 클러스터 관리자 권한으로 가는 우회로가 될 때

Kyverno 1.19.1에서는 한 네임스페이스에서 정책 작성 권한을 가진 테넌트가 클러스터 관리자 권한에 도달할 수 있습니다. 우회 원리와 업그레이드 전 점검 사항을 설명합니다.

많은 팀이 Kubernetes 테넌트를 서로 분리하기 위해 Kyverno를 운영합니다. Kyverno는 API 서버가 객체를 저장하기 전에 모든 객체를 정책과 대조하며, 네임스페이스 소유자가 자신의 네임스페이스에 적용할 정책을 직접 작성하도록 허용할 수도 있습니다. 2026년 11월 8일에 공개된 CVE-2026-124440은 이러한 권한 위임이 어떻게 무너질 수 있는지 보여 줍니다. Kyverno 1.19.1에서는 자신의 네임스페이스에서만 정책을 생성할 수 있는 테넌트가 Kyverno로 하여금 해당 네임스페이스 밖에서 동작하게 만들고, 단계적으로 클러스터 관리자 권한에 도달할 수 있습니다.

수정 사항은 10월 20일부터 Kyverno 1.19.2로 제공되고 있습니다. 이 릴리스를 적용하지 않은 클러스터는 여전히 취약합니다. 이 글에서는 무엇이 잘못되었는지, 권한 상승이 어떻게 이루어지는지, 그리고 자체 클러스터에서 무엇을 점검해야 하는지 설명합니다.

핵심 요약

  • CVE-2026-124440(GitHub 보안 권고 GHSA-39w8-hv5g-f73w)은 Kyverno 1.19.1에 영향을 미치며 1.19.2에서 수정되었습니다. CVE 레코드에서는 CVSS 4.0 기준 9.4, CVSS 3.1 기준 9.9로 평가하고 있습니다.
  • 공격자에게 필요한 것은 자신의 네임스페이스에 네임스페이스 범위의 Kyverno Policy를 생성할 권한뿐입니다.
  • 정책의 apiCall은 Kyverno의 admission-controller ServiceAccount로 실행되며, 이 ServiceAccount는 다른 네임스페이스를 읽고 클러스터 전체에 적용되는 어드미션 웹훅을 생성할 수 있습니다. 이 경로가 클러스터 관리자 권한으로 이어집니다.
  • 보안 권고에는 임시 해결책이 제시되어 있지 않습니다. 1.19.2로 업그레이드한 다음, 누가 정책을 생성할 수 있는지와 어떤 웹훅이 존재하는지 검토하십시오.
  • 저희가 검토한 자료에서는 실제 환경에서의 악용 사례가 보고되지 않았습니다.

네임스페이스 클램프의 원래 역할

Kyverno 정책은 요청을 평가하는 동안 Kubernetes API에서 데이터를 가져올 수 있습니다. 이 기능을 apiCall이라고 합니다. 규칙에서 urlPath를 지정하면 Kyverno가 API 서버를 호출하고, 그 결과가 규칙에 입력됩니다. 이 호출은 정책 작성자의 권한이 아니라 Kyverno 자체의 ServiceAccount로 이루어집니다.

플랫폼 관리자가 작성하는 클러스터 전체 범위의 ClusterPolicy라면 이는 예상된 동작입니다. 하지만 테넌트에게 작성을 허용할 수 있는 네임스페이스 범위의 Policy에서는 위험 요소가 됩니다. 이전 보안 권고(GHSA-c5qq-7g2q-cpqp 및 GHSA-5qq8-67g6-4h2w)에서는 네임스페이스 범위의 Policy가 apiCall을 이용해 다른 네임스페이스를 읽거나 클러스터 범위 객체를 쓸 수 있음이 드러났습니다. 2026년 9월 10일에 릴리스된 Kyverno 1.19.1은 ‘네임스페이스 클램프’를 추가했습니다. 호출을 실행하기 전에 Kyverno가 경로가 정책 자신의 네임스페이스 안에 머무르는지 확인하는 방식입니다.

검사와 실제 동작이 어긋나는 지점

1.19.1의 클램프는 실제로 전송하는 문자열을 검사하지 않았습니다. 보안 권고에 따르면 클램프는 urlPath의 퍼센트 인코딩을 디코딩하고, 그 결과를 정규화한 뒤(.. 세그먼트가 해석되도록), 이렇게 정리된 사본에서 네임스페이스를 확인했습니다. 그러나 요청 자체는 원래의 가공되지 않은 경로로 전송되었습니다.

디코딩이 성공하는 한 이 방식은 문제없이 동작합니다. 그러나 끝부분의 쿼리 문자열에 단독 %가 있는 경우처럼 경로에 잘못된 형식의 퍼센트 이스케이프가 포함되면 디코딩이 실패합니다. 이때 1.19.1 코드는 아직 인코딩된 상태의 원본 문자열을 검사하는 방식으로 대체(fallback)했습니다. API 서버에는 이런 대체 처리가 없습니다. API 서버는 경로를 완전히 디코딩한 뒤 요청을 라우팅합니다. 그 결과 클램프는 테넌트 자신의 네임스페이스를 보게 되지만, API 서버는 그 네임스페이스를 벗어나 다른 네임스페이스나 클러스터 범위 컬렉션으로 올라가는 인코딩된 .. 세그먼트를 보게 됩니다.

보안 권고는 이것이 하나의 페이로드가 아니라 특정 유형의 입력 전체에 해당하는 문제라고 강조합니다. 특정 리터럴 문자열 하나를 차단해서는 막을 수 없습니다. 근본적인 해결책은 검증하는 경로와 실행하는 경로를 정확히 일치시키는 것입니다.

1.19.2가 바로 그렇게 합니다. 저희가 1.19.2 소스를 검토한 바에 따르면, Kyverno는 이제 경로의 정규 형식을 하나만 만들고, 정규화할 수 없는 경로는 거부하며, 그 형식으로 네임스페이스를 확인한 뒤 동일한 형식을 API 서버에 전송합니다. 같은 릴리스에서 수정된 관련 문제인 CVE-2026-122310(GHSA-3xfv-vq8x-qhg4)은 ../ 세그먼트를 # 또는 ? 뒤에 숨겨, 정리된 사본에 대한 검사와 원본 요청 사이의 같은 불일치를 악용했습니다. 이는 별도의 레코드를 가진 별개의 CVE입니다.

권한 상승 과정 단계별 분석

제보자는 Kubernetes 1.31에 기본 Helm 차트와 기본값으로 Kyverno 1.19.1을 설치한 환경에서 이 공격 과정을 시연했습니다.

  1. 네임스페이스 범위의 Policy를 생성합니다. 테넌트의 유일한 권한은 자신의 네임스페이스에서 policies.kyverno.io를 생성하는 것입니다. 정책에는 클램프를 통과하지만 다른 곳으로 라우팅되는 경로를 가진 apiCall이 들어 있습니다.
  2. 정책을 트리거합니다. 테넌트 네임스페이스의 ConfigMap처럼 정책과 일치하는 평범한 객체를 하나 생성하면, Kyverno가 규칙을 평가하고 admission-controller ServiceAccount로 호출을 실행합니다.
  3. 네임스페이스를 넘어 읽습니다. 같은 기법으로 다른 네임스페이스의 객체를 읽을 수 있으며, Kyverno 배포에 읽기 권한이 있다면 Secrets도 포함됩니다.
  4. 클러스터 전체에 적용되는 웹훅을 작성합니다. 이 ServiceAccount는 MutatingWebhookConfiguration과 ValidatingWebhookConfiguration을 생성할 수 있으므로, 테넌트는 자신의 웹훅을 등록할 수 있습니다. 이 웹훅은 모든 네임스페이스에서 어드미션 요청을 가로채고 다시 작성합니다.
  5. 클러스터 관리자 권한에 도달합니다. 보안 권고는 이러한 웹훅을 이용해 kube-system에서 생성되는 파드를 변조하고, 이를 통해 테넌트에게 cluster-admin 권한을 부여하는 방법을 설명합니다. 그 결과 기밀성, 무결성, 가용성이 모두 상실됩니다.

사용자 상호 작용은 필요 없으며, 웹훅은 일반적인 클러스터 활동에 의해 실행됩니다.

영향을 받는 대상

CVE 레코드는 Kyverno 1.19.1을 영향을 받는 버전으로, 1.19.2를 영향을 받지 않는 버전으로 명시합니다. 1.19.1 이전 버전에는 클램프 자체가 없기 때문에 이 우회 기법의 영향은 받지 않습니다. 그러나 클램프가 해결하려던 이전 보안 권고의 영향을 받으므로, 구버전에 머무르는 것은 안전한 대안이 아닙니다. 네임스페이스 소유자, CI 파이프라인 또는 셀프서비스 포털이 Kyverno Policy를 생성할 수 있는 환경에서 위험이 가장 큽니다.

Kyverno 1.19.2는 2026년 10월 20일에 릴리스되었으며, 릴리스 노트에는 24건의 GitHub 보안 권고(긴급 9건, 높음 14건, 보통 1건)가 나열되어 있습니다. 그중 여러 건은 API 경로 검증 및 네임스페이스 격리와 관련됩니다. 이 보안 권고는 사흘 뒤인 10월 23일에 공개되었지만, 보안 권고와 CVE 레코드 모두 1.19.2를 수정 버전으로 명시합니다. 각 보안 권고에는 고유한 식별자가 있으므로, 전체를 하나의 CVE로 취급하지 말고 하나씩 대조하십시오.

자체 클러스터에서 점검할 사항

먼저 Git 저장소에 있는 버전이 아니라 실제로 실행 중인 Kyverno 버전을 확인합니다.

kubectl get deployments -n kyverno -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.template.spec.containers[*].image}{"\n"}{end}'

이미지 태그 중 v1.19.1이 있다면 지금 바로 1.19.2로의 업그레이드를 계획하십시오. 그다음 네 가지를 점검합니다.

  • 네임스페이스 범위의 Policy를 생성할 수 있는 주체. 테넌트 ID로 직접 테스트하고, policies.kyverno.io를 부여하는 역할을 찾으십시오. 기본 제공 admin 및 edit 역할에 권한을 추가하는 집계 ClusterRole도 포함해야 합니다.
  • apiCall을 사용하는 기존 정책. 목록을 뽑아 모든 urlPath를 검토하십시오. 특히 %, .., ? 또는 #가 포함된 경로를 주의 깊게 보십시오.
  • 웹훅 구성. 목록을 예상 구성과 비교하십시오. Kyverno는 자체 웹훅을 생성하므로, 알아볼 수 없는 이름과 최근 생성 시각에 집중하십시오.
  • 감사 로그. Kyverno admission-controller ServiceAccount가 평소 패턴에서 벗어나 웹훅 구성을 생성하거나 Secrets를 읽은 요청이 있는지 찾아보십시오.
kubectl auth can-i create policies.kyverno.io -n team-a --as=system:serviceaccount:team-a:default
kubectl get policies.kyverno.io -A -o json | jq -r '.items[] | select([.spec.rules[]?.context[]? | select(.apiCall)] | length > 0) | "\(.metadata.namespace)/\(.metadata.name)"'
kubectl get mutatingwebhookconfigurations,validatingwebhookconfigurations -o custom-columns=NAME:.metadata.name,CREATED:.metadata.creationTimestamp

team-a는 실제 테넌트 네임스페이스와 ID로 바꾸십시오. 1.19.1을 실행했던 클러스터에서 설명할 수 없는 웹훅이 발견되면 잠재적 보안 사고로 취급하고, 무엇이든 삭제하기 전에 감사 로그를 보존하십시오.

더 넓은 교훈: 통제하는 쪽도 감사하라

어드미션 컨트롤러, 정책 엔진, 오퍼레이터는 클러스터에서 가장 높은 권한을 가진 구성 요소인 경우가 많습니다. 제 역할을 하려면 폭넓은 권한이 필요하고, 테넌트는 매일 이들과 상호 작용합니다. 그래서 매력적인 공격 대상이 됩니다. 이번 사례에서 테넌트 분리 모델은 단 하나의 검사에 의존했고, 검사와 요청이 서로 다르게 처리한 디코딩 단계 하나만으로 그 모델이 무너졌습니다.

프라이빗 클라우드 및 멀티테넌트 Kubernetes 플랫폼에서는 세 가지 습관이 도움이 됩니다. 정책 엔진을 공격 표면의 일부로 취급하고, 그 보안 릴리스를 Kubernetes 버전만큼 면밀히 추적하십시오. 예를 들어 정책에 필요하지 않다면 Secret 읽기 권한을 부여하지 않는 식으로, 해당 ServiceAccount에는 필요한 권한만 부여하십시오. 그리고 테넌트가 권한 위임을 통해 어떤 기능에 접근할 수 있는지 검토하십시오. 플랫폼 권한으로 실행되는 기능은 테넌트의 입력을 플랫폼의 동작으로 바꾸기 때문입니다.

참고 자료