##問題
WEBアプリで、velocityを使用してHTMLテンプレートを記述する場合、EscapeToolを使用してエスケープ処理を行うのが普通だが、項目出力の都度エスケープ処理を記載しなければならず、記載忘れでクロスサイトスクリプティングなど重大な脆弱性を埋め込むおそれがある。
##解決方針
デフォルトでHTMLエスケープを行うようにして、コーディング時の負担を軽減する
##課題
全ての項目をエスケープすれば良いわけではない(出力項目の中にはHTMLタグを埋め込んでやる必要があることもある)。
ココに記載のあるように、VelocityプロジェクトでHTMLエスケープを自動で実施する仕組みを作っていたこともあるが、一律同じエスケープ処理を行うことは「実用的でない」としてdeprecatedになっている。
→一律エスケープするのではなく、デフォルトでエスケープはするが、エスケープしない場所を指定できるようにすれば良いのでは、と思った。
###実装案
- ココを参考にして、ある変数が定義されている場合のみエスケープを行う拡張を実装する。
- エスケープをするかどうか判断する変数の中身を入れ替えるマクロを定義する
- テンプレート記述時、エスケープをしたくない箇所のみ、マクロを使用してエスケープ対象から除外する。
→デフォルトでエスケープ処理し、セキュリティを担保しつつもエスケープしてはいけない箇所をコーディング時に切り分けることができる
###コード例
springboot1.2系での使用を想定
実務がこのバージョンだから。。。
- エスケープ処理
package com.example.demo;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.velocity.app.event.implement.EscapeReference;
import org.apache.velocity.context.Context;
import org.apache.velocity.util.ContextAware;
public class ReferenceInsertionEventHandler extends EscapeReference implements ContextAware {
private final String ATTR_KEY = "__escape_html__";
private final String ATTR_VALUE = "__raw__";
private Context ctx;
protected String escape(Object o) {
if(ctx.containsKey(ATTR_KEY) && ATTR_VALUE.equals(ctx.get(ATTR_KEY))){
//エスケープ除外用の変数が定義されていたらそのまま出力
return o.toString();
} else {
//エスケープ除外用の変数が定義されていなかったらエスケープして出力
return StringEscapeUtils.escapeHtml(o.toString());
}
}
protected String getMatchAttribute()
{
return "eventhandler.escape.html.match";
}
public void setContext(Context context) {
this.ctx = context;
}
}
- マクロ
#macro(raw)
#set( $__escape_html__ = "__raw__" )
#end
#macro(endraw)
#set( $__escape_html__ = "__" )
#end
- テンプレート
#parse("escape.vm")
エスケープされる:<br />
${attributeA}<br />
${attributeB}<br />
#raw()
エスケープされない:<br />
${attributeA}<br />
${attributeB}<br />
#endraw()
エスケープされる:<br />
${attributeA}<br />
${attributeB}<br />
##イケてないところ
- javascriptのエスケープは?
- 変数名かぶるかも
##解決策
- ThymeleafやFreeMarkerに移行する
- プロジェクト開始時からきちんと考えて準備しておく
→最初からしっかり決めておけば苦労は少ないけど、後から全部修正するのは大変な苦痛を伴う