
SolarWinds Serv-U CVE-2026-28318: 未認証のContent-Encoding: deflateによるクラッシュ。根本原因分析(内部ポインタの無効な解放によるヒープ破損)+ DoS専用のPoC。15.5.4 Hotfix 1で修正済み。
根本原因分析 + DoS 概念実証。 公開アドバイザリでは、これを認証不要の denial-of-service / uncontrolled resource consumption として分類しています。バイナリ解析の結果、根本的な欠陥は メモリ安全性 に関わるもので、HTTP
deflateデコードパスにおける 無効なfree()による内部ポインタの解放 であり、プロセスヒープを破壊します (STATUS_HEAP_CORRUPTION,0xC0000374)。これは 解凍爆弾 ではなく、クラッシュは 展開後のサイズに依存しません。
影響を受けるのは HTTP/HTTPS リスナーのみです(Web/管理パス)。FTP/FTPS/SFTP はこのコードパスに含まれません。
Content-Encoding: deflate を含む HTTP リクエストで、有効な deflate ボディがある場合、サービスがクラッシュします。ハンドラはボディを展開した後、圧縮されたボディへのポインタを解放 し、展開されたバッファに入れ替えます。しかし、そのポインタは HTTP 受信バッファへの内部ポインタ (\r\n\r\n の直後のボディを指している)であり、ヒープ割り当てのベースではありません。非ベースでアライメントされていないポインタに対して free() を呼び出すと、ヒープが破損し、プロセスが終了します。
バグは ボディポインタの解放方法 にあり、生成されるデータ量 にはないため、約 25 バイトの圧縮ボディ(数 KB に展開される)でも確実にクラッシュします。メモリ使用量は増加しません。「zip/deflate 爆弾」ではありません。
deflate デコードルーチンは RhinoNET.dll (Serv-U のネットワークライブラリ) にあり、HTTP 受信パス (ProcessReceive) から、ヘッダーパーサーが Content-Encoding: deflate を認識して "deflate" フラグを設定した後に呼び出されます。
簡略化すると、ルーチンは decode(this, &bodyPtr, &bodyLen) として呼び出され、以下の処理を行います:
1. inflate bodyPtr[0..bodyLen] -> grows an accumulator buffer `acc` (stock zlib, bounded, correct)
2. free(*bodyPtr) <-- *bodyPtr is an INTERIOR pointer into the receive buffer
3. *bodyPtr = acc // replace compressed body with decompressed buffer
4. *bodyLen = total
ステップ 2 がバグです。*bodyPtr は独立して割り当てられたブロックではありません。これは、単一の HTTP 受信バッファ 内 のリクエストボディを指しています。すなわち、receive_buffer + header_length です。内部(かつ 16 バイトアライメントされていない)ポインタを解放すると、アロケータは攻撃者が制御する領域をヒープチャンクヘッダとして解析し、ヒープメタデータの破損 → 0xC0000374 が発生します。
「明白な」仮説はどちらも 間違い であり、ランタイムで確認済みです:
zlib1.dll!inflate であり、毎回の呼び出しで avail_out を尊重します(100 以上のチャンクで観測、ゼロの範囲外書き込み)。prev_total + produced + 1 として割り当てられ、その正確な量だけ書き込まれます — タイトで、オーバーフローはありません。破損はステップ 2 の無効な free() のみによるものです。
Serv-U 15.5.4.108 に対して、ボディが "A" * 8192 に展開される Content-Encoding: deflate リクエストを送信中に、ランタイム(Frida)で取得しました:
…ce — mod 16 == 14 です。 ヒープ割り当てベースは 16 バイトアライメントされているため、これは割り当てベースではなく、内部ポインタです。\r\n\r\n で終わります:
…tream\r\nContent-Length: 26\r\nConnection: close\r\n\r\n
ed c1 01 0d 00 …)。ntdll.dll、例外 0xC0000374 (STATUS_HEAP_CORRUPTION)、呼び出しは RhinoNET.dll の deflate ハンドラ内の free から発生。クラッシュ前の最後のヒープ操作はまさにこの free(bodyPtr) です。0xC0000374) し、コード実行は達成できませんでした。 これは メモリ安全性の根本原因を持つ事前認証ヒープ破損 DoS として扱ってください。RCE を想定しないでください。Python 3 (標準ライブラリのみ) が必要です。テストを許可された Serv-U HTTP リスナーを指定してください。Windows では、Get-NetTCPConnection 呼び出しを使用してリスナーの PID を読み取り、Application イベントログでヒープ破損クラッシュを確認します。
python poc_verify.py # default 127.0.0.1:80, tiny packet, 1 shot
python poc_verify.py --host <ip> --port 80
python poc_verify.py --big # body decompresses to 8192 bytes
python poc_verify.py --shots 3
python poc_verify.py --no-events # skip event-log check (no privileges needed)
PoC は最小限のリクエストを構築します:
POST / HTTP/1.1
Host: <target>
Content-Encoding: deflate
Content-Type: application/octet-stream
Content-Length: <n>
Connection: close
<raw-deflate body — even a few dozen bytes is enough>
サービスがクラッシュした場合(リスナー PID が変更された / 0xC0000374 イベントが発生した)は PASS を報告し、サービスが生き残った場合(既にパッチ済み、影響を受けない、またはボディが deflate として処理されなかった)は FAIL を報告します。
Content-Encoding を削除または拒否します。例:リバースプロキシ上で:
if ($http_content_encoding) { return 400; }
RhinoNET.dll / Serv-U.exe (イメージベース 0x180000000) のパラメータ化された PE 逆アセンブルにより、受信 → ヘッダーパース → deflate デコードパスをマッピングし、誤った free の位置を特定。zlib が avail_out を尊重していることを証明し、デコード呼び出しのすべての alloc/free/memcpy をディスクにトレースし、プロセス例外ハンドラで破損時の障害命令と 0xC0000374 スタックをキャプチャ。その後、解放されたポインタ周辺のバイト(HTTP ヘッダテール + raw deflate ボディ、16 バイトミスアライメント)をダンプすることで、内部ポインタの解放が確認されました。分析で参照されたオフセットはビルド 15.5.4.108 に固有であり、異なるビルドでは異なります。
ベンダーのパッチ提供後に防御的研究として公開。PoC は DoS のみであり、許可されたテストを目的としています。
| Product | SolarWinds Serv-U (FTP / MFT / ファイルサーバー) |
| Vulnerable | 15.5.4 およびそれ以前で、Hotfix 1 が適用されていない もの(解析はビルド 15.5.4.108 で実施) |
| Fixed | Serv-U 15.5.4 Hotfix 1 (2026-06-04 リリース) |
| Vector | 認証不要、ネットワーク (HTTP/HTTPS 管理/Web ポート) |
| Public class | CWE-400 制御不能なリソース消費 · DoS · CVSS 7.5 · CISA KEV |
| Actual class | CWE-763 無効なポインタの解放 / CWE-590 ヒープ外メモリの解放 → ヒープ破損 |