JSP/Servlet | クライアントとサーバー間の値の受け渡し | 受け取ったデータをエスケープ処理する

クライアントとサーバーの間でデータを交換する処理を学ぶとき、あわせて学ぶべきことがある。それは「データのエスケープ」についてである。

ユーザーから送信された値を使う場合、「ユーザーはどのような値を送ってくるかわからない」ということを念頭に置く必要がある。特に注意しなければならないのがJavaScriptである。入力フィールドに<script>タグを使ったスクリプトを書いて送信されたらどうなるだろうか。そのテキストをそのまま画面に表示する処理になっている場合、ページが表示されるときにスクリプトが実行される可能性がある。最近のブラウザーでは防がれて実行されないことも多いが、それに依存してはいけない。たとえば掲示板やコメント投稿のようなシステムでは、そのようなスクリプトが投稿されて表示されるようになると、他の人がアクセスするたびに意図しないスクリプトが実行されてしまう。

一般にXSS、クロスサイトスクリプティングと呼ばれるサイト攻撃は、このような脆弱性を狙ったものである。ここで扱っている程度のサンプルなら問題は起きないかもしれないが、データベースなどにデータを蓄積して表示するシステムになると、この種のトラブルは避けられない。今の段階で、どう対処すればよいかだけでも学んでおこう。

この種の攻撃への対策の基本は「テキストを出力する前にエスケープ処理する」ことである。たとえばHTMLタグで使われる<>のような記号を&lt;&gt;に置き換えるだけで、そのタグを無効化できる。このように特別な機能を持つ記号類をエスケープ処理することで、そのデータに含まれる機能を無効化する。

以下の非常に簡単な例を見てみよう。

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%
String inpt = request.getParameter("input");
inpt = inpt == null ? "" : inpt;
String chk = request.getParameter("check");
chk = chk == null ? "OFF" : "ON";
String rd = request.getParameter("radio");
rd = rd == null ? "" : rd;

String str = "INPUT:" + getEscapedString(inpt) + "<br>" +
        "CHECK: " + getEscapedString(chk) + "<br>" +
        "RADIO: " + getEscapedString(rd) + "<br>";
%>
<%!
public String getEscapedString(String s){
    String str = s;
    str = str.replace("&","&amp;");
    str = str.replace("<","&lt;");
    str = str.replace(">","&gt;");
    str = str.replace("\"","&quot;");
    return str;
}
%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Sample jsp</title>
    <style>
    h1 {font-size:16pt; background:#AAFFAA; padding:5px; }
    </style>
</head>
<body>
    <h1>Sample jsp page</h1>
    <p>このページはサンプルです。</p>
    <p><%=str%></p>
    <form method="post" action="hello.jsp">
    <table>
        <tr>
            <td>入力</td>
            <td><input type="text" id="input" name="input"></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="checkbox" id="c1" name="check" value="Une"><label for="c1">チェックボックス</label></td>
        </tr>
        <tr>
            <td></td>
            <td>
                <input type="radio" name="radio" id="r1" value="first"><label for="r1">ラジオボタン1</label><br>
                <input type="radio" name="radio" id="r2" value="Second"><label for="r2">ラジオボタン2</label>
            </td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="送信"></td>
        </tr>
    </table>
    </form>
    </body>
</html>

ここではテキストを送信するとき、送られたテキストに含まれる<>"&のような記号をすべてエスケープ処理して表示している。エスケープ処理はgetEscapedStringというメソッドとして定義している。テキストを出力するときは、出力するテキストをgetEscapedStringでエスケープ処理して表示すればよい。

また、エスケープ処理で注意すべきなのは、「テキスト入力された値だけを処理すればよい」と考えないことである。ここではチェックボックスやラジオボタンの値までgetEscapedStringで処理している。「そのように送られてくる値は決まっているから不要ではないか」と思うかもしれない。しかしJSPではGETでもPOSTでも同じようにgetParameterで値を取得できる。たとえばhello.jsp?check=hogehogeのようにURLを指定してアクセスし、本来のcheckとは異なる値を渡すことができる。getMethodでPOSTだけを受け付けるように処理していない場合、その値がそのまま処理されてしまう。

そのため、プログラム内でクライアント側にテキストを出力するときは、常にエスケープ処理するようにしよう。