4.4 KiB
拦截 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 的前面:
- 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,所以按需叠加:
# 只放行标准端口
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 分析器无关。