水無月ばけらのえび日記の「安全なテンプレートシステムはあるのか」を読んでふと思いついたアイデア。
require 'erb'
include ERB::Util
class Object
def clarify
raise SecurityError.new("Object is tainted.") if self.tainted?
@clarified = true
self
end
def unclarify
@clarified = false
self
end
def clarified?
@clarified || false
end
end
class ERB
module Util
public
def h(s)
if s.tainted? || ! s.clarified?
html_escape(s)
else
s.to_s
end
end
end
end
########## test ##########
SCRIPT = <<EOS
a.tainted?: <%= a.tainted? %>
a.clarified?: <%= a.clarified? %>
h a: <%=h a %>
EOS
a = '<SCRIPT>alert("!")</SCRIPT>'
print "-----\n"
print ERB.new(SCRIPT).result(binding)
print "\n-----\n"
a.clarify
print ERB.new(SCRIPT).result(binding)
print "\n-----\n"
a.taint
print ERB.new(SCRIPT).result(binding)
print "\n-----\n"
a.unclarify
print ERB.new(SCRIPT).result(binding)
print "\n-----\n"
a.clarify
実行結果はこちら
-----
a.tainted?: false
a.clarified?: false
h a: <SCRIPT>alert("!")</SCRIPT>
-----
a.tainted?: false
a.clarified?: true
h a: <SCRIPT>alert("!")</SCRIPT>
-----
a.tainted?: true
a.clarified?: true
h a: <SCRIPT>alert("!")</SCRIPT>
-----
a.tainted?: true
a.clarified?: false
h a: <SCRIPT>alert("!")</SCRIPT>
-----
t.rb:6:in `clarify': Object is tainted. (SecurityError)
from t.rb:60
解説すると、Objectクラスに「汚染」フラグとは別に「浄化済み」フラグを設け、そのObjectが汚染されておらず、かつ浄化済みであれば変数の内容をエスケープせずに出力するようERB::Util::hを再定義しただけ。エスケープを抑制する条件を単に tainted? == false としないのは、事の発端であったプログラマにセキュリティを意識させるための工夫のつもり。
taintも再定義して中で @clarified = false したいんですが、そこまでは腕がありません。あと、clarified?の「|| false」とかも@clarifiedの初期化の仕方がわからず、ひよった結果です。
あと、名前はsanitizeにしようかとも思ったのですが、さすがに自重。