Skip to content
KitploitKITPLOIT
工具博客
Log in
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-0257 — Palo Alto Networks PAN-OS 的 GlobalProtect 门户和网关存在认证绕过漏洞,允许攻击者建立未经授权的 VPN 连接,利用此漏洞需要访问门户或网关的网络权限。 | Kitploit
工具/GitHubGitHub/tushargurav28/cve-2026-0257
漏洞分析漏洞利用Web应用程序漏洞利用网络安全密码学渗透测试身份验证红队
GitHubtushargurav28/cve-2026-0257

CVE-2026-0257

Palo Alto Networks PAN-OS 的 GlobalProtect 门户和网关存在认证绕过漏洞,允许攻击者建立未经授权的 VPN 连接,利用此漏洞需要访问门户或网关的网络权限。

查看仓库
3113个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2026-0257:GlobalProtect 身份验证绕过

概述

此漏洞利用仅通过服务器的公开可用 TLS 证书伪造身份验证 cookie,实现对 Palo Alto GlobalProtect 网关/门户的未经身份验证的 VPN 访问。


核心漏洞(为何有效)

GlobalProtect 使用预身份验证 cookie(portal-userauthcookie)允许客户端进行身份验证。以下是致命缺陷:

正常流程:
  1. 客户端进行身份验证(用户名 + 密码)
  2. 服务器生成 cookie → 使用服务器的 RSA 公钥加密
  3. 客户端存储加密后的 cookie
  4. 重新连接时,客户端发送 cookie → 服务器使用私钥解密 → 信任它

Bug:
  服务器仅检查 cookie 是否能用其私钥成功解密。
  它不会验证是谁加密的,也不验证明文内容是否合法。

由于 RSA 公钥嵌入在服务器的 TLS 证书中(任何连接者均可公开获取),任何攻击者都可以:

  1. 从 TLS 证书中获取公钥
  2. 使用任意用户名伪造 cookie
  3. 用公钥加密
  4. 发送给服务器 → 服务器解密 → 视为有效接受

这是一个教科书式的身份验证缺陷——在需要数字签名或 HMAC 的地方使用了加密。


漏洞利用链(5 步)

flowchart TD
    A["步骤1:原始 TCP 连接"] --> B["步骤2:发送精心构造的 TLS ClientHello"]
    B --> C["步骤3:解析 ServerHello → 提取 DER 证书"]
    C --> D["步骤4:遍历 ASN.1 提取 RSA 公钥"]
    D --> E["步骤5:伪造 PKCS#1 v1.5 加密 cookie"]
    E --> F["步骤6:POST 到 /ssl-vpn/login.esp"]
    F --> G{"服务器解密 cookie"}
    G -->|"有效明文"| H[" 身份验证绕过 — VPN 访问已授予"]
    G -->|"无效"| I[" 拒绝"]

步骤1:构建原始 TLS ClientHello

为什么要用原始方式? 我们需要服务器的 DER(原始二进制)格式证书。Python 的 ssl 模块内部完成完整 TLS 握手,并且不会以同样的方式暴露原始证书字节。通过进行原始 TCP 连接并发送手工构造的 ClientHello,我们可以在字节级别拦截服务器的响应。

网络格式

TLS 记录如下所示:

┌──────────────────────────────────────────────────┐
│ TLS 记录头部(5 字节)                              │
│ ┌──────┬──────────┬────────────┐                 │
│ │ 类型 │ 版本    │  长度      │                 │
│ │ 0x16 │ 0x03 01 │  2 字节    │                 │
│ │(握手)│(TLS 1.0)│            │                 │
│ └──────┴──────────┴────────────┘                 │
│                                                  │
│ 握手消息                                         │
│ ┌──────┬────────────┬─────────────────────────┐  │
│ │ 类型 │  长度      │      正文               │  │
│ │ 0x01 │  3 字节    │  (ClientHello)          │  │
│ │(CHlo)│            │                         │  │
│ └──────┴────────────┴─────────────────────────┘  │
└──────────────────────────────────────────────────┘

代码:build_hello()

ClientHello 正文包含:

字段值用途
版本0x03 0x03 (TLS 1.2)告知服务器我们支持 TLS 1.2
随机数4 字节时间戳 + 28 个随机字节握手的随机数
会话 ID0x00 (空)无会话恢复
密码套件9 个套件,包括 TLS_RSA_WITH_AES_128_CBC_SHA关键:我们包含仅 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)。这会促使服务器使用 RSA 证书而非 ECDSA 证书进行响应——这对漏洞利用至关重要,因为该漏洞仅适用于 RSA。


步骤2:接收并解析服务器的响应

发送 ClientHello 后,服务器返回多个 TLS 记录:

服务器响应:
  ┌─────────────────┐
  │ ServerHello      │  (握手类型 2)
  ├─────────────────┤
  │ Certificate      │  (握手类型 11) ← 我们需要这个
  ├─────────────────┤
  │ ServerKeyExchange│  (握手类型 12, 可选)
  ├─────────────────┤
  │ ServerHelloDone  │  (握手类型 14) ← 停止信号
  └─────────────────┘

代码:parse_certs()

阶段 1 — 剥离 TLS 记录头部:

每个 TLS 记录有一个 5 字节头部:[类型(1)] [版本(2)] [长度(2)]。代码扫描所有记录,对于类型为 22(握手)的记录,拼接其有效载荷:

while i + 5 <= len(data):
    t = data[i]                              # 内容类型
    rl = (data[i + 3] << 8) | data[i + 4]   # 记录长度
    if t == 22:                              # 握手
        hs.extend(data[i + 5: i + 5 + rl])  # 抓取有效载荷
    i += 5 + rl                              # 下一记录

阶段 2 — 查找证书消息(类型 11):

在握手流内部,每个消息有一个 4 字节头部:[类型(1)] [长度(3)]。我们扫描类型为 11 的消息:

while j + 4 <= len(hs):
    ht = hs[j]                                           # 握手类型
    hl = (hs[j+1] << 16) | (hs[j+2] << 8) | hs[j+3]    # 3 字节长度
    if ht == 11:  # Certificate!
        # 解析内部的证书列表

阶段 3 — 提取单个 DER 证书:

证书消息包含一个证书列表,每个证书前面有一个 3 字节的长度:

证书消息正文:
┌───────────────────────────────────┐
│ 证书总长度(3 字节)               │
├───────────────────────────────────┤
│ 证书 1 长度(3 字节)             │
│ 证书 1 DER 数据(可变长度)       │
├───────────────────────────────────┤
│ 证书 2 长度(3 字节)             │
│ 证书 2 DER 数据(可变长度)       │
├───────────────────────────────────┤
│ ...                               │
└───────────────────────────────────┘

在我们的测试运行中,我们得到了 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 中的每个元素是:

┌─────┬────────┬───────────────────┐
│ 标签 │ 长度  │ 值(有效载荷)    │
│ 1B  │ 1-5B  │ 可变             │
└─────┴────────┴───────────────────┘

长度编码:

  • 如果字节 < 0x80:长度就是该字节本身(短格式)
  • 如果字节 ≥ 0x80:低 7 位表示后续编码长度的字节数(长格式)
# 示例:长度字节 = 0x82 → 后面还有 2 个字节
# 接下来 2 个字节:0x06 0x4F → 长度 = 0x064F = 1615 字节

代码:rd_tl()

def rd_tl(d, p):
    tag = d[p]; p += 1
    length = d[p]; p += 1
    if length & 0x80:                    # 长格式?
        nb = length & 0x7F              # 后续字节数
        length = 0
        for _ in range(nb):
            length = (length << 8) | d[p]
            p += 1
    return {"tag": tag, "len": length, "pos": p}  # pos = 值的起始位置

X.509 证书结构

Certificate ::= SEQUENCE {              ← 标签 0x30
  tbsCertificate SEQUENCE {              ← 标签 0x30
    version      [0] EXPLICIT            ← 标签 0xA0 (可选)
    serialNumber INTEGER                 ← 标签 0x02
    signature    SEQUENCE (AlgorithmID)  ← 标签 0x30
    issuer       SEQUENCE                ← 标签 0x30
    validity     SEQUENCE                ← 标签 0x30
    subject      SEQUENCE                ← 标签 0x30
    subjectPublicKeyInfo SEQUENCE {      ← 标签 0x30  ★ 我们需要这个 ★
      algorithm SEQUENCE {               ← 标签 0x30
        algorithm OID                    ← 标签 0x06
        parameters (可选)
      }
      subjectPublicKey BIT STRING {      ← 标签 0x03
        RSAPublicKey SEQUENCE {          ← 标签 0x30
          modulus    INTEGER             ← 标签 0x02  ★ n ★
          exponent   INTEGER             ← 标签 0x02  ★ e ★
        }
      }
    }
    ...
  }
  ...
}

代码:get_rsa_key()

该函数通过读取标签+长度并跳过不需要的字段来遍历 DER 树:

# 进入外部 SEQUENCE (Certificate)
r = rd_tl(der, 0)            # → SEQUENCE
# 进入 tbsCertificate SEQUENCE
r = rd_tl(der, p)            # → SEQUENCE
# 检查可选的版本标签
r = rd_tl(der, p)
if r["tag"] == 0xA0:         # 版本字段存在 → 跳过
    p = r["pos"] + r["len"]
    r = rd_tl(der, p)

# 跳过:serial → sigAlg → issuer → validity → subject
# (只需读取每个 TLV 并跳过去)

# 现在我们在 subjectPublicKeyInfo
# 读取 AlgorithmIdentifier → 检查 OID 是否为 RSA
oid = der[r["pos"]: r["pos"] + r["len"]]
rsa_oid = [0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01]
#          ↑ 这是 1.2.840.113549.1.1.1 = rsaEncryption
if oid != rsa_oid:
    return None  # 不是 RSA(可能是 ECDSA)

# 读取 BIT STRING → 跳过 1 字节(未使用位指示符)
# 读取内部 SEQUENCE → 提取 modulus (n) 和 exponent (e)

重要细节——模数中的前导零字节:

if der[ms] == 0 and ml > 1:
    ms += 1    # 去掉前导 0x00
    ml -= 1

DER 将整数编码为有符号数。如果模数的高位为 1,则会预加一个 0x00 字节以保持正数。我们去掉它,因为我们需要原始无符号值。

对于我们的目标: 模数 = 2048 位(256 字节),指数 = 65537(0x10001)


步骤4:伪造身份验证 cookie(PKCS#1 v1.5)

这是漏洞利用的核心。

Cookie 包含的内容

明文 cookie 格式为:

admin;;Windows;;1748928001;0.0.0.0
  │        │        │        │
  │        │        │        └── 客户端 IP
  │        │        └── Unix 时间戳
  │        └── 操作系统标识符
  └── 用户名(我们选择 "admin")

PKCS#1 v1.5 加密填充(类型 2)

在进行 RSA 加密之前,必须将明文填充到密钥长度(对于 2048 位 RSA 为 256 字节):

下载工具