
Windows Network File System リモートエクスプロイト (CVE-2022-30136)
著者: Ricardo Narvaja
デモンストレーション目的のみ。完全なエクスプロイトは脆弱なWindows Serverシステムで動作します。
解説記事をチェックしてください: Analysis of CVE-2022-30136 “Windows Network File System Vulnerability”。
この記事は、CVE-2022-30136脆弱性を利用するCore Impactエクスプロイト「Windows Network File System Remote」の開発中に行った分析を示すために書きました。
Windows Network File Systemのリモートコード実行脆弱性は、NFSバージョン4.1を使用したCOMPOUND REQUESTでサーバーレスポンスを作成する際に発生するサイズ計算エラーです。
サーバーはプールを割り当てるために必要なサイズよりも小さいサイズを計算し、その後、レスポンスを生成するためにデータをコピーする際にバッファをオーバーフローさせます。
nfssvr.sys 内の関数 Nfs4SvrXdrpGetEncodeOperationResultByteCount は各操作に対して呼び出され、必要なサイズよりも小さいサイズ(各操作につき4バイト少ない)を返します。
Nfs4SvrXdrpGetEncodeOperationResultByteCount に対してパッチが適用されました。
この関数はCOMPOSE REQUESTの各OPERATION中に呼び出され、OPCODEに基づいて各操作に必要なバイト数を返します。その後、ヘッダーやレスポンスの他の部分に追加されます。次に、割り当てるレスポンス全体の最終サイズを計算し、そこにコピーして返信します。
それぞれの場合において、脆弱なバージョンで返される各操作のサイズの値は、パッチ適用後のバージョンよりも4バイト小さいことがわかります。
Windows Server 2019用のPOCを構築しました。
以下はこのPOCで使用する脆弱なバージョンのnfssvr.sysと、Windows Server 2019用のパッチ適用後のバージョンです:

次の画像は差分のCASE 26を示しています:

CASE 26の例では、脆弱なバージョンでは計算値に加算される定数が0x2cであり、パッチ適用後は0x30であることがわかります。
同様のことが各OPCODEに対応する各ケースでも見られます。脆弱なバージョンは常にパッチ適用後よりも4バイト小さいサイズを返します。
パッチはすべてのOPCODEで同様であるため、すべてのケースは示しません。
Nfs4SvrXdrpGetEncodeOperationResultByteCount の親は Nfs4SvrXdrEncodeCompoundResults です。これはCOMPOUND REQUESTで送信された操作の数を読み取ります。
このPOCでは値は0x34 (52d)です。私のPOCがサーバーのポート2049(NFSのデフォルトポート)に接続するとき、停止のために条件付きブレークポイントを設定する必要があります。


この場合、number_of_operations=0x34のときに停止します。

タグARGSのプールがここで割り当てられます。

次に、フィールドをリバースエンジニアリングするためにTAG_ARGS_0x10e0という名前の構造体を作成します。

number_of_operationsをr13にコピーし、カウンタがr13の値に達するまで操作ごとに脆弱な関数にループします。

最初のpackage_OPCODE=0x35が示されており、これはCOMPOUND REQUESTの最初の必須操作であるSEQUENCEに対応します。以下の画像では、矢印が私のパッケージ内のこのOPCODEを指しています。

ここで脆弱な関数の引数を見ることができます。


脆弱な関数内でOPCODEを読み取り、対応するCASEに進みます。

元のOPCODE値(53)から3が減算されます。

そしてCASE 50にジャンプし、この操作に必要なサイズとして0x28を返します。


差分を見ると、パッチ適用後は0x2cを返していることがわかります。

この戻り値は、操作のサイズを計算するために、レスポンス内の他のフィールドの前の値に加算されます。この場合、この値は0X40cです。

以下で加算される値を確認できます:

ループを抜けると、合計サイズが計算されます。この場合、合計サイズは0x1310です。

脆弱なバージョンとパッチ適用後のバージョンの差は、number_of_operations * 4という式を使ってサイズを計算することで推測できます。
この場合、パッチ適用後のバージョンの割り当ては脆弱なバージョンよりも0x34 * 4 = 0x68大きくなります。

その後、0x24が加算されます。この値は脆弱なバージョンとパッチ適用後のバージョンの両方で同様の方法で計算されます。

次に、両方の場合で定数0xfを加算します。

この時点まで、この例でのサイズは0x1340です。

次に、rpcxdr_OncRpcBufMgrpAllocateに到達します。

次にr15に移動します。


1を減算し、4を加算します。その後、0x800と比較します。

この誤計算されたサイズは、0x800より大きい場合にのみ使用されます。そのため、COMPOUND REQUESTのみがバグをトリガーします。
最初に、サイズ0x80、タグXdBDのプールを割り当てます。

最後に、いくつかの定数値を加算して、サイズ0x1398の応答用プールをここで割り当てます。

次に0x13a0(タグXdBPとヘッダーを含む)を割り当てます。

そこから、新しく割り当てられたプールのアドレスをフィールド tag_XdBD_0x80.p_TAG_XDBP_0x13a0 に格納します。

これは常にコピー先となる応答のアドレスを指します。

その後、応答ヘッダーの構築を開始します。

以下は、一時的なポインタの内容にデータを保存し、それに4を加算する例です。


以下で、応答アドレスの内容にコピーする方法を確認できます。

これにより最初のdwordが書き込まれ、ポインタが4増加します。

次に2番目のdwordを書き込み、4を加算します。


関数を抜けた後、ヘッダー全体が書き込まれます。

その後、nfssvr.sysに戻り、応答の書き込みを続けます。

デコードと応答への書き込みを続け、一時ポインタに4を加算します。

ヘッダーが完了すると、すべての操作を書き込むこのループに到達します。最初のOPCODE 0x35から始まります。


プールの先頭から0x428書き込むことがわかります。

これでタグの後を指します。


ここにブレークポイントを設定することで、すべての操作がどのように書き込まれたかを確認できます。


ループを抜けるとすべての操作がコピーされます。

プールの末尾を確認しましょう。

そこで、制限を超えた書き込みを確認できます。

割り当てがコピーされたデータよりも小さいため、プールオーバーフローが発生します。
これによりターゲットマシンでBSODが発生します。しかし、問題はリモートコード実行やWrite What Whereを達成できるかどうかです。
オーバーフローバイト内で制御されたデータを含む応答を得るために、いくつかのオペコードの組み合わせを試しましたが、残念ながらうまくいきませんでした。
最大のタグ(私が制御可能)は先頭にのみ配置でき、最大サイズは0x400です。

試した他のすべてのオペコードは制御されたデータを応答しません。したがって、このバグでRCEを取得したり特権を昇格させたりすることは不可能か、少なくとも非常に困難だと思います。とはいえ、存在する多くの可能性の中ですべての組み合わせを試したわけではないので、まだ可能かもしれません。
POCの構築には「NFS CLIENT」という名前のクライアントを試しました。これはNFS 4.1をサポートしており、さまざまなオペコードを試してファイルのコピー、編集、フォルダの作成などができました。

この構築では、COMPOUNDサンプルパッケージを作成し、サイズ、クライアントID、セッションIDなどを調整できました。

次に、EXCHANGE_IDを送信してクライアントIDを取得し、それを使用してCREATE_SESSIONを送信し、最後に大きなCOMPOUND REQUESTを送信しました。

この時点でバグが悪用され、DoS攻撃を可能にするリモートコード実行につながります。
お役に立てば幸いです。ご質問があれば、[email protected]までご連絡ください。
お楽しみください!