#はじめに
前回の続きとして、XSS対策について触れてみたい。XSSといっても色々あるが、今回JSONの文字列の中にJava Scriptのコードを埋め込まれる場合の問題点にフォーカスしたい。
#環境
- Java 1.8
- Tomcat 8.0.53
- Jackson 2.10.1
#問題となる状況
前回サーバ側は、URLでGETでアクセスするとJSONを返却するサーブレットを作成したが、今回そのままそれを利用する。そして返却するJSONデータの中にJavaScriptのコードを埋め込んでみる。具体的には以下のJSONを返却するようにした。
{"id":1,"name":"kimisyo","datas":["Programmer","Data Scientist<script>alert('hello!')</script>"]}
うまくサニタイジングされないと、<script>alert('hello!')</script>
の部分のJavaScriptのコードが実行されてしまう。
まずは、一般的な方法として、AjaxによりサーバのURLにアクセスし、その結果をJSONとして読み込み、中身を表示するJavaScirptを実行してみる。ソース(jspファイル)は以下の通りだ。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>jQuery Hello World</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script>
$.ajax({
url:"http://localhost:8080/servletTest/helloworld", // 通信先のURL
type:"GET", // 使用するHTTPメソッド
// dataType:"json", // 応答のデータの種類
dataType:"text", // 応答のデータの種類(xml/html/script/json/jsonp/text)
timespan:1000 // 通信のタイムアウトの設定(ミリ秒)
}).done(function(data,textStatus,jqXHR) {
var data2 = JSON.parse(data);
alert(data2.datas);
// failは、通信に失敗した時に実行される
}).fail(function(jqXHR, textStatus, errorThrown ) {
alert("error");
// alwaysは、成功/失敗に関わらず実行される
}).always(function(){
alert("complete");
});
</script>
</head>
<body>
<div id="test"></div>
</body>
</html>
このjspのURLをブラウザよりたたくと、以下のようにJSONのデータの中身が表示される。JSONに埋め込んだJavaScriptがそのままデータとして読み込まれており、問題のない動作である。
では、次にJSONを返却するサーブレットのURLを直接Edgeでたたいてみる。すると見事に埋め込んだJavaScriptがブラウザにより実行されてしまう。サーバ側の開発に携わる者としては見たくない動作だ。
#回避策
ブラウザが"<"と ">"で囲まれた箇所をhtmlのタグとして認識してしまったことが問題であるため、JSONの中の"<", ">" 等の特殊文字をUnicodeエスケープシーケンスに変換してやればよい。
まずは、以下のクラスを作成する。エスケープしたい文字を、CustomCharacterEscapesメソッドに追加していく。
package servletTest;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.CharacterEscapes;
public class CustomCharacterEscapes extends CharacterEscapes {
private final int[] asciiEscapes;
public CustomCharacterEscapes()
{
int[] esc = CharacterEscapes.standardAsciiEscapesForJSON();
esc['"'] = CharacterEscapes.ESCAPE_STANDARD;
esc['\''] = CharacterEscapes.ESCAPE_STANDARD;
esc['/'] = CharacterEscapes.ESCAPE_STANDARD;
esc['\n'] = CharacterEscapes.ESCAPE_STANDARD;
esc['>'] = CharacterEscapes.ESCAPE_STANDARD;
esc['<'] = CharacterEscapes.ESCAPE_STANDARD;
asciiEscapes = esc;
}
@Override
public int[] getEscapeCodesForAscii() {
return asciiEscapes;
}
// no further escaping (beyond ASCII chars) needed:
@Override
public SerializableString getEscapeSequence(int ch) {
return null;
}
}
次に前回のサーブレットのところで一部修正する。
package servletTest;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
// 参考 https://itsakura.com/java-jackson
@WebServlet("/helloworld")
public class ServletTest extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//Javaオブジェクトに値をセット
JsonBean jsonBean = new JsonBean();
jsonBean.setId(1);
jsonBean.setName("kimisyo");
List<String> datas = new ArrayList<>();
datas.add("Programmer");
datas.add("Data Scientist<script>alert('hello!')</script>");
jsonBean.setDatas(datas);
ObjectMapper mapper = new ObjectMapper();
mapper.getFactory().setCharacterEscapes(new CustomCharacterEscapes());
try {
//JavaオブジェクトからJSONに変換
String testJson = mapper.writeValueAsString(jsonBean);
//JSONの出力
response.getWriter().write(testJson);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
具体的には、mapper.getFactory().setCharacterEscapes(new CustomCharacterEscapes());
を追加している。
さあ実験してみよう
上のServletのURLをブラウザより直接たたくとブラウザに以下の通り表示される。Unicodeシーケンスに変換されており、埋め込んだJavaScriptのコードは実行されない。
{"id":1,"name":"kimisyo","datas":["Programmer","Data Scientist\u003Cscript\u003Ealert(\u0027hello!\u0027)\u003C\u002Fscript\u003E"]}
では、Unicodeシーケンスに変換しても、JSONを受け取ったJavasScirpt側では正しくJSONデータとして解釈されるだろうか。
このJSONをJavaScriptでAjaxでアクセスして表示させると、変換しない場合と同様に、"<"や">"がJSONのデータとして読み込まれた結果が表示され、正しく解釈されたことが分かる。
#おわりに
次は HTMLにJSONを埋め込んでJavaScript から利用するケースの問題点について取り扱ってみたい。
#参考