注目

研究・分析 / RESEARCH / PHISHING / ISC

SANS ISCが多形フィッシングページを分析 — 難読化のバグで無限ループ、50回取得で1件が機能せず

SANS Internet Storm Centerは2026年8月27日、アクセスのたびにソースが変化する多形フィッシングページの分析を公開した。難読化に用いたグローバル変数 k の再利用が原因でデコードが無限ループし、50回の取得で1件が正常に描画されないことを確認した。

攻撃手法

SANS Internet Storm Center(ISC)のハンドラー Jan Kopriva氏は2026年8月27日、アクセスのたびにソースコードが変化する多形(polymorphic)フィッシングページの分析「A polymorphic phishing page (that occasionally breaks itself)」を公開した。ISCは、同一URLへの複数回アクセスで毎回異なる難読化JavaScriptとHTMLが配信され、一部では難読化のバグにより頁が無限ループで描画されないことを突き止めた。

本分析は、静的なハッシュや文字列シグネチャに依存した検知の限界を示す事例だ。国内でもフィッシング報告が月6万件を超える中で、多形化が検知回避だけでなく、攻撃者自身の到達率を下げる副作用を伴う点も含めて理解する必要がある。

きっかけ — 30秒固まるフィッシングページ

ISCによると、ハンドラー宛てに届いたフィッシングメールは一見すると平凡だった。リンクは hxxps://addresses.performs.vu/communications.html?good=[recipient_address] という典型的な構造で、特段不審な点はなかった。

しかし、リンクを開くとブラウザが約30秒間固まり、仮想マシンのCPU 1コアが100%で張り付いた。HTMLソース自体の取得は瞬時に完了していたため、原因はサーバではなく頁内の難読化されたJavaScriptにあった。

ISCがソースを調べると、ほぼ全体が難読化されたJavaScriptで、特に次の二つの関数(読みやすく整形したもの)が注目された。

function _il(m) { for(k=0; 64>k; k++) { m[_lV(_ie(),k)]=k } return m }
function _YF(m,h) { var v=""; for(k=m; k<=h; k++) { v=v+String.fromCharCode(k) } return v }

二つの関数はともに ループ変数 kvarlet で宣言せず、グローバル変数として使っている_il はデコード処理の一部で k0 から 63 まで回す想定だが、その内部で毎回呼び出す _ie() がさらに _YF() を複数回呼び出す。_ie() の最後の呼び出しは _YF(47,47) で、文字 /(ASCII 47)を生成する。

_YF(47,47)k47 に設定して1回実行し、k48 に進めて終了する。制御が戻った外側の _ilk48 から 49 に進め、再び _ie() を呼び出す。すると再び _YF(47,47)k48 に戻すため、48 → 49 → 48 → 49 … の往復で外側のループが 49 から進まなくなる。ISCは、この変数スコープの不備が無限ループの原因だと特定した。内側のループを正しくローカル変数に修正すると、デコードは正常に完了し、背後にあったのは平凡な認証情報窃取フォームだった。

再アクセスで判明した多形性

当初、ISCは作者が難読化の実装を誤っただけと考えた。だが、同じURLに再アクセスすると頁は正常に表示され、複数回の再試行でも成功した。さらにソースコードは毎回異なっていた。関数名や変数名、関数の並び順、数値定数の表現、ペイロードを含む大きな符号化ブロック、さらには SolutionViewerCredentials といった頁タイトルまでリクエストごとに変化していた。

ISCは仮説を検証するため、同一URLを50回取得するスクリプトを実行し、LLMの助けを借りて比較した。結果は次の通りだった。

  • 50件すべてでSHA-256が異なる、すなわち50通りの頁が生成された
  • 頁タイトルは21通りに分散した
  • 50件中49件は正常にデコードできたが、1件は最初の頁と同様にグローバル変数 k の衝突で無限ループした
  • 難読化を手動で修正すると、その1件も正常にデコードできた

