公開インターネットに面した WebSocket サーバーは、格好の標的です。このプロトコルには組み込みのメッセージサイズ制限がないため、たった 1 つの接続が細工されたフレーム 1 つでプロセス全体を引きずり落とそうとすることができ、アップグレードのハンドシェイクは杜撰な検証を探る格好の場所になります。最新の sgcWebSockets は、これらの入り口を既定で閉じる一連の保護機能を搭載しており、敵対的なトラフィックの下でもサーバーが稼働し続けます。
以下のすべての内容は、3 つの WebSocket サーバーすべてに適用されます。TsgcWebSocketServer、TsgcWebSocketHTTPServer、そして http.sys ベースの TsgcWebSocketServer_HTTPAPI です。.NET 版は同じネイティブライブラリ上で動作するため、同じ保護機能を継承します。
3 種類のメモリ攻撃を止める 1 つの制限
目玉となる追加機能は、受信メッセージの最大サイズを制限する単一のプロパティ MaxMessageSize です。既定値は 64 MB で、ほぼすべてのアプリケーションにとって十分な大きさですが、引き上げたり引き下げたりすることも、0 に設定して制限を無効化することもできます。
この 1 つのプロパティが、いずれもサーバーの RAM を枯渇させるという同じ結末を迎える、3 種類の異なるメモリ枯渇手法に対して防御します。
- サイズ超過フレーム。 クライアントが巨大なペイロード長を宣言します。サーバーは本体の読み取りを開始する前に、そのフレームを拒否するようになりました。
- 終わりのないフラグメント化。 クライアントがフラグメント化されたメッセージを開始しながら完了させず、継続フレームを永遠に流し続けます。サーバーは、すべてのフラグメントにわたって再構成される合計サイズに上限を設けるようになりました。
- 圧縮爆弾。 ギガバイト単位に膨張する小さな per-message-deflate フレームです。サーバーは、膨張した出力が制限に達した瞬間に解凍を停止するようになりました。
いずれの場合も、接続は WebSocket close code 1009 (Message Too Big) でクリーンにクローズされ、サーバーのメモリが上限を超えることはありません。
oServer := TsgcWebSocketServer.Create(nil);
oServer.Port := 80;
// accept messages up to 16 MB, reject anything larger with close 1009
oServer.MaxMessageSize := 16 * 1024 * 1024;
oServer.Active := True;
安全なフレーム長の解析
WebSocket フレームは 64-bit の長さフィールドを保持できます。RFC 6455 によれば、そのフィールドの最上位ビットはゼロでなければなりません。sgcWebSockets は、64-bit の長さの最上位ビットが立っているフレームを信頼するのではなく拒否するようになったため、上記のサイズ制限を、ラップアラウンドする整数ですり抜けることはできません。このチェックは常に有効で、MaxMessageSize には依存しません。
より厳格なハンドシェイク
SecurityOptions 配下の 2 つの新しいオプションが WebSocket アップグレード自体を堅牢化し、どちらも既定で有効です。
EnforceWebSocketVersion は、13 以外のバージョンを要求するハンドシェイクに対して、アップグレードを完了する代わりに 426 Upgrade Required と Sec-WebSocket-Version: 13 ヘッダーで応答します。ValidateWebSocketKey は、Sec-WebSocket-Key が欠落しているか、有効な 16 バイトの base64 ノンスでないハンドシェイクを 400 Bad Request で拒否します。どちらのチェックも RFC 6455 のパスにのみ適用されるため、レガシー仕様の古いクライアントには影響しません。
oServer := TsgcWebSocketServer.Create(nil);
oServer.SecurityOptions.EnforceWebSocketVersion := True; // 426 on a wrong version
oServer.SecurityOptions.ValidateWebSocketKey := True; // 400 on a malformed key
// lock the server to your own site while you are at it
oServer.SecurityOptions.OriginsAllowed := 'https://app.example.com';
oServer.Active := True;
ファイアウォールとの連携
これらの保護機能は、フレームパーサーの内部、つまりプロトコル層に存在します。そこはまさにメモリ攻撃が発生する場所です。それは Firewall および RateLimiter コンポーネントが届かない部分です。なぜなら、これらはメッセージがデコードされた後にしかメッセージを見ないからです。この 2 つの層は互いを補完します。IP フィルタリング、接続およびメッセージのレート制限、オリジンポリシーにはファイアウォールとレートリミッターを引き続き使用し、パーサー自体は新しい組み込み制限に守らせてください。公開サーバーには、MaxMessageSize を実際の最大値に設定し、OriginsAllowed をフロントエンドにロックし、MaxConnections に上限を設けることをお勧めします。
アップグレード
新しい保護機能は、更新するとすぐに安全な既定値で有効になるため、ほとんどのサーバーは一切のコード変更なしにメモリおよびハンドシェイクの堅牢化を得られます。アプリケーションが正当に 64 MB を超えるメッセージをやり取りする場合は、MaxMessageSize をそれに応じて引き上げてください。既存のクライアントコードには影響しません。
sgcWebSockets ダウンロードページから更新するか、GetIt または登録済みアカウントから入手してください。
ご質問、フィードバック、または移行のサポートが必要ですか? お問い合わせください。コードを書いた本人から返信が届きます。
