雑記

2000|01|
2003|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|
2007|01|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|09|11|
2009|02|03|05|06|07|08|10|11|12|
2010|01|03|04|05|06|07|08|09|10|
2011|05|06|09|10|
2012|03|07|09|12|
2013|01|02|04|05|07|08|10|11|
2014|04|05|08|10|12|
2015|01|05|
2016|09|

2008-02-04 [長年日記]

[Ruby] ERB でのタグ出力を簡単かつ安全に

水無月ばけらのえび日記の「安全なテンプレートシステムはあるのか」を読んでふと思いついたアイデア。

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: &lt;SCRIPT&gt;alert(&quot;!&quot;)&lt;/SCRIPT&gt;

-----
a.tainted?: false
a.clarified?: true
h a: <SCRIPT>alert("!")</SCRIPT>

-----
a.tainted?: true
a.clarified?: true
h a: &lt;SCRIPT&gt;alert(&quot;!&quot;)&lt;/SCRIPT&gt;

-----
a.tainted?: true
a.clarified?: false
h a: &lt;SCRIPT&gt;alert(&quot;!&quot;)&lt;/SCRIPT&gt;

-----
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にしようかとも思ったのですが、さすがに自重。