Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

フィードお問い合わせプライバシー© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
Instatic-Stored-XSS-CVE-2026-103931 — InstaticのisSafeUrl() URLフィルターにおける格納型XSSの概念実証と分析。先頭のC0制御文字がjavascript:スキームのブロックを回避する。 | Kitploit
ツール/GitHubGitHub/overgrowncarrot1/instatic-stored-xss-cve-2026-103931
静的分析脆弱性分析コード分析エクスプロイトウェブアプリケーション悪用ウェブセキュリティ
GitHubovergrowncarrot1/instatic-stored-xss-cve-2026-103931

Instatic-Stored-XSS-CVE-2026-103931

InstaticのisSafeUrl() URLフィルターにおける格納型XSSの概念実証と分析。先頭のC0制御文字がjavascript:スキームのブロックを回避する。

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
リポジトリを見る
1日前未レビュー
共有

isSafeUrl() における先頭 C0 制御文字を介した URL スキームフィルターバイパス

影響対象: Instatic v0.0.13 / コミット 63ad5d6(および src/core/html-sanitize/index.ts を含むそれ以前のすべてのリビジョン) コンポーネント: src/core/html-sanitize/index.ts → isSafeUrl() / safeUrl() 分類: CWE-79(格納型 XSS)、原因は CWE-184(不許可入力リストの不完全性)

概要

isSafeUrl() は、パブリッシャー全体にわたって javascript:、vbscript:、data: URL をブロックする唯一のチョークポイントです。スキームプレフィックスを検査する前に、.replace(/[\t\n\r]/g, '').trim() で入力を正規化します。

WHATWG URL パーサーは、スキームを読み取る前にすべての先頭 C0 制御文字(U+0000–U+001F)とスペースを除去します。JavaScript の String.prototype.trim() は U+0009、U+000A、U+000B、U+000C、U+000D、U+0020 および Unicode スペースのみを除去し、U+0000–U+0008 と U+000E–U+001F はそのまま残します。

そのため、例えば U+0001 を先頭に付けた URL はガードによって安全と報告されますが、すべてのブラウザはそれを javascript: スキームとして解析し実行します。

概念実証

未修正の src/core/html-sanitize/index.ts に対して:

payload           : "\x01javascript:alert(document.domain)"
isSafeUrl()       : true          <-- ガードは「安全」と報告
WHATWG URL scheme : javascript:   <-- ブラウザが実際に実行するもの
safeUrl() output  : "\x01javascript:alert(document.domain)"   (NOT collapsed to "#")

32 個の C0 制御文字のうち 27 個がチェックをバイパスします。U+0000 は HTML 属性の解析によって無効化され(NUL → U+FFFD)、26 個の確実に悪用可能なプレフィックス(U+0001–U+0008、U+000E–U+001F)が残ります。同じバイパスが vbscript: および data: フィルターも突破します。

既存のテストスイート(src/__tests__/publisher/utils.test.ts)は大文字小文字の折りたたみと埋め込まれたタブ(java\tscript:)をカバーしていますが、先頭の制御文字は決してカバーしていません。これがこの問題が検出されなかった理由です。

エンドツーエンドの経路

base.link は href: { type: 'url' } を宣言し、LinkPropsSchema はそれを制約のない Type.String({ default: '#' }) として型付けしています — 書き込み時の URL 検証はありません。したがって:

  1. escapeProps() は type: 'url' | 'image' | 'media' のプロパティを isSafeUrl(value) ? value : '#' にルーティングします — ペイロードは生のまま、意図的に HTML エスケープされずに通過します。
  2. base.link の render() は `<a href="https://github.com/overgrowncarrot1/instatic-stored-xss-cve-2026-103931/blob/main/%24%7BsafeUrl%28props.href%29%7D" …>` を出力します。safeUrl() は同じ壊れた isSafeUrl() で再チェックし、次に escapeHtml() を適用します — これは & < > " ' のみをエスケープし、制御文字には触れません。
  3. ペイロードはそのまま href 属性に到達します: <a href="https://github.com/overgrowncarrot1/instatic-stored-xss-cve-2026-103931/blob/main/%5Cx01javascript%3Aalert%28document.domain%29" target="_self">Click me</a>

影響を受けるシンク

これらはすべて同じ isSafeUrl() を通ります:

シンクファイル
すべての url / image / media モジュールプロパティ(link href、button href、image src、video src/poster、form action、form redirectUrl)src/core/publisher/escapeProps.ts:108
任意のノード上のユーザー設定による任意のカスタム HTML 属性src/core/htmlAttributes/attributes.ts:66
Markdown リンク/画像の href と srcsrc/core/markdown/renderMarkdown.ts:75
サイトの faviconUrlsrc/core/publisher/render.ts:322
すべてのベースモジュールの safeUrl() 呼び出しsrc/modules/base/utils/escape.ts

影響

低権限の編集者から管理者の完全な侵害への権限昇格。

組み込みの Client ロールは site.content.edit を保持しており、これだけでリンクの href やカスタム HTML 属性を設定するのに十分です。注入された URL はその後、管理者エディタのキャンバスでレンダリングされます。これは srcdoc iframe であり、/admin と同一オリジンです。

server/securityHeaders.ts:69-72 は /admin に対して frame-ancestors 'none'; base-uri 'self'; object-src 'none' のみを設定しており、script-src ポリシーは意図的にまだ設定されていないという明示的なコードコメントがあります。script-src がなければ、javascript: URL が管理者オリジンで実行されるのを止めるものは何もありません。

Owner または Admin がエディタで影響を受けるページを開き、その要素をアクティブにすると、ペイロードは管理者 SPA と同一オリジンで実行されます。セッション Cookie は HttpOnly であるため直接読み取ることはできませんが、ペイロードは被害者として管理者 API を操作できます(オーナーアカウントの作成、シークレットの読み取り、またはプラグインのインストール。そのサーバーエントリポイントはコード実行への経路です)。

公開サイトでは影響はより限定的です: cspPlan.ts は script-src 'none'(または 'self')を設定しており、これが javascript: URL をブロックします。しかし server/publish/frontendInjections.ts:377 は、インラインスクリプトを含む任意のページに対してこれを 'self' 'unsafe-inline' に緩和し、'unsafe-inline' は javascript: URL を再び許可します — そのため、それらのページでは公開サイトの XSS に到達可能です。

attributes.ts の docstring がすでにこの正確な脅威モデルを特定していることに注意してください(「公開サイト上で、そしてさらに深刻なことに、管理者エディタのキャンバス内(/admin と同一オリジン)で」)— ガードがそれを完全には実装していないだけです。

検証

ブラウザで確認済み(Chromium、各オリジンの CSP を再現する srcdoc iframe、リポジトリ自身の safeUrl() のバイト単位の出力をレンダリング):

再現したオリジン適用された CSP結果
管理者エディタのキャンバス(/admin)なし — securityHeaders.ts が出力するとおりjavascript: が実行された
公開ページ(ベースライン)script-src 'none' — cspPlan.ts が出力するとおりブロック(script-src-elem)
インラインスクリプト付きの公開ページscript-src 'self' 'unsafe-inline' — frontendInjections.ts:377 が出力するとおり注記参照

最初の 2 行は観察された結果です。管理者オリジンでの実行は document.domain を配信元オリジンとして報告し、不透明なコンテキストではなく同一オリジンでの実行を確認しました。

忠実性に関する注意: ハーネスは各ポリシーを <meta http-equiv> 経由で適用しますが、Instatic はそれを HTTP レスポンスヘッダーとして送信します。これらは script-src の強制においては等価ですが、実際の bun run dev インスタンスでの再現であれば本物のヘッダーが付随するでしょう。

修正

trim() に頼るのではなく、C0 + スペースの範囲全体を除去します。これはまさに React 自身の isJavaScriptProtocol 正規表現が ^[\u0000-\u001F ]* プレフィックスで行っていることであり、これが理論上のものではなく既知の実世界のバイパスクラスであることの有用なクロスチェックです。

--- a/src/core/html-sanitize/index.ts
+++ b/src/core/html-sanitize/index.ts
@@ -30,7 +30,11 @@ export function escapeHtml(value: unknown): string {
   * normalisation browsers apply during URL parsing.
   */
  export function isSafeUrl(url: string): boolean {
-  const normalized = url.replace(/[\t\n\r]/g, '').trim().toLowerCase()
+  const normalized = String(url ?? '')
+    .replace(/[\t\n\r]/g, '')
+    .replace(/^[\u0000-\u0020]+/, '')
+    .replace(/[\u0000-\u0020]+$/, '')
+    .toLowerCase()
    return (
      !normalized.startsWith('javascript:') &&
      !normalized.startsWith('vbscript:') &&

パッチ適用済みファイルに対して検証済み: ペイロードはブロックされ、safeUrl() はそれを # に折りたたみ、制御文字/スペースを先頭に持つ 99 個の危険な URL のうち 0 個が受け入れられたままとなり、既存の 14 個の isSafeUrl テストケースはすべて同一に動作します。

推奨されるリグレッションテスト

it('blocks javascript: behind a leading C0 control character', () => {
  for (let i = 0x00; i <= 0x1f; i++) {
    expect(isSafeUrl(String.fromCharCode(i) + 'javascript:alert(1)')).toBe(false)
  }
})

多層防御

/admin に実際の script-src を設定することも検討してください(server/securityHeaders.ts に記載された延期されたフォローアップ)。これにより、この 1 つのインスタンスではなく、このバグクラス全体を封じ込めることができます。

ツールをダウンロード