FlawOpen

flawopen.com/Teardowns/cve-2026-76504-cisco-sdwan-api-authentication-bypass

● CVE-2026-76504 · CVSS 9.8[1] · 심각
보안 연구 · FlawOpen

CVE-2026-76504: Cisco SD-WAN Manager 인증 우회

CVE-2026-76504는 인증되지 않은 원격 공격자가 Cisco Catalyst SD-WAN Manager의 API 인증 규칙을 우회해 관리자 권한으로 API에 접근할 수 있게 합니다. Cisco는 실제 공격을 확인했습니다. 해당 브랜치의 수정 버전으로 업그레이드하고 시스템이 안전하다고 판단하기 전에 Manager를 조사하세요.[1], [5]

CVSS 벡터CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H[1]
💡 알기 쉬운 설명 (ELI5)

주소를 한 가지 표기로만 인식하는 보안 출입문을 떠올려 보세요. 공격자가 URI 인코딩으로 표기를 바꾸면 시스템의 한 부분은 같은 문으로 인식하지만 보안 규칙은 다르게 처리합니다. Cisco에 따르면 이 불일치로 요청이 인증을 건너뛰고 관리자처럼 API에 접근할 수 있었습니다. 주소를 일관되게 처리하고 같은 접근 규칙을 적용해야 합니다. 운영자가 사용할 수 있는 Cisco의 공식 해결책은 소프트웨어 업그레이드입니다.[1]

핵심 개념 및 용어

URI 인코딩
URL 경로의 문자를 퍼센트 인코딩 바이트로 나타내는 방식입니다. 모든 계층이 경로를 일관되게 디코딩하고 비교해야 합니다.[1], [7]
인증 우회
시스템이 요구된 방식으로 호출자의 신원을 확인하지 않았는데도 요청이 보호된 기능에 도달하는 것입니다.[1]
API 세션 규칙
요청이 엔드포인트에 도달하기 전에 유효한 인증 세션이 있는지 확인하는 Manager의 규칙입니다.[1]
CWE-177
URL 인코딩 처리 오류입니다. 인코딩된 URL 데이터를 일관되지 않게 처리하면 보안 판단이 무력화될 수 있습니다. Cisco가 이 CVE에 할당한 약점입니다.[1], [7]

프레임워크 매핑

근본 원인 분석 (Root Cause)

Cisco는 CVE-2026-76504의 원인을 Catalyst SD-WAN Manager API 세션 인증의 HTTP 요청 URI 인코딩 처리 오류로 설명합니다. 조작된 요청은 특정 API 엔드포인트를 제한하는 인증 규칙을 우회하고 관리자 권한으로 접근할 수 있습니다. Cisco는 CVSS 3.1 9.8 Critical로 평가하고, 시스템 구성과 관계없이 제품이 영향을 받으며 PSIRT가 2026년 9월 실제 공격을 인지했다고 밝혔습니다. 권고문은 URI 인코딩된 j_security_check 요청을 IOC 예시로 제시하고 URI의 한 문자만 인코딩해도 악용될 수 있다고 명시합니다. 이 예시를 완전한 탐지 시그니처로 간주하지 마세요. Cisco는 취약한 소스 코드나 패치 diff를 공개하지 않으므로 아래 코드 쌍은 권한 확인 설계의 예시일 뿐 Cisco 코드나 재현 코드가 아닙니다. Cisco는 브랜치별 수정 버전을 안내하며 완전한 해결책이 되는 우회 방법은 없다고 말합니다. 네트워크 제한과 Live Protect 보호는 임시 완화책이며, 보호 기능은 정상 로그인을 방해할 수 있고 업그레이드를 대체하지 않습니다.[1], [5]

배포 유형별 CVE-2026-76504 수정 릴리스

