我正在尝试将URL放入data-
属性。特别是
<tr data-href="/page.cfm?Id=#EncodeForHTMLAttribute(ID)#">
...
或者也许应该
<tr data-href="/page.cfm?Id=#EncodeForURL(ID)#">
...
请注意,ID可以包含特殊字符
编辑:
以后我要去
$("tr").click(function() { window.location = $(this).data("href"); });
最佳答案
让我们分析一些方案:
完全不编码
<!--- our "tricky" ID --->
<cfset ID = '"><script>alert("my evil script");</script><div foo="'>
<!--- we are closing the data-href attribute, injecting our JS and start a new tag to complete the remaining tag --->
<cfoutput>
<div data-href="page.cfm?Id=#ID#"></div>
<!--- [data-href] is printed as: page.cfm?Id="><script>alert("my evil script");</script><div foo=" --->
</cfoutput>
结果
出现一个警告对话框,显示“我的邪恶脚本”。
结论
切勿让用户输入未经编码! (您已经知道这一点。)
编码查询字符串以将其输出为HTML
注意:您应该始终对HTML属性的完整值进行编码,而不仅仅是部分属性。
<!--- our "tricky" ID --->
<cfset ID = "&a=b?c">
<!--- we are having some reserved characters here that will confuse the browser's query string parser --->
<cfoutput>
<div data-href="#encodeForHtmlAttribute("page.cfm?Id=#ID#")#"></div>
<!--- [data-href] is printed as: page.cfm?Id=&a=b?c --->
<script>
var attr = document.getElementsByTagName('div')[0].getAttribute('data-href');
console.log(attr); <!--- page.cfm?Id=&a=b?c --->
<cfif structIsEmpty(URL)> <!--- test related: to prevent infinite redirection --->
location.href = attr;
</cfif>
</script>
</cfoutput>
<cfdump var="#URL#">
结果
请求
page.cfm
时,我们将被重定向到page.cfm?Id=&a=b?c
,即data-href
属性的纯值。但是,URL
的作用域转储将为我们提供键值对:Id: [empty string]
a: b?c
这是可以预期的,因为浏览器的查询字符串解析器无法区分字面意义和字符的技术目的。 I recently answered this here.
结论
当具有多个上下文(此处为HTML和URL / QueryString)时,对输出进行编码是不够的。
将查询字符串编码为URL
<!--- our "tricky" ID --->
<cfset ID = 'a&b="><script>alert("my evil script");</script><div foo="'>
<!--- we are mixing in both contexts now --->
<cfoutput>
<div data-href="page.cfm?Id=#encodeForUrl(ID)#"></div>
<!--- [data-href] is printed as: page.cfm?Id=a%26b%3D%22%3E%3Cscript%3Ealert%28%22my+evil+script%22%29%3B%3C%2Fscript%3E%3Cdiv+foo%3D%22 --->
<script>
var attr = document.getElementsByTagName('div')[0].getAttribute('data-href');
console.log(attr); <!--- page.cfm?Id=a%26b%3D%22%3E%3Cscript%3Ealert%28%22my+evil+script%22%29%3B%3C%2Fscript%3E%3Cdiv+foo%3D%22 --->
<cfif structIsEmpty(URL)> <!--- test related: to prevent infinite redirection --->
location.href = attr;
</cfif>
</script>
</cfoutput>
<cfdump var="#URL#">
结果
请求
page.cfm
时,我们将被重定向到page.cfm?Id=a%26b%3D%22%3E%3Cscript%3Ealert%28%22my+evil+script%22%29%3B%3C%2Fscript%3E%3Cdiv+foo%3D%22
,即data-href
属性的纯值。 URL
的作用域转储将为我们提供键值对:Id: a&b="><script>alert("my evil script");</script><div foo="
这次,浏览器的查询字符串解析器可以区分字面意义和字符的技术目的。但是这里的HTML上下文呢?好吧,由
encodeForUrl()
完成的百分比编码不会与HTML的保留字符冲突,因为%
在HTML中没有技术目的,也不会破坏任何内容。结论
从理论上讲,我们已经完成了。无需对URL编码的值进行HTML编码,因为这两种编码没有重叠。
将查询字符串编码为URL-AND-输出为HTML
<!--- our "tricky" ID --->
<cfset ID = 'a&b="><script>alert("my evil script");</script><div foo="'>
<!--- we are mixing in both contexts again --->
<cfoutput>
<div data-href="#encodeForHtmlAttribute("page.cfm?Id=#encodeForUrl(ID)#")#"></div>
<!--- [data-href] is printed as: page.cfm?Id=a%26b%3D%22%3E%3Cscript%3Ealert%28%22my+evil+script%22%29%3B%3C%2Fscript%3E%3Cdiv+foo%3D%22 --->
<script>
var attr = document.getElementsByTagName('div')[0].getAttribute('data-href');
console.log(attr); <!--- page.cfm?Id=a%26b%3D%22%3E%3Cscript%3Ealert%28%22my+evil+script%22%29%3B%3C%2Fscript%3E%3Cdiv+foo%3D%22 --->
<cfif structIsEmpty(URL)> <!--- test related: to prevent infinite redirection --->
location.href = attr;
</cfif>
</script>
</cfoutput>
<cfdump var="#URL#">
结果
请求
page.cfm
时,我们将被重定向到page.cfm?Id=a%26b%3D%22%3E%3Cscript%3Ealert%28%22my+evil+script%22%29%3B%3C%2Fscript%3E%3Cdiv+foo%3D%22
,即data-href
属性的纯值。 URL
的作用域转储将为我们提供键值对:Id: a&b="><script>alert("my evil script");</script><div foo="
似乎什么都没有改变,对吧?不完全是。这就是我们的
data-href
现在在最终HTML输出中的样子:page.cfm?Id=a%26b%3D%22%3E%3Cscript%3Ealert%28%22my+evil+script%22%29%3B%3C%2Fscript%3E%3Cdiv+foo%3D%22
如您所见,百分比编码现在还针对HTML进行了编码(
%
被编码为其十六进制表示形式%
)。结论
现在,对于两种情况,该值都是安全的。
还有更多的编码可以混入(以
encodeForJavaScript()
认为),但是您明白了。始终要考虑值中的哪些字符需要编码,以免出于技术目的而被误解。最终可能会有3到4个嵌套编码。但是再说一遍:通常,这些编码不会相互冲突,因此不一定需要针对所有上下文对其进行编码。