# 拦截 TLS、放行 RDP ## 问题 RDP 不是一连上就开始 TLS:客户端先在 TPKT 里发一个 X.224 Connection Request,服务端回一个 Connection Confirm,双方借此协商安全协议([MS-RDPBCGR] 1.3.1.1),协商完才在同一条连接上开始 TLS 握手。 `tls` 分析器以前要求流的第一个字节就是 TLS record(`0x16`),碰到 RDP 开头的 `03 00` 直接放弃。结果是: - RDP 里的 TLS 对 `tls` 规则完全不可见,没法按 RDP 单独写规则; - 反过来,任何流量只要在 TLS 前面垫一段假的 X.224 协商,就能绕过"拦截所有 TLS"的规则。 ## 做法 `tls` 分析器现在把 RDP 协商当作 hello 之前可选的一段前导来解析: - 第一个字节是 `0x16`:和以前一样,直接当 TLS 解析。 - 第一个字节是 `0x03`:按 TPKT + X.224 解析。客户端方向要求是 Connection Request,服务端方向要求是 Connection Confirm;长度、class、协商结构任何一处不对都视为非法,分析器取消。 - 前导解析完后照常解析 ClientHello / ServerHello,record 分片重组等逻辑完全一样。 协商一解析出来就上报,不用等 TLS: | 属性 | 类型 | 说明 | | --- | --- | --- | | `tls.rdp_req.cookie` | string | Connection Request 里的 cookie,去掉 `Cookie: ` 前缀和 CRLF,如 `mstshash=alice`;没有 cookie 时不存在 | | `tls.rdp_req.protocols` | int | RDP_NEG_REQ 里客户端请求的协议位图;没有 RDP_NEG_REQ 时为 0 | | `tls.rdp_resp.protocol` | int | RDP_NEG_RSP 里服务端选定的协议;没有 RDP_NEG_RSP 时为 0 | | `tls.rdp_resp.failure` | int | 服务端回的是 RDP_NEG_FAILURE 时的失败码(此时没有 `protocol`) | 协议取值: | 值 | 名称 | 是否走 TLS | | --- | --- | --- | | 0 | PROTOCOL_RDP(标准 RDP 安全,RC4) | 否 | | 1 | PROTOCOL_SSL | 是 | | 2 | PROTOCOL_HYBRID(CredSSP / NLA) | 是 | | 4 | PROTOCOL_RDSTLS | 是 | | 8 | PROTOCOL_HYBRID_EX | 是 | | 16 | PROTOCOL_RDSAAD | 是 | ## 规则 规则按顺序匹配、第一条命中的生效,所以放行 RDP 的规则要写在拦截 TLS 的前面: ```yaml - name: allow-rdp-tls action: allow log: true expr: > tls != nil && tls.req != nil && tls.rdp_req != nil && tls.rdp_resp != nil && tls.rdp_resp.protocol in [1, 2, 4, 8, 16] - name: block-tls action: block log: true expr: tls != nil && tls.req != nil ``` 放行条件同时要求: - **`tls.req != nil`**:ClientHello 已经完整解析。只看到协商就放行的话,协商之后跟什么都行。 - **`tls.rdp_resp != nil` 且 `protocol` 是走 TLS 的协议**:真实的 RDP 只有在服务端选了 TLS 类协议之后才会发 ClientHello。服务端选了 PROTOCOL_RDP 或者回了协商失败,后面却出现 TLS,说明是伪造的,交给 `block-tls` 拦掉。 正常 RDP 的时序是 CR → CC → ClientHello,ClientHello 到的时候 `rdp_resp` 早已上报,不存在规则先看到 `req` 再看到 `rdp_resp` 的竞争。 标准 RDP 安全(`protocol == 0`)的连接里根本没有 TLS,两条规则都不会命中,按默认放行。 ### 收紧放行条件 上面的规则只验证"协商格式正确、协商结果和后续 TLS 一致"。客户端和服务端串通的话,完全可以按规范发一套协商再跑任意 TLS,所以按需叠加: ```yaml # 只放行标准端口 expr: ... && port.dst == 3389 # 只放行指定服务器 expr: ... && ip.dst in ["10.0.0.10", "10.0.0.11"] # 要求客户端带 mstshash cookie(mstsc 通常会带,但不保证,先观察日志再启用) expr: ... && tls.rdp_req.cookie != nil && tls.rdp_req.cookie startsWith "mstshash=" ``` ## 行为变化 **以前按 `tls != nil && tls.req != nil` 写的规则现在会命中 RDP。** 以前 RDP 里的 TLS 看不到,所以不受影响;现在看得到了。不想拦 RDP 的话,必须像上面那样在前面加一条放行规则。 ## 局限 **只认 RDP 这一种前导。** 其他协议里的 STARTTLS(SMTP、IMAP、XMPP 等)和任何非 TLS、非 TPKT 开头的前缀仍然会让 `tls` 分析器直接放弃。要做到"拦截所有 TLS",还需要配合 `fet` 等不依赖协议开头的检测。 **RD Gateway 不在此列。** 经由 RD 网关的 RDP 跑在 443 端口的 HTTPS 上,开头就是普通 TLS,没有 X.224 协商,只能按 SNI / IP 放行。 **RDP 的 UDP 传输不在此列。** RDP-UDP 用的是 DTLS,走 UDP,与 `tls` 分析器无关。