本サイトは以下の図のような構成を想定しています。
脆弱性体験サイトの仕組み
ユーザはログイン後、登録された検索したい会員名を、Webアプリケーションサーバで検索します。会員名は、以下に示すHTMLファイルのFormにある変数unameに格納して送信されます。
<form action="NotSecureSearchServlet" method="post">
検索したい会員の名前を入れて下さい(脆弱):<input type="text" name="uname" />
<input type="submit" value="検索" />
</form>
例えば、AIUEOという会員名で検索する場合に、Webブラウザから送信される値の一部を以下に示します。
uname=AIUEO
Webアプリケーションサーバは、Webブラウザから受け取った変数unameの値を用いてSQL文を構成します。構成されるSQL文を以下に示します。また、Webアプリケーションサーバは、構成したSQL文をDBMSに送信します。
SELECT * FROM user WHERE id='AIUEO'
このSQL文は、氏名(id)がAIUEOである会員の、すべての属性を射影します。
DBMSは、Webアプリケーションサーバから受け取ったSQL文を実行し、検索結果をWebアプリケーションサーバに送信します。
Webアプリケーションサーバは、DBMSから受け取った検索結果を表示するためのページを作成し、Webブラウザに送信します。Webブラウザは、受け取ったページを表示します。
データベースには、以下の会員情報が登録されています。
会員情報
脆弱サイトにログインする場合は、次のユーザ名とパスワードを以下の箇所に入力して「ログイン」ボタンを押します。成功すると「ログイン成功」と書かれたダイアログが表示され、入力した箇所に「こんにちはuetoさん」と表示されます。
ユーザ名とパスワードの入力
ログイン後に「こんにちはuetoさん」をクリックすると、会員検索のためのテキストボックスが2つ表示されます。いずれかのテキストボックスに検索したい会員名を入力して、「検索」ボタンを押します(検索するテキストボックスは2つありますが、どちらでも構いません)。例えば、uetoで検索すると、以下の図のように表示されます。
uetoで検索した場合
データベースに登録されていない会員名で検索した場合、データベースに登録されていない旨を伝える文が表示されます。 例えば、miuraで検索すると以下の図のように表示されます。 フォーム(脆弱)を利用した場合はユーザが入力した会員名が表示されますが、フォーム(preparedstatementの利用)を利用した場合はユーザが入力した会員名が表示されません。
フォーム(脆弱)を利用した場合
フォーム(preparedstatementの利用)を利用した場合
「ログアウト」ボタンを押すと、脆弱サイトからログアウトします(2つのテキストボックスのどちらでも構いません)。
参考:マスタリングTCP/IP 情報セキュリティ編(第2版)
8.3節 XSS攻撃とその対策
フォーム(脆弱)のテキストボックスに、下に示すXSS攻撃用のJavaScriptコードを会員名として入力し、検索を押して下さい。すると、次の図のようなダイアログが表示され、JavaScriptコードが実行されていることが確認できます。
ueto<script>alert(document.cookie);</script>
XSS攻撃用のJavaScriptコード
結果
上に示した、検索したい会員名としてのJavaScriptコードはデータベースに登録されていないので、Webアプリケーションサーバは登録されていない旨を伝えるページを作成しようとします。今回の例では、以下のページが作成されます。Webブラウザは、ueto以降のscriptタグ内(赤色の部分)をJavaScriptコードとして解釈し、alert(document.cookie);を実行します。
<html>
<body>
検索結果:ueto<script>alert(document.cookie);</script> について該当するデータがございません。
</body>
</html>
フォーム(脆弱)を利用した場合のページ
一方、フォーム(preparedstatementの利用)、正確には、Servlet(preparedstatementの利用)を利用する場合は、Webアプリケーションサーバは会員名をページに出力しないように設計されているので、以下のページが作成され、XSS攻撃は成功しません。preparedstatementについては、SQLインジェクションの章で説明します。
<html><body>検索結果:該当するデータがございません。</body></html>
フォーム(preparedstatementの利用)を利用した場合のページ
参考:マスタリングTCP/IP 情報セキュリティ編(第2版)
8.4節 SQLインジェクションとその対策
フォーム(脆弱)のテキストボックスに、下に示す攻撃のための文字列を会員名として入力し、検索を押して下さい。
aiueo' or 'A'='A
すると、次の図のような表が表示されます。これは、データベースからすべての会員情報を不正に取得できてしまうことを示しています。
フォーム(脆弱)を利用した場合
一方、フォーム(preparedstatementの利用)を利用する場合はプリペアドステートメントを利用しているので攻撃は成功せず、以下の図のように「該当するデータがございません。」となります。
フォーム(preparedstatementの利用)を利用した場合
Webアプリケーションサーバ(Servlet)は、(ユーザが会員名として入力した)攻撃のための文字列をWebブラウザから受け取ると、下の(Javaの)文を実行します。
String sql = "SELECT * FROM user WHERE id = '" + id + "'";
" + id + "に攻撃のための文字列が代入された場合、変数sqlの値として、下のSQL文が構成されます。
SELECT * FROM user WHERE id='aiueo' or 'A'='A'
このSQL文の、WHERE句に注目すると、検索条件は「id(データベース中の氏名)が'aiueo'と同値、もしくは、'A'が'A'と同値かどうか」です。idがどんな値でも、'A'と'A'は常に同値なので、検索条件は常に真となります。そのため、データベース内の全ての行で検索条件が真となり、全ての行が選択されてしまいます。
Webアプリケーションサーバは、ユーザが会員名として入力した攻撃のための文字列をWebブラウザから受け取ると、下の文を実行します。
// SQL文の用意. ?がプレースホルダです
String sql = "SELECT * FROM user WHERE id = ?";
// プリペアドステートメントオブジェクトの用意
PreparedStatement ps = conn.prepareStatement(sql);
// SQL文の1番目の?を、id(ユーザから受け取った攻撃のための文字列)に置き換え
ps.setString(1, id);
この結果、データベースの氏名(id)が「攻撃のための文字列」である会員が存在しないので、攻撃が成立せず、単に、「該当するデータがございません。」と出ます。
※本サイトでは、セキュリティの観点から「SQLインジェクションの仕組み」で示した文字列の時のみリクエストを通すようにしています。
参考:マスタリングTCP/IP 情報セキュリティ編(第2版)
8.5節 SQLインジェクションとその対策
脆弱サイトは、cookieを利用してセッション管理をしています。そのため、ログアウトボタンを押してログアウトをしなければ、Webブラウザを閉じた後に、もう一度アクセスした場合でも、ログイン状態になるように設計されています。確認してみて下さい。
次に、ユーザがログアウトボタンを押した時の動作を説明します。ユーザがログアウトボタンを押すと、WebブラウザがWebアプリケーションサーバに対して下に示すリクエストを送信します。
http://contents.saitolab.org/samples/LoginServlet?logout=true
このリクエストには、logout=trueというパラメータが記述されています。Webアプリケーションサーバは、このパラメータを受け取るとログアウト処理を行う設計になっています。
まず、脆弱サイトにログインして下さい。その状態で、悪友のmiuraちゃんから下に示すメールが届いたとします。あなたがメールに記載されているリンクをクリックすると、ログアウトしていないにもかかわらず、脆弱サイトから強制的にログアウトさせられてしまいます。確認してみて下さい。また、クリック後は更新(Ctrl+R)することで再度ログイン状態を維持することができます。
メールに記載されているリンク(幸せのページ)のコードを抜粋し、下に示します。このリンクをクリックすると、前提で示したリクエストと同じリクエスト(赤色の部分)が、Webアプリケーションサーバに送信されます。送信するのは脆弱サイトにログインしているユーザ(あなた)なので、あなたのログアウト処理が行われてしまいます。より正確には、あなたがログインした状態の情報を持つcookieを、そのリクエスト時に送信することにより、logout=trueというパラメータが、Webアプリケーションサーバで解釈されます。
<a href="http://www.saitolab.org/samples/LoginServlet?logout=true">幸せのページ</a>