Skip to content
This repository was archived by the owner on Oct 16, 2021. It is now read-only.

Commit 496736f

Browse files
committed
http: opt-in insecure HTTP header parsing
Allow insecure HTTP header parsing. Make clear it is insecure. See: - nodejs/node#30553 - nodejs/node#27711 (comment) - nodejs/node#30515 PR-URL: nodejs/node#30567 Backport-PR-URL: nodejs/node#30473 Reviewed-By: Fedor Indutny <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent d41314e commit 496736f

File tree

8 files changed

+46
-4
lines changed

8 files changed

+46
-4
lines changed

doc/api/cli.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,16 @@ added: v9.0.0
420420
Specify the `module` of a custom [experimental ECMAScript Module][] loader.
421421
`module` may be either a path to a file, or an ECMAScript Module name.
422422

423+
### `--insecure-http-parser`
424+
<!-- YAML
425+
added: REPLACEME
426+
-->
427+
428+
Use an insecure HTTP parser that accepts invalid HTTP headers. This may allow
429+
interoperability with non-conformant HTTP implementations. It may also allow
430+
request smuggling and other HTTP attacks that rely on invalid headers being
431+
accepted. Avoid using this option.
432+
423433
### `--max-http-header-size=size`
424434
<!-- YAML
425435
added: v11.6.0
@@ -1043,6 +1053,7 @@ Node.js options that are allowed are:
10431053
* `--http-server-default-timeout`
10441054
* `--icu-data-dir`
10451055
* `--input-type`
1056+
* `--insecure-http-parser`
10461057
* `--inspect-brk`
10471058
* `--inspect-port`, `--debug-port`
10481059
* `--inspect-publish-uid`

doc/node.1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ Specify the
209209
as a custom loader, to load
210210
.Fl -experimental-modules .
211211
.
212+
.It Fl -insecure-http-parser
213+
Use an insecure HTTP parser that accepts invalid HTTP headers. This may allow
214+
interoperability with non-conformant HTTP implementations. It may also allow
215+
request smuggling and other HTTP attacks that rely on invalid headers being
216+
accepted. Avoid using this option.
217+
.
212218
.It Fl -max-http-header-size Ns = Ns Ar size
213219
Specify the maximum size of HTTP headers in bytes. Defaults to 8KB.
214220
.

lib/_http_client.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const {
3232
freeParser,
3333
parsers,
3434
HTTPParser,
35+
isLenient,
3536
prepareError,
3637
} = require('_http_common');
3738
const { OutgoingMessage } = require('_http_outgoing');
@@ -654,7 +655,8 @@ function tickOnSocket(req, socket) {
654655
req.socket = socket;
655656
req.connection = socket;
656657
parser.initialize(HTTPParser.RESPONSE,
657-
new HTTPClientAsyncResource('HTTPINCOMINGMESSAGE', req));
658+
new HTTPClientAsyncResource('HTTPINCOMINGMESSAGE', req),
659+
isLenient());
658660
parser.socket = socket;
659661
parser.outgoing = req;
660662
req.parser = parser;

lib/_http_common.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const { getOptionValue } = require('internal/options');
2929
const { methods, HTTPParser } =
3030
getOptionValue('--http-parser') === 'legacy' ?
3131
internalBinding('http_parser') : internalBinding('http_parser_llhttp');
32+
const insecureHTTPParser = getOptionValue('--insecure-http-parser');
3233

3334
const FreeList = require('internal/freelist');
3435
const incoming = require('_http_incoming');
@@ -238,6 +239,16 @@ function prepareError(err, parser, rawPacket) {
238239
err.message = `Parse Error: ${err.reason}`;
239240
}
240241

242+
let warnedLenient = false;
243+
244+
function isLenient() {
245+
if (insecureHTTPParser && !warnedLenient) {
246+
warnedLenient = true;
247+
process.emitWarning('Using insecure HTTP parsing');
248+
}
249+
return insecureHTTPParser;
250+
}
251+
241252
module.exports = {
242253
_checkInvalidHeaderChar: checkInvalidHeaderChar,
243254
_checkIsHttpToken: checkIsHttpToken,
@@ -250,5 +261,6 @@ module.exports = {
250261
parsers,
251262
kIncomingMessage,
252263
HTTPParser,
264+
isLenient,
253265
prepareError,
254266
};

lib/_http_server.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const {
3939
chunkExpression,
4040
kIncomingMessage,
4141
HTTPParser,
42+
isLenient,
4243
_checkInvalidHeaderChar: checkInvalidHeaderChar,
4344
prepareError,
4445
} = require('_http_common');
@@ -385,7 +386,8 @@ function connectionListenerInternal(server, socket) {
385386
// https://github.com/nodejs/node/pull/21313
386387
parser.initialize(
387388
HTTPParser.REQUEST,
388-
new HTTPServerAsyncResource('HTTPINCOMINGMESSAGE', socket)
389+
new HTTPServerAsyncResource('HTTPINCOMINGMESSAGE', socket),
390+
isLenient(),
389391
);
390392
parser.socket = socket;
391393

src/node_http_parser_impl.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ class Parser : public AsyncWrap, public StreamListener {
508508

509509
static void Initialize(const FunctionCallbackInfo<Value>& args) {
510510
Environment* env = Environment::GetCurrent(args);
511+
bool lenient = args[2]->IsTrue();
511512

512513
CHECK(args[0]->IsInt32());
513514
CHECK(args[1]->IsObject());
@@ -528,7 +529,7 @@ class Parser : public AsyncWrap, public StreamListener {
528529

529530
parser->set_provider_type(provider);
530531
parser->AsyncReset(args[1].As<Object>());
531-
parser->Init(type);
532+
parser->Init(type, lenient);
532533
}
533534

534535
template <bool should_pause>
@@ -812,12 +813,14 @@ class Parser : public AsyncWrap, public StreamListener {
812813
}
813814

814815

815-
void Init(parser_type_t type) {
816+
void Init(parser_type_t type, bool lenient) {
816817
#ifdef NODE_EXPERIMENTAL_HTTP
817818
llhttp_init(&parser_, type, &settings);
819+
llhttp_set_lenient(&parser_, lenient);
818820
header_nread_ = 0;
819821
#else /* !NODE_EXPERIMENTAL_HTTP */
820822
http_parser_init(&parser_, type);
823+
parser_.lenient_http_headers = lenient;
821824
#endif /* NODE_EXPERIMENTAL_HTTP */
822825
url_.Reset();
823826
status_message_.Reset();

src/node_options.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
392392
"(default: 120000)",
393393
&EnvironmentOptions::http_server_default_timeout,
394394
kAllowedInEnvironment);
395+
AddOption("--insecure-http-parser",
396+
"Use an insecure HTTP parser that accepts invalid HTTP headers",
397+
&EnvironmentOptions::insecure_http_parser,
398+
kAllowedInEnvironment);
395399
AddOption("--input-type",
396400
"set module type for string input",
397401
&EnvironmentOptions::module_type,

src/node_options.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ class EnvironmentOptions : public Options {
156156
bool print_eval = false;
157157
bool force_repl = false;
158158

159+
bool insecure_http_parser = false;
160+
159161
bool tls_min_v1_0 = false;
160162
bool tls_min_v1_1 = false;
161163
bool tls_min_v1_2 = false;

0 commit comments

Comments
 (0)