배포 / 릴리스 계열수정된 릴리스필수 조치
20.9 이전 온프레미스 릴리스수정 릴리스가 제공된 지원 계열로 이동하세요.마이그레이션하고 지원되지 않는 이전 계열을 계속 사용하지 마세요.
온프레미스 20.9 계열20.9.10.1각 Manager를 이 버전 또는 지원 계열의 이후 버전으로 업그레이드하세요.
온프레미스 20.12 계열20.12.8.2각 Manager를 이 버전 또는 지원 계열의 이후 버전으로 업그레이드하세요.
온프레미스 20.15 계열20.15.6.1각 Manager를 이 버전 또는 지원 계열의 이후 버전으로 업그레이드하세요.
온프레미스 20.18 계열20.18.4.1각 Manager를 이 버전 또는 지원 계열의 이후 버전으로 업그레이드하세요.
온프레미스 26.1 계열26.1.2.1각 Manager를 이 버전 또는 지원 계열의 이후 버전으로 업그레이드하세요.
온프레미스 26.2 계열26.2.1각 Manager를 이 버전 또는 지원 계열의 이후 버전으로 업그레이드하세요.
Cisco SD-WAN Cloud (Cisco Managed)20.15.605 이상이며 Cisco는 호스팅 서비스가 수정됐다고 밝혔습니다.관리형 서비스 사용 여부를 확인하세요. 이 호스팅 업데이트에는 고객 조치가 필요하지 않습니다.

단계별 공격 실행 흐름

단계 1

원격 요청이 Manager API에 도달합니다

계정이나 사용자 상호작용은 필요하지 않습니다. 인터넷 노출은 위험을 높이며 공급업체는 구성과 관계없이 취약하다고 밝힙니다.[1]

단계 2

URI 인코딩된 경로가 인증 규칙을 우회합니다

Cisco는 API 세션 인증에서 URI 인코딩 처리가 일관되지 않다고 설명합니다. 공개 자료에는 취약한 구현이 공개되지 않았습니다.[1]

단계 3

API가 요청을 관리자 요청으로 처리합니다

악용에 성공하면 관리자 권한을 얻습니다. Cisco는 실제 공격을 확인했지만 접근 후 전체 공격 체인은 공개하지 않았습니다.[1], [5]

단계 4

업그레이드하고 조사합니다

admin-tech 파일을 수집하고 모든 Manager를 적절한 수정 버전으로 업그레이드한 뒤 Cisco TAC에 증거 검토를 요청하세요. TAC 응답을 기다리며 업그레이드를 미루지 마세요.[1], [2]

소스 코드 비교: 취약한 구현 vs 보안 패치

Cisco는 취약한 소스 코드나 패치 diff를 공개하지 않았습니다. 이 Python 예시는 일반적인 설계 원칙이며 Cisco 코드가 아닙니다. 라우팅과 권한 확인에 검증된 동일한 정규화 요청 대상을 사용하고 보호된 엔드포인트에는 인증된 주체를 요구해야 합니다. router/framework의 문서화된 정규화 규칙을 따르고 임의 디코딩이나 이중 디코딩을 추가하지 마세요. CVE-2026-76504의 지원되는 수정 방법은 Cisco가 해당 브랜치에 지정한 수정 버전으로 업그레이드하는 것입니다.[1], [7]

소스 코드 발췌: URI 정규화 및 권한 확인 경계의 Python 예시이며 Cisco 소스 코드가 아닙니다

취약한 구현
from urllib.parse import unquote, urlsplit


def dispatch(raw_target, session, routes):
    # 한 계층은 아직 인코딩된 경로를 검사합니다.
    path_for_auth = urlsplit(raw_target).path
    if path_for_auth.startswith("/api/") and session is None:
        raise PermissionError("authentication required")

    # 후속 계층은 경로 조회 전에 디코딩하므로 두 판단이 서로 달라질 수 있습니다.
    route_path = unquote(path_for_auth)
    return routes[route_path]()
보안 강화 패치
import re
from urllib.parse import unquote, urlsplit


def canonical_path(raw_target):
    raw_path = urlsplit(raw_target).path
    if re.search(r"%(?![0-9A-Fa-f]{2})", raw_path):
        raise ValueError("malformed URI encoding")

    path = unquote(raw_path, errors="strict")
    if "%" in path or "\\" in path:
        raise ValueError("ambiguous path encoding")
    if any(part in {".", ".."} for part in path.split("/")):
        raise ValueError("path traversal segment")
    return path


def dispatch(raw_target, session, routes):
    path = canonical_path(raw_target)  # Decode and validate exactly once.
    route = routes.get(path)
    if route is None:
        raise LookupError("route not found")

    requires_authentication, handler = route
    if requires_authentication and session is None:
        raise PermissionError("authentication required")
    return handler()

엔지니어링 및 시스템 보안 강화 체크리스트

출처