Via 记的是「这个报文经过了哪些代理」。格式是「协议版本 代理名」,每过一跳追加一项。
它是排查链路的第一手证据,但也可以被伪造或删掉。
第五章讲代理时已经用过它一次。 这一节把它当成一个字段来细看:格式有哪几种写法,以及怎么拿它做告警。
一、每一项的格式
每一项是「协议版本 代理名」,中间一个空格。代理名可以带一个括号注释,写明软件名和版本。
| 写法 | 含义 |
|---|---|
| 1.1 outer-proxy | HTTP/1.1,代理名叫 outer-proxy |
| 1.1 squid (squid/4.15) | 名字后面用括号补软件版本 |
| 1.0 gw | HTTP/1.0 的老网关 |
| 2 inner | HTTP/2(注意写的是 2,不是 2.0) |
最后一行是个小陷阱:HTTP/2 在 Via 里就写 2,不写 2.0。
因为 Via 的协议版本沿用「主版本.次版本」的写法,而 HTTP/2 没有次版本,所以只有一位。
二、顺序:最左是最近的
Via: 1.1 p1, 1.1 p2 表示请求先过 p1 再过 p2,还是先过 p2 再过 p1?
答案是先 p1 后 p2。每过一跳往右追加一项,所以最左边的是第一个经过的,最右边的是最后一个。
客户端看到这个头时,如果想找「离自己最近的代理」,看最左边那一项。
源服务器看到的顺序是整个链路从入口到出口的顺序。
三、拿它做劫持告警
思路很简单:维护一份「已知代理名单」,把响应里 Via 的每一项解析出来,名单之外的就报警。
Demo 里名单是 outer-proxy 和 inner-proxy,实际收到的两项都在名单里,告警为空。
如果中间多插了一个不认识的代理,就会出现在「名单外的」那一栏。
但这个办法有两个明确的局限。
一是可以伪造:攻击者可以自己写一个 Via: 1.1 outer-proxy 来伪装,也可以直接删掉 Via。
二是可以改写:有些设备不是追加而是覆盖,看到的结果就不可靠了。
所以 Via 只能当辅助证据。真正要确认链路,得靠出口侧的主动探测,或者对比不同位置的日志。
四、动手 Demo:两级代理的 Via 与名单比对
源服务器自己回一个 Via: 1.1 inner-proxy,外层代理追加 1.1 outer-proxy,客户端收到两项。
然后把每一项解析出来,跟一份已知名单比对。
# 源服务器
return reply(u'HTTP/1.1 200 OK',
[(u'Via', u'1.1 inner-proxy')], b'ok')
# 外层代理:追加自己
nv = hd.get(u'via', u'')
keep = [(u'Via', (nv + u', ' if nv else u'') + u'1.1 outer-proxy')]
KNOWN = {u'outer-proxy', u'inner-proxy'}
got = [x.strip().split(u' ')[-1].split(u'(')[0].strip()
for x in via.split(u',') if x.strip()]
unknown = [g for g in got if g not in KNOWN]
实跑输出:
--------------------------------------------------------------------
Via 的格式,以及怎么拿它做劫持告警
--------------------------------------------------------------------
客户端收到的 Via 1.1 inner-proxy
项数 1
顺序 最左是最先经过的,最右是最后经过的
每一项的格式是「协议版本 代理名」,代理名可以带注释:
1.1 outer-proxy 协议 1.1 | 名字 outer-proxy
1.1 outer-proxy (squid/4.15) 协议 1.1 | 名字 outer-proxy (squid/4.15)
1.0 gw 协议 1.0 | 名字 gw
2 inner 协议 2 | 名字 inner
注意 2 表示 HTTP/2,不是「版本 2.0」
用它做劫持告警的思路:维护一份「已知代理名单」,
响应里的 Via 出现名单外的项,就说明中间多了一个不认识的东西。
已知代理 inner-proxy、outer-proxy
这次出现的 inner-proxy
名单外的 没有
Via 是排查链路的第一手证据。它有两个局限要知道:
一是可以被伪造或干脆删掉,所以「没有 Via」不等于「没走代理」;
二是有些设备会改写而不是追加,看到的结果就不可靠了。
真正可靠的做法是在出口侧做主动探测,而不是只信报文里的自述。
客户端收到的 Via 是 1.1 inner-proxy, 1.1 outer-proxy,两项。
顺序值得注意:inner-proxy 在左,outer-proxy 在右。可客户端先碰到的是 outer,为什么它在右边?
因为源服务器先写了自己那一项,代理再往右追加——追加是从右往左「长出去」,
但读的时候从左往右是按经过顺序。
名单比对结果:已知 inner-proxy、outer-proxy,这次出现的也是这两个,名单外的为空。
输出里还试了四种写法:1.1 outer-proxy、1.1 squid (squid/4.15)、1.0 gw、
2 inner——最后那个 2 是 HTTP/2。
30 秒实验
- 在代理里把
Via改成直接覆盖而不是追加,看客户端还能不能看出有两跳。 - 手工在请求里加一个
Via: 1.1 fake,看源服务器会不会信。 - 想一想:如果攻击者能删掉
Via,你还怎么证明请求经过了代理?
常见误区
- 「
Via最右是最近的代理」——最左是最近的,按经过顺序从左往右。 - 「HTTP/2 在 Via 里写 2.0」——写
2。 - 「没有 Via 就说明没走代理」——
Via可以被删掉,也可能是设备没实现。
自测三问(点开对答案)
1. `Via` 每一项的格式是什么? —— 「协议版本 代理名」,代理名后面可以带括号注释,写明软件和版本。 2. 多项 `Via` 的顺序怎么读? —— 从左往右按请求经过的顺序,最左是第一个经过的代理。 3. 用 `Via` 做劫持告警有什么局限? —— 可以伪造、可以删掉、有些设备会改写而不是追加,所以只能当辅助证据。通用首部还剩最后一个:一个用来写「这份东西有问题」的字段。
- ☐ (本节完,下一节 6.3.9 Warning)