daisuzz.log

Node.jsの最大リクエストヘッダサイズでハマった話

現象

  • Node.jsで立てたWebサーバが不思議な挙動をとるようになった

  • HTTPリクエストを送ると、リクエストに失敗する

  • 発生する時間やエラーを再現する人はバラバラで一貫性がない

  • その場しのぎの対応としてブラウザの再起動やCookieの削除で解決することが多かったが、原因の詳細はわからずもやもやしていた

原因

  • Node.jsの最大リクエストヘッダサイズのデフォルト値である8kBを越えるHTTPリクエストを送信していたことが原因

  • Node.jsは、2018/11にDoS攻撃脆弱性(https://nvd.nist.gov/vuln/detail/CVE-2018-12121)対応として、デフォルトのHTTPリクエストヘッダの最大サイズを変更前の80kBから8kB(8192Bytes)に変更する修正が加えられた

  • デフォルトでは、HTTPリクエストヘッダのサイズが8kBを越えるとソケットが破棄されて「431 Request Header Fields Too Large」を返す

$ npm start

> sample-nodejs-header-overflow@1.0.0 start /../../../sample-nodejs-header-overflow
> node index.js

// curlで8kB以上のHTTPリクエストを送信
ErrorCode:  HPE_HEADER_OVERFLOW
BytesParsed:  8559

// curlで8kB以上のHTTPリクエストを送信
ErrorCode:  HPE_HEADER_OVERFLOW
BytesParsed:  8559

// curlで8kB以上のHTTPリクエストを送信
ErrorCode:  HPE_HEADER_OVERFLOW
BytesParsed:  9085

対策

  • HTTPリクエストヘッダのサイズが超えた場合に起こるclientErrorイベントを補足して、ソケットが強制的に破棄されないようにエラーハンドリングを行う
const http = require('http');

const server = http.createServer((req, res) => {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World');
});

server.on('clientError', (err, socket) => {
    console.log('ErrorCode: ', err.code);
    console.log('BytesParsed: ', err.bytesParsed);
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(8080);
  • アプリケーション起動時に「--max-http-header-size」という起動オプションを設定して、Node.jsが受け取る最大のHTTPリクエストヘッダサイズを増やす
$ node --max-http-header-size=16384 index.js

おわりに

今回リクエストヘッダのサイズが8kBを越えた主な原因は、かなりの数のCookieを使ってWebサーバにアクセスしていたことでした。 仕様上Cookieの数が多くなりHTTPリクエストのサイズに不安がある場合は、エラーハンドリングを正しく実装して、起動オプションでNode.jsが受け取るHTTPリクエストサイズの最大値を上げておくと良いかと思います。

参考