多形性は初期のJavaScriptラッパーに留まらなかった。50通りの頁から復号された最終的なフィッシングHTMLも50通りすべてで異なっていた。フォーム名や入力欄の名前、CSSクラス、要素ID、画像読み込み時のパラメータ、可視文字列内に挿入された零幅文字(zero-width characters) まで毎回変更されていた。一方で、利用者に提示されるフォームの見た目と基本的な窃取機能は同一だった。

ISCが集めた 約56件(50件の自動取得と手動アクセスの合計)中、2件が同様のループバグで機能しなかった。ISCは「正確な失敗率を断定することはできない」としつつも、特定の1回だけの破損ではなく、コード生成機構が繰り返し非機能な頁を作り得ることを明確にしている。

なぜ多形にするのか — 検知回避とコスト

多形フィッシングページ自体は新しい概念ではない。ISCは、10年以上前から学術的に議論され、アクセスごとにランダムなHTML属性値を生成する頁が以前から実戦で使われてきたことや、JavaScriptが同じ処理を異なる見た目のコードで実現しやすいことを指摘している。

狙いは明確だ。ハッシュ値やランダムに生成された識別子、単純な文字列シグネチャに依存した検知を無力化する。毎回が事実上新しいコピーであれば、それらの静的な成果物の価値は大幅に下がる。ISCは一方で、多形化が万能の回避策ではないことも強調する。頁の根底にあるロジックや挙動、構造的な特徴の多くは変換を経ても残存するためだ。ただし、静的な成果物に過度に依存した検知機構のコストは確実に押し上げるとしている。

皮肉なことに、今回の事例では検知回避のための難読化が、攻撃者自身のコストも押し上げていた。少なくとも約3.6%(56件中2件)の頁が被害者に届いても機能せず、認証情報を窃取できないためだ。ISCは「最初の頁が正常に読み込まれていれば、ありふれたフィッシングページとして見過ごしていたはず」とし、バグがなければ注目されなかった頁が、バグゆえに興味深い事例になったと振り返っている。

何がコードを生成しているのか

ISCは最後に、何が多形コードを生成しているのかを考察している。生成AIの流行からLLMバックエンドを想起しやすいが、ISCは**Unit 42が2026年1月に示した概念実証(PoC)**に触れつつも、今回のサンプルでLLMの関与を裏付ける証拠はないとしている。そのPoCでは、LLMがリアルタイムで構文的に異なるフィッシングJavaScriptを生成し、訪問ごとに固有の亜種を作ることが示された。

ISCがより妥当とみるのは、従来型の多形難読化ツールだ。個々の頁コピー間の変換が体系的であることや、繰り返し発生するグローバル変数の衝突が、変数スコープを正しく考慮しない単純なランダムなリネームと並べ替え機構に合致することが根拠だとしている。

日本の組織への示唆

本分析は、国内で多発する認証情報窃取型フィッシングへの対策を考えるうえで示唆に富む。同一URLでも毎回ハッシュや文字列が変化する多形頁に対しては、ハッシュや文字列のブロックリストだけでは防御が不十分になり得る。ISCが示すように、頁の構造や振る舞い、ネットワーク通信といった動的な特徴への着目が重要になる。

同時に、多形化の実装不良が攻撃の到達率を下げるという今回の観察は、攻撃者側の品質管理の不完全さを示す。防御側としては、SANS ISCが公開した分析手法や、Talosが同日に公開したJavaScript難読化の体系的な解読ワークフローを参照し、難読化されたスクリプトを安全に復号・解析できる体制を整えることが望ましい。ISC自身も、今回の解析でLLMを比較作業の補助に用いたことを明記しており、AIをサンドボックスの代替ではなく補助として使う姿勢が参考になる。

出典

この記事は、公開情報と編集部の調査・知見をもとに構成しています。内容は公開時点の情報に基づく解説であり、個別の環境への診断・対応を保証するものではありません。重要な判断では、記事内の出典や各提供元の最新情報もご確認ください。