
著者: @x0root
脆弱性: ブラウザでレンダリング可能なアップロード (SVG / HTML) による格納型クロスサイトスクリプティング (XSS)
影響を受けるソフトウェア: FileRise (< 2.7.1)
パッチ適用バージョン: 2.7.1
公式CVE (GHSA経由で申請): CVE-2025-68116 (追跡/勧告: GHSA-35pp-ggh6-c59c)
関連する以前の勧告 (回避された当初の緩和策): GHSA-qrcv-vjvf-fr29
CVSS評価:
報告者の評価では、Privileges Required (PR) を脆弱性を仕込む時点ではなく、悪用される時点で評価します。
悪用は、被害者が生成された公開共有リンクにアクセスした時点で発生し、認証や権限は不要です (PR:N)。
CNAの評価は、悪意のあるファイルをアップロードできるかどうかに基づいてPRを評価します。
しかし、CVSS v3.1はPrivileges Required (PR) を、脆弱性が悪用される時点で攻撃者が保持していなければならない権限と定義しており、脆弱な状態を作り出すために必要な権限ではありません。
したがって、PR:Nの方が実際の悪用条件をより正確に反映しており、結果として緊急 (9.6) の重大度分類になります。
注記: GHSA-qrcv-vjvf-fr29は、FileRiseのWeb UI (プレビューペイン) 内でSVGがレンダリングされるのを防ぐ緩和策を導入しました。このレポートは、その緩和策の回避—具体的にはバックエンドの共有/ダウンロードエンドポイント—を文書化したもので、GHSA-35pp-ggh6-c59c / CVE-2025-68116として追跡されています。
この文書は、CVE-2025-68116の完全な技術記録です。これはFileRiseの格納型XSSであり、以前の緩和策後も存続し、最終的にv2.7.1で修正されました。発見、悪用の概念実証、繰り返された失敗した修正、正確な根本原因の制御フロー分析 (証拠付き)、パッチの最終検証、CVSS評価に関連する悪用可能性の特性の分析を含みます。以下のすべての内容は、再現テスト、コントローラの検査、公開された勧告スレッドに基づいています。
以前の勧告であるGHSA-qrcv-vjvf-fr29は、FileRiseのWeb UIでのインライン描画をブロックすることで、SVGアップロードを介した格納型XSSに対処しました。その緩和策は、次のようなバックエンドエンドポイントがSVGファイルを配信する方法には対処していませんでした:
/api/file/download.php/api/file/share.phpCVE-2025-68116 (GHSA-35pp-ggh6-c59cとして追跡) は、GHSA-qrcv-vjvf-fr29緩和策の回避を文書化しています:攻撃者は細工されたSVGを保存し、公開共有リンクまたは特定のダウンロード動作を介して被害者に配信し、FileRiseオリジンでのスクリプト実行につなげることができます。
バックエンドがSVGをレンダリング可能な形で公開したままかどうかを検証するため、単純なPoC SVGをアップロードしました:
次の方法でファイルにアクセスすると:
/api/file/download.php?…/api/file/share.php?token=…その結果、alert()が実行されました。
元のGHSA-qrcv-vjvf-fr29緩和策 (UIプレビューブロック) は、これらのエンドポイントへの直接アクセスによって回避されました。
alert()はPoCにすぎません。ペイロードを内部APIと対話させることで、意味のある影響をテストしました。
使用したテストペイロード:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">
fetch('/api/upload/upload.php')
.then(response => response.text())
.then(data => alert('API Response: ' + data));
</script>
</svg>
ログイン済みの管理者がこのSVGを含む共有リンクを開くと、スクリプトが実行され、認証済みのAPIリクエストが行われました。観察された影響は次のとおりです:
{"csrf_expired":true,"csrf_token":"..."})テスト中に実証された影響の分類:
この問題を非公開で報告しました。メンテナーは段階的な修正をいくつかリリースしました:
v2.6.0 → v2.7.0の間、共有リンクエンドポイントはインライン描画とスクリプト実行を可能にする方法でSVGを配信し続けました。以下の根本原因分析は、以前の修正がこのベクターを完全に塞げなかった理由を説明しています。
根本的な原因は単一のヘッダー欠落ではなく、shareFile() (コントローラ) 内の制御フローと出力順序にあり、多くの実行パスでセキュリティヘッダーが適用されないようになっていました。2種類の問題が存在しました:
exit;ポイント。awkスキャンを使用して、readfile()呼び出しまでのshareFile()内のheader()とexit;の出現を一覧表示しました:
コマンド: awk '/function shareFile(/ {flag=1} /readfile(/ {flag=0} flag && /(header|exit;)/ {printf "%4d | %s\n", NR, $0}' src/controllers/FileController.php
観測された出力 (私の実行から抜粋):
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
セキュリティヘッダー (強化ロジック) は1743行目あたりから始まります:
1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
この関数は多くのパスでより早くヘッダーとexit;を出力するため、それらのリクエストはContent-Disposition、nosniff、または制限的なタイプを設定する強化コードに到達しませんでした。
パスワード保護された共有フローでは、関数はパスワード要求HTMLを早期に出力していました:
if (!empty($record['password']) && empty($providedPass)) { header("Content-Type: text/html; charset=utf-8"); ... exit; }
このパスはContent-Type: text/htmlを送信し、SVG強化ロジックの前に終了するため、パスワードが提供されていないパスワード保護共有では、ブラウザでインライン描画が発生します。
この脆弱性はパスワード保護されたフローに限定されませんでした。私のテストでは、パスワードなしの共有リクエストもtext/htmlを返しました:
コマンド: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type"
観測結果: < Content-Type: text/html; charset=UTF-8
これにより、一般的な (パスワードなし) ケースでもレスポンスはtext/htmlであり、SVGがインライン描画されたことが確認されます。
ヘッダー強化の前に出力されたPHP警告を含む共有エンドポイントの生のフェッチをキャプチャしました。スナップショット (抜粋):
コマンド: curl -s "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" | head -n 30
観測された生の出力 (抜粋):
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
これらの警告は、ヘッダー強化の前に出力 (非推奨通知) が生成され、それらの実行では後続のheader()呼び出しが有効にならないことを示しています。
nosniffを設定する強化コードは存在しましたが、早期終了と出力により多くのコードパスでは到達しませんでした。根本原因の報告を受けて、メンテナーは制御フローと出力順序に対処する変更を適用しました。v2.7.1では:
exit;ポイントは修正/処理されました。最終検証 (v2.7.1での私のテスト):
コマンド: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition"
観測結果: < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg
結果:ブラウザはダウンロードを強制されました。SVGはインライン描画されず、XSSペイロードは実行されませんでした。私の環境ではv2.7.1が問題を解決したと考えます。
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:L — スコア: 9.6 (緊急)
私はこの根拠を勧告スレッドに文書化し、PR:Nを使用するよう要求しました。
CNAのCVSSベクターは、制約された脅威モデルを反映しています。
実証テストは、より深刻で再現可能な悪用経路を示しており、これは標準的なスコアリングルールの下でのより高いCVSS 3.1基本スコアと一致します。
独立した評価者は、この文書に記載された観測された悪用条件を使用して重大度を評価することが推奨され、公開された重大度が狭く範囲設定されたベースラインではなく実際の影響を反映するようにします。
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
セキュリティヘッダーは~1743から始まります: 1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type" < Content-Type: text/html; charset=UTF-8
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
...followed by the SVG payload being printed and rendered inline.
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition" < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg