可以说跨站脚本攻击(XSS)是包含多种变化的复杂问题,但万变不离其宗,我们先说说前三大陷阱:
HTML转义(HTML escaping)?不够!
研发人员很少能够意识到HTML转义(使用HTML entities)并不总是HTML页面处理动态输出数据正确的解决方案。世界上没有完美的转义技术-能够像魔术师一样把动态数据安全的转义成为HTML输出上下文。做为研发人员,使用转义(Escaper)还是净化器(Sanitizer)取决于HTML动态输出写入的目标(URL, Javascript字符串…..)
框架的意义不大
框架通常包含转义器(Escaper)。但是除非你使用一个专门面向安全的库,如Coverity Security Library(CSL),否则不可能获取所有必须的转义器。举个例子,你不可能在Spring框架中获取面向CSS 字符串的转义器。这些框架的转义器(HTML Entities,Javascript字符串…),从设计之初就不是面向安全的,而且通常不会考虑某些安全相关的关键字。
相互嵌套的上下文会迷惑我们的意识(inception style)
在修复XSS过程中最重要的一点是嵌套HTML上下文。下文是一个例子,显示了如何构造一个特定的嵌套上下文堆栈,并确定使用什么样的转义器。
HTML 代码片段:
<a href="javascript:hello('${content}')">...
这个实例中,内容属于使用Java Expression Language(EL)符号写入HTML页面的动态数据, (所以, ${content}是很有意思的数据啊J).
仔细分析content被添加的位置,我们看到content被封装在一个:
· HTML 属性: 属性内容被双引号 "包含起来。
· URL: 我们正在分析的是一个 href 属性,应该是一个给浏览器的URL地址。
· JavaScript: 我们分析到关键字 javascript: scheme 和接下来的内容将被认为是JavaScript
· JavaScript 字符串:内容包含一个字符串,将被传递给JavaScript函数 hello() 。
要了解HTML上下文,我们需要确认浏览器在获取JavaScript内容后,最终将做什么动作:
1. HTML 解析器(parser)获取href属性和未转义的HTML entities内容。
2. 解析URL目标并识别这是一个 javascript: scheme
3. 获取URL内容( javascript:之后)并做URL decode解码。
4. 传递URL的内容给JavaScript
5. Javascript解析器运行并获取包含内容的JavaScript字符串
6. Javascript字符串的内容被处理进入字符串转义序列:Javascript字符串解码。
这些是浏览器执行的解码步骤。研发人员需要复制这些顺序,以保证HTML上下文堆栈的内容安全:
1. 被引号包含的HTML属性(Attribute)
2. URL
3. JavaScript 字符串
所以如果要安全的将这些内容输出,研发人员需要做这些事情:
<a href="javascript:hello('${cov:htmlEscape(cov:uriEncode(cov:jsStringEscape(content)))}')"
>...
但是如何知道那些HTML上下文或者内容需要转义?是否需要准备一个全面的HTML5转义器?答案是否。在这个世界上有#N个关键的上下文,你需要真正的理解和负责所有可能遇到的状况。这篇文章接下来将详细概括这些内容。如果你需要插入动态数据到一个本文没有介绍到的上下文,请寻求本地安全专家的支持。不要尝试通过控制你的浏览器来达到不目的,下文才是真正保证你正确的步骤...
Coverity安全库(Coverity Security Library)
Coverity安全库(Coverity Security Library ,简称CSL)是一个安全、轻量级、方便免费的编码仓库,能够协助处理HTML和SQL动态数据。
CSL 现在能够在Github上获取到,链接为https://github.com/coverity/coverity-security-library, Maven Central上也能获取到。
在本文档中,我们使用CSL作为转义器和净化器的参考实现,协助插入动态数据到Web页面中。我们同样使用Hava表达式语言(EL)的符号。这些Java函数也一样是开源的,请查看Github上的使用文档。
安装与使用(Installationand Usage)
请参考github上的文档和代码示例了解如何将CSL应用在你的开发过程中,如Java,JSP和EL中。
让事情变得正确(GettingIt Right)
HTML
HTML 标准组件
使用场景:
在tag内部展示一些动态数据。
正确修正:
这是最简单的使用场景- 只是动态内容中的HTML 转义:
<div>
Hello ${cov:htmlEscape(name)}!
</div>
HTML属性
使用场景:
在Tag中的动态的构建Attribute内容
正确修正:
首先,确保你使用的是单引号或双引号包含了属性,而不是倒引号(`````)。所有的属性都必须做这种操作。永远不要使用不包含引号的HTML 属性!
下一步,检查这个属性是不是一个URL, JavaScript, CSS 或其他嵌入式的语言,如果是的话,找到attribute的类型定义:
如果不是任何一种属性类型,那么使用HTML转义器:
<div data="${cov:htmlEscape(content)}">
HTML属性中的JavaScript 字符串
使用场景:
插入一些动态数据到包含JavaScript的属性中
正确修正:
首先,确保所有的属性都正确的使用引号包含,具体请参考HTML属性章节。
其次,确认正在插入的JavaScript数据字符串,也是被引号包含起来的JavaScript字符串。如果正在尝试插入字符串之外的数据,请查看CSL文档内容中JavaScript转义器的替换方案。
接下来,要认识到这是一个嵌套上下文,那么顺序是非常重要的,浏览器处置的解码步骤为:
1. HTML 属性
2. JavaScript 字符串
所以面向动态数据,需要实现一个JavaScript字符串转义器,然后实现一个HTML转义器,就像这样:
<div onclick="jsFunc('${cov:htmlEscape(cov:jsStringEscape(content))}')">
Click me
</div>
HTML属性中的全量URL
使用场景:
插入动态URL到一个能够理解URL的属性中,如链接或iframe。
正确修正:
首先确保这个属性已经被正确的使用引号包含(参见HTML 属性章节)。
其次,确认URL不是需要被限制在一个受控的服务器内容,如flash文件的object tag或 script tag.
接下来,由于这看起来像是一个嵌套上下文,整个URL不能被正确的转义以避免XSS,但是CSL有一个函数能够将危险的URL(如用javascript: scheme开头的那些…)正确的转换成为安全的URL(称之为asURL)。为了能够正确使用这个函数,你需要将其应用到一个全量构建的URL。
所以在这种状况下,我们将使用asURL 函数和一个HTML 转义器:
<a href="${cov:htmlEscape(cov:asURL(content))}">
Click me
</a>
HTML属性中的CSS 字符串
使用场景:
允许一个用户控制CSS字符串,如字体类型属性或CSS选择器,以控制一个特殊属性。
正确修正:
卡壳的状态下,最好用以下方案进行修正:
1. 将style属性使用双引号 "包含
2. 将CSS字符串使用单引号 '包含
3. 识别属性中的上下文堆栈作为CSS
使用一个CSS字符串转义器,然后使用一个HTML转义器:
<a style="font-family:'${cov:htmlEscape(cov:cssStringEscape(content))}'">
Click me
</a>
JavaScript
JavaScript 字符串
使用场景:
使用字符串写入方式传递一些动态数据到JavaScript区块中
正确修正:
看起来应该是嵌套上下文,但是只要是你是一个尝试过让多种浏览器正确运行一个网站的人,你就会知道,浏览器喜欢的东西是不同的,奇怪的而且很难保证正确的!
不过幸运的是,这不是一件很困难的事,因为只需要实现一个JavaScript字符串转义器。这个魔法能够确保JavaScript字符串像转义普通字符串一样转义<等特殊字符。如果使用CSL,我们已经实现了这些内容。举个例子::
<script type="text/javascript">
var str ='${cov:jsStringEscape(content)}';
// Do something with the string `str`
</script>
JavaScript字符串中的HTML
使用场景:
插入动态内容到即将被JavaScript使用的HTML
正确修正:
像上一个示例一样,动态数据将成为JavaS字符串转义的一部分。但是由于HTML内容需要被转义,因此我们使用HTML转义器:
<div id="forMyContent"></div>
<script>
var foo ="<h1>${cov:jsStringEscape(cov:htmlEscape(content))}</h1>";
$("#forMyContent")
.html(foo);
</script>
数字
使用场景:
通过写入脚本区块传递一个动态数字到JavaScript中。
正确修正:
由于并没有一个比较健全的方案能够将任意数据转换成一个数字并保持它的意义,我们在CSL中提供了一个函数,确保传递的字符串是一个数字,或将默认输出控制为0。你可以在以下场景中按全的使用:
<script type="text/javascript">
var num = ${cov:asNumber(content)};
// Do something with the number `num`
</script>
CSS
CSS 字符串
使用场景:
允许一个用户控制CSS字符串如: font-family ,或一个CSS选择器,如 style tag.
正确修正:
和脚本块中的JavaScript 字符串一样,这个也很简单-只要哦保证你的字符串被引号包含,然后使用CSL中的CSS字符串转义器:
<style>
#foo[id~='${cov:cssStringEscape(content)}'] {
background-color:pink!important;
}
</style>
CSS 字符串中的URL
使用场景:
在style tag中允许用户控制一个CSS URL,如背景图片。
正确修正:
首先确保CSS URL已经被正确的引号包含-双引号或单引号。
其次,你需要一个CSS字符串转义器,我们墙裂推荐使用CSL的转义器,而不是一个简单的JavaScript转义器,因为CSS转义的安全需求和JavaScript转义稍有不同。
和JavaS或HTML中的URLs不一样,你无须担心URL的内容,因为现在的先进CSS 转义器并不需要转换 javascript: 或其他特殊包含有害内容的URL(如包含 data: 的URL…)
<style>
#foo {
background:url('${cov:cssStringEscape(content)}');
}
</style>
CSS Colors
使用场景:
允许用户控制CSS Color,如背景色。
正确修正:
由于CSS colors并没有在字符串中特殊定义,因此并没有这个上下文的转义器,我们必须做一些验证或过滤。 CSL提供了一个过滤器,能够验证和过滤hex 或 text的颜色,同时避免注入攻击,使用示例:
<style>
#foo {
background-color: ${cov:asCssColor(content)};
}
</style>
URL
HTML中的URL Fragment/Hash
使用场景:
输出一个fragment/hash 是动态的URL。
正确修正:
# 之后的fragment 是URL的文本,并没有相关的转义器,所以需要使用父上下文的转义器,所以有时候会出现URL encode后浏览器你不decode的状况。
下面是一个示例,面向HTML hre中的URL,确保了fragment被正确安全的转义。
<a href="/path/page?elmt=1#${cov:htmlEscape(content)}">
Link name
</a>
URL Query 字符串和Path 属性
使用场景:
在path或query string中输出包含动态数据的URL,URL其他部分不是动态的。
正确修正:
URL的这些属性都能够被安全的encode: path, query string 名和query string 值.我们使用CSL uriEncode 函数来完成这些工作。注意并不是所有的URL Encode都适用,假如你不使用CSL,那么你可能需要使用嵌套的方法(HTML 转义器和 URL encoder) 。
<!--Assuming 'name' and 'value' are dynamic data -->
<a href="http://example.com/path/?${cov:uriEncode(name)}=${cov:uriEncode(value)}">
Click me
</a>
关于作者:
韩葆(笔名韩逸松),毕业于北京工业大学计算机学院,最初在 Sun 中国研究院为 OpenSolaris 操作系统搭建测试平台。后进入安全测试领域,深入研究白盒测试、静态分析、网络协议 Fuzzing、渗透测试等技术。经历了研发测试行业和软件安全行业在中国从无到有,从小到大的整个过程。2013 年作为中国区首席工程师加入 Coverity(2014年被Synopsys收购),成功的将静态分析技术引入中国,在 2014 年和 2015 年两次作为中国质量竞争力大会演讲嘉宾介绍代码静态分析与软件安全测试技术。目前担任 Synopsys Software Integrity Group 客户经理,致力于软件团队的研发测试与软件安全平台搭建,改善软件质量,提高软件安全性。
我的联系方式(微信):13311307163,邮箱是Bao.Han@synopsys.com,如果大家有什么问题或是想要试用Coverity,请随时联系:)。
友情链接