Skip to content

Commit d4bcc5f

Browse files
committed
http: opt-in insecure HTTP header parsing
Allow insecure HTTP header parsing. Make clear it is insecure. See: - nodejs#30553 - nodejs#27711 (comment) - nodejs#30515 PR-URL: nodejs#30567 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 fbe3c63 commit d4bcc5f

8 files changed

+46
-4
lines changed

doc/api/cli.md

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

424+
### `--insecure-http-parser`
425+
<!-- YAML
426+
added: REPLACEME
427+
-->
428+
429+
Use an insecure HTTP parser that accepts invalid HTTP headers. This may allow
430+
interoperability with non-conformant HTTP implementations. It may also allow
431+
request smuggling and other HTTP attacks that rely on invalid headers being
432+
accepted. Avoid using this option.
433+
424434
### `--max-http-header-size=size`
425435
<!-- YAML
426436
added: v11.6.0
@@ -1072,6 +1082,7 @@ Node.js options that are allowed are:
10721082
* `--http-server-default-timeout`
10731083
* `--icu-data-dir`
10741084
* `--input-type`
1085+
* `--insecure-http-parser`
10751086
* `--inspect-brk`
10761087
* `--inspect-port`, `--debug-port`
10771088
* `--inspect-publish-uid`

doc/node.1

+6
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

+3-1
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');
@@ -655,7 +656,8 @@ function tickOnSocket(req, socket) {
655656
req.socket = socket;
656657
req.connection = socket;
657658
parser.initialize(HTTPParser.RESPONSE,
658-
new HTTPClientAsyncResource('HTTPINCOMINGMESSAGE', req));
659+
new HTTPClientAsyncResource('HTTPINCOMINGMESSAGE', req),
660+
isLenient());
659661
parser.socket = socket;
660662
parser.outgoing = req;
661663
req.parser = parser;

lib/_http_common.js

+12
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

+3-1
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

+5-2
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ class Parser : public AsyncWrap, public StreamListener {
495495

496496
static void Initialize(const FunctionCallbackInfo<Value>& args) {
497497
Environment* env = Environment::GetCurrent(args);
498+
bool lenient = args[2]->IsTrue();
498499

499500
CHECK(args[0]->IsInt32());
500501
CHECK(args[1]->IsObject());
@@ -515,7 +516,7 @@ class Parser : public AsyncWrap, public StreamListener {
515516

516517
parser->set_provider_type(provider);
517518
parser->AsyncReset(args[1].As<Object>());
518-
parser->Init(type);
519+
parser->Init(type, lenient);
519520
}
520521

521522
template <bool should_pause>
@@ -799,12 +800,14 @@ class Parser : public AsyncWrap, public StreamListener {
799800
}
800801

801802

802-
void Init(parser_type_t type) {
803+
void Init(parser_type_t type, bool lenient) {
803804
#ifdef NODE_EXPERIMENTAL_HTTP
804805
llhttp_init(&parser_, type, &settings);
806+
llhttp_set_lenient(&parser_, lenient);
805807
header_nread_ = 0;
806808
#else /* !NODE_EXPERIMENTAL_HTTP */
807809
http_parser_init(&parser_, type);
810+
parser_.lenient_http_headers = lenient;
808811
#endif /* NODE_EXPERIMENTAL_HTTP */
809812
url_.Reset();
810813
status_message_.Reset();

src/node_options.cc

+4
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
396396
"(default: 120000)",
397397
&EnvironmentOptions::http_server_default_timeout,
398398
kAllowedInEnvironment);
399+
AddOption("--insecure-http-parser",
400+
"Use an insecure HTTP parser that accepts invalid HTTP headers",
401+
&EnvironmentOptions::insecure_http_parser,
402+
kAllowedInEnvironment);
399403
AddOption("--input-type",
400404
"set module type for string input",
401405
&EnvironmentOptions::module_type,

src/node_options.h

+2
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ class EnvironmentOptions : public Options {
157157
bool print_eval = false;
158158
bool force_repl = false;
159159

160+
bool insecure_http_parser = false;
161+
160162
bool tls_min_v1_0 = false;
161163
bool tls_min_v1_1 = false;
162164
bool tls_min_v1_2 = false;

0 commit comments

Comments
 (0)