| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 98259dc commit 6544cfb
11 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2595,6 +2595,17 @@ added: v8.4.0 | |||
| 2595 | 2595 | Returns a [HTTP/2 Settings Object][] containing the deserialized settings from | |
| 2596 | 2596 | the given `Buffer` as generated by `http2.getPackedSettings()`. | |
| 2597 | 2597 | ||
| 2598 | + ### `http2.sensitiveHeaders` | ||
| 2599 | + <!-- YAML | ||
| 2600 | + added: REPLACEME | ||
| 2601 | + --> | ||
| 2602 | + | ||
| 2603 | + * {symbol} | ||
| 2604 | + | ||
| 2605 | + This symbol can be set as a property on the HTTP/2 headers object with an array | ||
| 2606 | + value in order to provide a list of headers considered sensitive. | ||
| 2607 | + See [Sensitive headers][] for more details. | ||
| 2608 | + | ||
| 2598 | 2609 | ### Headers object | |
| 2599 | 2610 | ||
| 2600 | 2611 | Headers are represented as own-properties on JavaScript objects. The property | |
@@ -2643,6 +2654,32 @@ server.on('stream', (stream, headers) => { | |||
| 2643 | 2654 | }); | |
| 2644 | 2655 | ``` | |
| 2645 | 2656 | ||
| 2657 | + #### Sensitive headers | ||
| 2658 | + | ||
| 2659 | + HTTP2 headers can be marked as sensitive, which means that the HTTP/2 | ||
| 2660 | + header compression algorithm will never index them. This can make sense for | ||
| 2661 | + header values with low entropy and that may be considered valuable to an | ||
| 2662 | + attacker, for example `Cookie` or `Authorization`. To achieve this, add | ||
| 2663 | + the header name to the `[http2.sensitiveHeaders]` property as an array: | ||
| 2664 | + | ||
| 2665 | + ```js | ||
| 2666 | + const headers = { | ||
| 2667 | + ':status': '200', | ||
| 2668 | + 'content-type': 'text-plain', | ||
| 2669 | + 'cookie': 'some-cookie', | ||
| 2670 | + 'other-sensitive-header': 'very secret data', | ||
| 2671 | + [http2.sensitiveHeaders]: ['cookie', 'other-sensitive-header'] | ||
| 2672 | + }; | ||
| 2673 | + | ||
| 2674 | + stream.respond(headers); | ||
| 2675 | + ``` | ||
| 2676 | + | ||
| 2677 | + For some headers, such as `Authorization` and short `Cookie` headers, | ||
| 2678 | + this flag is set automatically. | ||
| 2679 | + | ||
| 2680 | + This property is also set for received headers. It will contain the names of | ||
| 2681 | + all headers marked as sensitive, including ones marked that way automatically. | ||
| 2682 | + | ||
| 2646 | 2683 | ### Settings object | |
| 2647 | 2684 | <!-- YAML | |
| 2648 | 2685 | added: v8.4.0 | |
@@ -3814,3 +3851,4 @@ following additional properties: | |||
| 3814 | 3851 | [`tls.createServer()`]: tls.md#tls_tls_createserver_options_secureconnectionlistener | |
| 3815 | 3852 | [`writable.writableFinished`]: stream.md#stream_writable_writablefinished | |
| 3816 | 3853 | [error code]: #http2_error_codes_for_rst_stream_and_goaway | |
| 3854 | + [Sensitive headers]: #http2_sensitive_headers | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,7 @@ const { | |||
| 8 | 8 | getDefaultSettings, | |
| 9 | 9 | getPackedSettings, | |
| 10 | 10 | getUnpackedSettings, | |
| 11 | + sensitiveHeaders, | ||
| 11 | 12 | Http2ServerRequest, | |
| 12 | 13 | Http2ServerResponse | |
| 13 | 14 | } = require('internal/http2/core'); | |
@@ -20,6 +21,7 @@ module.exports = { | |||
| 20 | 21 | getDefaultSettings, | |
| 21 | 22 | getPackedSettings, | |
| 22 | 23 | getUnpackedSettings, | |
| 24 | + sensitiveHeaders, | ||
| 23 | 25 | Http2ServerRequest, | |
| 24 | 26 | Http2ServerResponse | |
| 25 | 27 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -129,6 +129,7 @@ const { | |||
| 129 | 129 | getSettings, | |
| 130 | 130 | getStreamState, | |
| 131 | 131 | isPayloadMeaningless, | |
| 132 | + kSensitiveHeaders, | ||
| 132 | 133 | kSocket, | |
| 133 | 134 | kRequest, | |
| 134 | 135 | kProxySocket, | |
@@ -312,7 +313,7 @@ function emit(self, ...args) { | |||
| 312 | 313 | // create the associated Http2Stream instance and emit the 'stream' | |
| 313 | 314 | // event. If the stream is not new, emit the 'headers' event to pass | |
| 314 | 315 | // the block of headers on. | |
| 315 | - function onSessionHeaders(handle, id, cat, flags, headers) { | ||
| 316 | + function onSessionHeaders(handle, id, cat, flags, headers, sensitiveHeaders) { | ||
| 316 | 317 | const session = this[kOwner]; | |
| 317 | 318 | if (session.destroyed) | |
| 318 | 319 | return; | |
@@ -326,7 +327,7 @@ function onSessionHeaders(handle, id, cat, flags, headers) { | |||
| 326 | 327 | let stream = streams.get(id); | |
| 327 | 328 | ||
| 328 | 329 | // Convert the array of header name value pairs into an object | |
| 329 | - const obj = toHeaderObject(headers); | ||
| 330 | + const obj = toHeaderObject(headers, sensitiveHeaders); | ||
| 330 | 331 | ||
| 331 | 332 | if (stream === undefined) { | |
| 332 | 333 | if (session.closed) { | |
@@ -2351,6 +2352,7 @@ function processHeaders(oldHeaders, options) { | |||
| 2351 | 2352 | headers[key] = oldHeaders[key]; | |
| 2352 | 2353 | } | |
| 2353 | 2354 | } | |
| 2355 | + headers[kSensitiveHeaders] = oldHeaders[kSensitiveHeaders]; | ||
| 2354 | 2356 | } | |
| 2355 | 2357 | ||
| 2356 | 2358 | const statusCode = | |
@@ -2373,6 +2375,10 @@ function processHeaders(oldHeaders, options) { | |||
| 2373 | 2375 | if (statusCode < 200 || statusCode > 599) | |
| 2374 | 2376 | throw new ERR_HTTP2_STATUS_INVALID(headers[HTTP2_HEADER_STATUS]); | |
| 2375 | 2377 | ||
| 2378 | + const neverIndex = headers[kSensitiveHeaders]; | ||
| 2379 | + if (neverIndex !== undefined && !ArrayIsArray(neverIndex)) | ||
| 2380 | + throw new ERR_INVALID_OPT_VALUE('headers[http2.neverIndex]', neverIndex); | ||
| 2381 | + | ||
| 2376 | 2382 | return headers; | |
| 2377 | 2383 | } | |
| 2378 | 2384 | ||
@@ -3333,6 +3339,7 @@ module.exports = { | |||
| 3333 | 3339 | getDefaultSettings, | |
| 3334 | 3340 | getPackedSettings, | |
| 3335 | 3341 | getUnpackedSettings, | |
| 3342 | + sensitiveHeaders: kSensitiveHeaders, | ||
| 3336 | 3343 | Http2Session, | |
| 3337 | 3344 | Http2Stream, | |
| 3338 | 3345 | Http2ServerRequest, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,6 +9,8 @@ const { | |||
| 9 | 9 | ObjectKeys, | |
| 10 | 10 | Set, | |
| 11 | 11 | String, | |
| 12 | + StringFromCharCode, | ||
| 13 | + StringPrototypeToLowerCase, | ||
| 12 | 14 | Symbol, | |
| 13 | 15 | } = primordials; | |
| 14 | 16 | ||
@@ -25,11 +27,14 @@ const { | |||
| 25 | 27 | hideStackFrames | |
| 26 | 28 | } = require('internal/errors'); | |
| 27 | 29 | ||
| 30 | + const kSensitiveHeaders = Symbol('nodejs.http2.sensitiveHeaders'); | ||
| 28 | 31 | const kSocket = Symbol('socket'); | |
| 29 | 32 | const kProxySocket = Symbol('proxySocket'); | |
| 30 | 33 | const kRequest = Symbol('request'); | |
| 31 | 34 | ||
| 32 | 35 | const { | |
| 36 | + NGHTTP2_NV_FLAG_NONE, | ||
| 37 | + NGHTTP2_NV_FLAG_NO_INDEX, | ||
| 33 | 38 | NGHTTP2_SESSION_CLIENT, | |
| 34 | 39 | NGHTTP2_SESSION_SERVER, | |
| 35 | 40 | ||
@@ -454,6 +459,9 @@ const assertValidPseudoHeaderTrailer = hideStackFrames((key) => { | |||
| 454 | 459 | throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key); | |
| 455 | 460 | }); | |
| 456 | 461 | ||
| 462 | + const emptyArray = []; | ||
| 463 | + const kNeverIndexFlag = StringFromCharCode(NGHTTP2_NV_FLAG_NO_INDEX); | ||
| 464 | + const kNoHeaderFlags = StringFromCharCode(NGHTTP2_NV_FLAG_NONE); | ||
| 457 | 465 | function mapToHeaders(map, | |
| 458 | 466 | assertValuePseudoHeader = assertValidPseudoHeader) { | |
| 459 | 467 | let ret = ''; | |
@@ -466,6 +474,8 @@ function mapToHeaders(map, | |||
| 466 | 474 | let value; | |
| 467 | 475 | let isSingleValueHeader; | |
| 468 | 476 | let err; | |
| 477 | + const neverIndex = | ||
| 478 | + (map[kSensitiveHeaders] || emptyArray).map(StringPrototypeToLowerCase); | ||
| 469 | 479 | for (i = 0; i < keys.length; ++i) { | |
| 470 | 480 | key = keys[i]; | |
| 471 | 481 | value = map[key]; | |
@@ -494,11 +504,12 @@ function mapToHeaders(map, | |||
| 494 | 504 | throw new ERR_HTTP2_HEADER_SINGLE_VALUE(key); | |
| 495 | 505 | singles.add(key); | |
| 496 | 506 | } | |
| 507 | + const flags = neverIndex.includes(key) ? kNeverIndexFlag : kNoHeaderFlags; | ||
| 497 | 508 | if (key[0] === ':') { | |
| 498 | 509 | err = assertValuePseudoHeader(key); | |
| 499 | 510 | if (err !== undefined) | |
| 500 | 511 | throw err; | |
| 501 | - ret = `${key}\0${value}\0${ret}`; | ||
| 512 | + ret = `${key}\0${value}\0${flags}${ret}`; | ||
| 502 | 513 | count++; | |
| 503 | 514 | continue; | |
| 504 | 515 | } | |
@@ -508,12 +519,12 @@ function mapToHeaders(map, | |||
| 508 | 519 | if (isArray) { | |
| 509 | 520 | for (j = 0; j < value.length; ++j) { | |
| 510 | 521 | const val = String(value[j]); | |
| 511 | - ret += `${key}\0${val}\0`; | ||
| 522 | + ret += `${key}\0${val}\0${flags}`; | ||
| 512 | 523 | } | |
| 513 | 524 | count += value.length; | |
| 514 | 525 | continue; | |
| 515 | 526 | } | |
| 516 | - ret += `${key}\0${value}\0`; | ||
| 527 | + ret += `${key}\0${value}\0${flags}`; | ||
| 517 | 528 | count++; | |
| 518 | 529 | } | |
| 519 | 530 | ||
@@ -552,7 +563,7 @@ const assertWithinRange = hideStackFrames( | |||
| 552 | 563 | } | |
| 553 | 564 | ); | |
| 554 | 565 | ||
| 555 | - function toHeaderObject(headers) { | ||
| 566 | + function toHeaderObject(headers, sensitiveHeaders) { | ||
| 556 | 567 | const obj = ObjectCreate(null); | |
| 557 | 568 | for (var n = 0; n < headers.length; n += 2) { | |
| 558 | 569 | const name = headers[n]; | |
@@ -593,6 +604,7 @@ function toHeaderObject(headers) { | |||
| 593 | 604 | } | |
| 594 | 605 | } | |
| 595 | 606 | } | |
| 607 | + obj[kSensitiveHeaders] = sensitiveHeaders; | ||
| 596 | 608 | return obj; | |
| 597 | 609 | } | |
| 598 | 610 | ||
@@ -621,6 +633,7 @@ module.exports = { | |||
| 621 | 633 | getSettings, | |
| 622 | 634 | getStreamState, | |
| 623 | 635 | isPayloadMeaningless, | |
| 636 | + kSensitiveHeaders, | ||
| 624 | 637 | kSocket, | |
| 625 | 638 | kProxySocket, | |
| 626 | 639 | kRequest, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1215,22 +1215,29 @@ void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) { | |||
| 1215 | 1215 | // this way for performance reasons (it's faster to generate and pass an | |
| 1216 | 1216 | // array than it is to generate and pass the object). | |
| 1217 | 1217 | ||
| 1218 | - std::vector<Local<Value>> headers_v(stream->headers_count() * 2); | ||
| 1218 | + MaybeStackBuffer<Local<Value>, 64> headers_v(stream->headers_count() * 2); | ||
| 1219 | + MaybeStackBuffer<Local<Value>, 32> sensitive_v(stream->headers_count()); | ||
| 1220 | + size_t sensitive_count = 0; | ||
| 1221 | + | ||
| 1219 | 1222 | stream->TransferHeaders([&](const Http2Header& header, size_t i) { | |
| 1220 | 1223 | headers_v[i * 2] = header.GetName(this).ToLocalChecked(); | |
| 1221 | 1224 | headers_v[i * 2 + 1] = header.GetValue(this).ToLocalChecked(); | |
| 1225 | + if (header.flags() & NGHTTP2_NV_FLAG_NO_INDEX) | ||
| 1226 | + sensitive_v[sensitive_count++] = headers_v[i * 2]; | ||
| 1222 | 1227 | }); | |
| 1223 | 1228 | CHECK_EQ(stream->headers_count(), 0); | |
| 1224 | 1229 | ||
| 1225 | 1230 | DecrementCurrentSessionMemory(stream->current_headers_length_); | |
| 1226 | 1231 | stream->current_headers_length_ = 0; | |
| 1227 | 1232 | ||
| 1228 | - Local<Value> args[5] = { | ||
| 1229 | - stream->object(), | ||
| 1230 | - Integer::New(isolate, id), | ||
| 1231 | - Integer::New(isolate, stream->headers_category()), | ||
| 1232 | - Integer::New(isolate, frame->hd.flags), | ||
| 1233 | - Array::New(isolate, headers_v.data(), headers_v.size())}; | ||
| 1233 | + Local<Value> args[] = { | ||
| 1234 | + stream->object(), | ||
| 1235 | + Integer::New(isolate, id), | ||
| 1236 | + Integer::New(isolate, stream->headers_category()), | ||
| 1237 | + Integer::New(isolate, frame->hd.flags), | ||
| 1238 | + Array::New(isolate, headers_v.out(), headers_v.length()), | ||
| 1239 | + Array::New(isolate, sensitive_v.out(), sensitive_count), | ||
| 1240 | + }; | ||
| 1234 | 1241 | MakeCallback(env()->http2session_on_headers_function(), | |
| 1235 | 1242 | arraysize(args), args); | |
| 1236 | 1243 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -116,7 +116,6 @@ using Nghttp2SessionCallbacksPointer = | |||
| 116 | 116 | ||
| 117 | 117 | struct Http2HeadersTraits { | |
| 118 | 118 | typedef nghttp2_nv nv_t; | |
| 119 | - static const uint8_t kNoneFlag = NGHTTP2_NV_FLAG_NONE; | ||
| 120 | 119 | }; | |
| 121 | 120 | ||
| 122 | 121 | struct Http2RcBufferPointerTraits { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -55,13 +55,14 @@ NgHeaders<T>::NgHeaders(Environment* env, v8::Local<v8::Array> headers) { | |||
| 55 | 55 | return; | |
| 56 | 56 | } | |
| 57 | 57 | ||
| 58 | - nva[n].flags = T::kNoneFlag; | ||
| 59 | 58 | nva[n].name = reinterpret_cast<uint8_t*>(p); | |
| 60 | 59 | nva[n].namelen = strlen(p); | |
| 61 | 60 | p += nva[n].namelen + 1; | |
| 62 | 61 | nva[n].value = reinterpret_cast<uint8_t*>(p); | |
| 63 | 62 | nva[n].valuelen = strlen(p); | |
| 64 | 63 | p += nva[n].valuelen + 1; | |
| 64 | + nva[n].flags = *p; | ||
| 65 | + p++; | ||
| 65 | 66 | } | |
| 66 | 67 | } | |
| 67 | 68 | ||
@@ -189,6 +190,11 @@ size_t NgHeader<T>::length() const { | |||
| 189 | 190 | return name_.len() + value_.len(); | |
| 190 | 191 | } | |
| 191 | 192 | ||
| 193 | + template <typename T> | ||
| 194 | + uint8_t NgHeader<T>::flags() const { | ||
| 195 | + return flags_; | ||
| 196 | + } | ||
| 197 | + | ||
| 192 | 198 | } // namespace node | |
| 193 | 199 | ||
| 194 | 200 | #endif // SRC_NODE_HTTP_COMMON_INL_H_ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -460,6 +460,7 @@ struct NgHeaderBase : public MemoryRetainer { | |||
| 460 | 460 | virtual std::string name() const = 0; | |
| 461 | 461 | virtual std::string value() const = 0; | |
| 462 | 462 | virtual size_t length() const = 0; | |
| 463 | + virtual uint8_t flags() const = 0; | ||
| 463 | 464 | virtual std::string ToString() const; | |
| 464 | 465 | }; | |
| 465 | 466 | ||
@@ -505,6 +506,7 @@ class NgHeader final : public NgHeaderBase<typename T::allocator_t> { | |||
| 505 | 506 | inline std::string name() const override; | |
| 506 | 507 | inline std::string value() const override; | |
| 507 | 508 | inline size_t length() const override; | |
| 509 | + inline uint8_t flags() const override; | ||
| 508 | 510 | ||
| 509 | 511 | void MemoryInfo(MemoryTracker* tracker) const override; | |
| 510 | 512 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,47 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + if (!common.hasCrypto) | ||
| 4 | + common.skip('missing crypto'); | ||
| 5 | + const assert = require('assert'); | ||
| 6 | + const http2 = require('http2'); | ||
| 7 | + const makeDuplexPair = require('../common/duplexpair'); | ||
| 8 | + | ||
| 9 | + { | ||
| 10 | + const testData = '<h1>Hello World</h1>'; | ||
| 11 | + const server = http2.createServer(); | ||
| 12 | + server.on('stream', common.mustCall((stream, headers) => { | ||
| 13 | + stream.respond({ | ||
| 14 | + 'content-type': 'text/html', | ||
| 15 | + ':status': 200, | ||
| 16 | + 'cookie': 'donotindex', | ||
| 17 | + 'not-sensitive': 'foo', | ||
| 18 | + 'sensitive': 'bar', | ||
| 19 | + // sensitiveHeaders entries are case-insensitive | ||
| 20 | + [http2.sensitiveHeaders]: ['Sensitive'] | ||
| 21 | + }); | ||
| 22 | + stream.end(testData); | ||
| 23 | + })); | ||
| 24 | + | ||
| 25 | + const { clientSide, serverSide } = makeDuplexPair(); | ||
| 26 | + server.emit('connection', serverSide); | ||
| 27 | + | ||
| 28 | + const client = http2.connect('http://localhost:80', { | ||
| 29 | + createConnection: common.mustCall(() => clientSide) | ||
| 30 | + }); | ||
| 31 | + | ||
| 32 | + const req = client.request({ ':path': '/' }); | ||
| 33 | + | ||
| 34 | + req.on('response', common.mustCall((headers) => { | ||
| 35 | + assert.strictEqual(headers[':status'], 200); | ||
| 36 | + assert.strictEqual(headers.cookie, 'donotindex'); | ||
| 37 | + assert.deepStrictEqual(headers[http2.sensitiveHeaders], | ||
| 38 | + ['cookie', 'sensitive']); | ||
| 39 | + })); | ||
| 40 | + | ||
| 41 | + req.on('end', common.mustCall(() => { | ||
| 42 | + clientSide.destroy(); | ||
| 43 | + clientSide.end(); | ||
| 44 | + })); | ||
| 45 | + req.resume(); | ||
| 46 | + req.end(); | ||
| 47 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments