HTTP 的消息体到哪里结束?答案只有三种:Content-Length 写明长度、Transfer-Encoding: chunked 分块、或者干脆等连接关闭。这篇逐个看它们的字节、开销和用处,再故意写错长度,看错位和截断是怎么发生的——这也是请求走私类漏洞的根源。
响应头里:Transfer-Encoding = chunked ;Content-Length = None 消息体共 589 字节:1 个数据块,块大小 [577];实际内容 577 字节,框架开销 12 字节(块长度行 + 每块后的 CRLF + 结尾的 "0\r\n\r\n") 内容开头 b'<!doctype html><html lang=en><head><meta'…结尾 b'ript></body></html>\n';最后一个块之后 b'\r\n0\r\n\r\n' 是结束标记(长度 0 的块 + 空的 trailer + 空行)
每块:十六进制长度行 CRLF + 数据 + CRLF;长度 0 的块结束,可带 trailer。
Content-Length :消息体 100000 字节,框架开销 0 字节(0.00%) chunked,每块 100 字节 :消息体 106005 字节,框架开销 6005 字节(6.00%) chunked,每块 1 KiB :消息体 100691 字节,框架开销 691 字节(0.69%) chunked,每块 16 KiB :消息体 100060 字节,框架开销 60 字节(0.06%)
块越小开销越高:每块 100 字节 6.00%,16 KiB 0.06%。
chunked:客户端收到的数据(到达时刻 ms, 字节):(0, 66), (205, 26), (409, 26), (610, 26), (813, 26), (1016, 31) Content-Length(服务器要先生成完整内容,算出长度再发):第一个字节在 1001 ms 才到,共 159 字节,全部在同一时刻到达
chunked 允许边生成边发,不必先知道总长度。
100 字节在 0 ms 就到齐了,但客户端无从知道这是全部——一直等到服务器在 2001 ms 关闭连接才能确定。这样的响应不可能复用连接
没有长度的响应要等连接关闭才结束,无法复用连接,也无法发现被截断。
Content-Length: 1000,实际只发 600 字节就关闭连接 → IncompleteRead:已读 600 字节,还缺 400 chunked,发了一个块之后没有发结尾的 0 块就关闭连接 → IncompleteRead:已读 256 字节,还缺 None 没有长度,发 600 字节就关闭连接(close 定界) → 正常读到 600 字节(状态 200)(读起来一切正常,但客户端无法判断是否被截断) Content-Length: 500,实际发了 1000 字节 → 正常读到 500 字节(状态 200)(多出的 500 字节被客户端忽略 / 留在连接里) 响应错位演示:服务器对第 1 个请求声明 Content-Length: 5 却多发了 6 个字节 "WORLD-EXTRA"[:6]…客户端按长度读出第 1 个响应的 b'hello';紧接着的字节是 b'WORLD-EXTRAHTTP/1.1 200 OK\r\nContent-Leng',被当作“第 2 个响应”的开头——不是合法的状态行,连接上的响应从此错位(请求走私 / 响应拆分漏洞的根源)
写小了,多余字节污染后面的响应;截断在有长度的响应里能被发现。