HTTP request smuggling primitive: bare LF accepted as a request-line terminator in strict mode
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 55/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- node.js, typescript
調査の方向性
url.ts:176 から開始し、url.exit.toHTTP09 を headers_start まで追跡して、単独の LF のパスを、他の request-line terminator に使用されている LENIENT_OPTIONAL_CR_BEFORE_LF のチェックと比較します。strict-mode のバージョンなしリクエストとバージョンありリクエストに対する回帰テストを追加し、その後、strict mode が HTTP/0.9 ヘッダーや body をパースせずに単独の LF のケースを拒否することを確認します。
索引モデルが issue の本文から書いたものです。
説明
llhttp_set_lenient_optional_cr_before_lf documents that llhttp "would error when a LF is not
preceded by CR when terminating the request line", and that relaxing this exposes request smuggling.
url.ts:176 exits on a bare \n straight to the HTTP/0.9 adapter with no
LENIENT_OPTIONAL_CR_BEFORE_LF check — the only line terminator in the grammar that is ungated.
url.exit.toHTTP09 then continues into headers_start with no http_minor guard, so the message is
labelled HTTP/0.9 but headers and a Content-Length body are still parsed.
PoC
const http = require('http'), net = require('net');
const srv = http.createServer((req, res) => { // default parser, no options
let body = '';
req.on('data', c => body += c);
req.on('end', () => {
console.log(` ACCEPTED HTTP/${req.httpVersion} ${req.method} ${req.url}` +
` headers=${JSON.stringify(req.headers)} body=${JSON.stringify(body)}`);
res.end('ok');
});
});
srv.on('clientError', e => console.log(` REJECTED ${e.code}`));
const cases = [
['versionless + bare LF', 'GET /x\nHost: a\r\nContent-Length: 5\r\n\r\nhello'],
['versioned + bare LF', 'GET /x HTTP/1.1\nHost: a\r\n\r\n'],
];
srv.listen(0, async () => {
for (const [name, raw] of cases) {
console.log(name);
await new Promise(done => {
const c = net.connect(srv.address().port, '127.0.0.1', () => c.write(raw));
c.on('close', done); c.on('error', done);
setTimeout(() => c.destroy(), 300);
});
}
srv.close();
});
versionless + bare LF
ACCEPTED HTTP/0.9 GET /x headers={"host":"a","content-length":"5"} body="hello"
versioned + bare LF
REJECTED HPE_INVALID_VERSION
The same terminator is rejected when a version is present and accepted when it is absent. The
accepted message is reported as HTTP/0.9 yet carries headers and a body, neither of which HTTP/0.9
defines.
Scope, stated plainly: this path sets keepalive=0, and a pipelined follow-up request is rejected
with HPE_CLOSED_CONNECTION, so on its own it is a divergence from the documented strict-mode
guarantee rather than a demonstrated desync. Two requests parse on one connection only with
lenient_keep_alive also enabled.
- 主要言語
- TypeScript
- スター
- 1.9k
- フォーク
- 237
- PR マージ指標
- 30日以内にマージされた PR はありません
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
nodejs/llhttp のほかの issue
-
難易度 4/5 3〜5日 初心者へのやさしさ 52/100
-
難易度 3/5 1〜2日 初心者へのやさしさ 54/100
-
難易度 5/5 1週間以上 初心者へのやさしさ 32/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 45/100
-
難易度 4/5 3〜5日 初心者へのやさしさ 38/100
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
-
area:tools bug good first issue help wanted priority:P2
難易度 2/5 1〜3時間 初心者へのやさしさ 90/100
TaewoooPark/Motifcode#14 ·
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
newrelic-experimental/preflight#793 · コメント 1 件 ·
-
bug 🐞
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
-
[Bounty proposal] fix(web): memory insights count an evening memory on the next day ($25 proposed) オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
BasedHardware/omi#15320 ·