概念
XSS攻击全称跨站脚本攻击,是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS,XSS是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。
项目环境
spring + struts2 +.....(仅列举相关的)
需求
防止xss攻击
分析
1.防止xss攻击,可以从请求处拦截特殊字符,核心是过滤特殊字符串
2.由于项目是采用struts2来处理请求的,所以应从struts处着手找方案
3.struts2中的StrutsPrepareAndExecuteFilter,核心是一个Filter,Action可以脱离web容器,让http请求和action关联在一起的
4.可以继承StrutsPrepareAndExecuteFilter类来实现特殊字符过滤
StrutsPrepareAndExecuteFilter分析
StrutsPrepareAndExecuteFilter与普通的Filter并无区别,方法除继承自Filter外,仅有一个回调方法,Filter方法调用顺序是init—>doFilter—>destroy
注意:只关心怎么实现可以跳过这个内容,直接看下面实现部分
1.init方法
init是Filter第一个运行的方法,主要的工作是初始化一些配置
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;
try {
//封装filterConfig,其中有个主要方法getInitParameterNames将参数名字以String格式存储在List中
FilterHostConfig config = new FilterHostConfig(filterConfig);
// 初始化struts内部日志
init.initLogging(config);
//创建dispatcher ,加载资源
dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
//初始化类属性:prepare 、execute
this.prepare = new PrepareOperations(dispatcher);
this.execute = new ExecuteOperations(dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
//回调空的postInit方法
this.postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}
1-1.FilterHostConfig类
可以看出,FilterHostConfig主要是对配置文件的一个封装,如果有需求要操作配置文件的参数,可以继承此类实现我们的业务
public class FilterHostConfig implements HostConfig {
private FilterConfig config;
/**
*构造函数
*/
public FilterHostConfig(FilterConfig config) {
this.config = config;
}
/**
* 根据init-param配置的param-name获取param-value的值
*/
public String getInitParameter(String key) {
return this.config.getInitParameter(key);
}
/**
* 返回初始化参数名的List
*/
public Iterator
return MakeIterator.convert(this.config.getInitParameterNames());
}
/**
* 返回上下文
*/
public ServletContext getServletContext() {
return this.config.getServletContext();
}
}
1-2.初始化Dispatcher
创建Dispatcher,会读取 filterConfig 中的配置信息
public Dispatcher initDispatcher(HostConfig filterConfig) {
Dispatcher dispatcher = this.createDispatcher(filterConfig);
dispatcher.init();
return dispatcher; }
Dispatcher类init的源码
public void init() {
if (this.configurationManager == null) {
this.configurationManager = this
.createConfigurationManager("struts");
}
try {
this.init_FileManager();
//加载org/apache/struts2/default.properties
this.init_DefaultProperties();
//加载struts-default.xml,struts-plugin.xml,struts.xml
this.init_TraditionalXmlConfigurations();
this.init_LegacyStrutsProperties();
//用户自己实现的ConfigurationProviders类
this.init_CustomConfigurationProviders();
//Filter的初始化参数
this.init_FilterInitParameters();
this.init_AliasStandardObjects();
Container ex = this.init_PreloadConfiguration();
ex.inject(this);
this.init_CheckWebLogicWorkaround(ex);
if (!dispatcherListeners.isEmpty()) {
Iterator i$ = dispatcherListeners.iterator();
while (i$.hasNext()) {
DispatcherListener l = (DispatcherListener) i$.next();
l.dispatcherInitialized(this);
}
}
this.errorHandler.init(this.servletContext);
} catch (Exception arg3) {
if (LOG.isErrorEnabled()) {
LOG.error("Dispatcher initialization failed", arg3,
new String[0]);
}
throw new StrutsException(arg3);
}
}
View Code
1-3.创建Dispatcher
将配置信息解析出来,封装成为一个Map,然后根据servlet上下文和参数Map构造Dispatcher
private Dispatcher createDispatcher(HostConfig filterConfig) {
HashMap params = new HashMap();
Iterator e = filterConfig.getInitParameterNames();
while (e.hasNext()) {
String name = (String) e.next();
String value = filterConfig.getInitParameter(name);
params.put(name, value);
}
return new Dispatcher(filterConfig.getServletContext(), params);
}
2.doFilter方法
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
//父类向子类转:强转为http请求、响应
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
if (this.excludedPatterns != null
&& this.prepare.isUrlExcluded(request,
this.excludedPatterns)) {
chain.doFilter(request, response);
} else {
//设置编码和国际化
this.prepare.setEncodingAndLocale(request, response);
//创建Action上下文
this.prepare.createActionContext(request, response);
this.prepare.assignDispatcherToThread();
request = this.prepare.wrapRequest(request);
ActionMapping mapping = this.prepare.findActionMapping(request,
response, true);
if (mapping == null) {
boolean handled = this.execute
.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
this.execute.executeAction(request, response, mapping);
}
}
} finally {
this.prepare.cleanupRequest(request);
}
}
2-1.setEncodingAndLocale
设置编码这是调用了Dispatcherde.prepare方法加载配置
public void setEncodingAndLocale(HttpServletRequest request,
HttpServletResponse response) {
this.dispatcher.prepare(request, response);
}
prepare方法只是做一些初始化和国际化的缺省设置
public void prepare(HttpServletRequest request, HttpServletResponse response) {
String encoding = null;
if (this.defaultEncoding != null) {
encoding = this.defaultEncoding;
}
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
encoding = "UTF-8";
}
Locale locale = null;
if (this.defaultLocale != null) {
locale = LocalizedTextUtil.localeFromString(this.defaultLocale,
request.getLocale());
}
if (encoding != null) {
this.applyEncoding(request, encoding);
}
if (locale != null) {
response.setLocale(locale);
}
if (this.paramsWorkaroundEnabled) {
request.getParameter("foo");
}
}
View Code
2-2.createActionContext
ActionContext是一个容器,这个容易主要存储request、session、application、parameters等
ActionContext是一个线程的本地变量,不同的action之间不会共享ActionContext,所以也不用考虑线程安全问题。
实质是一个Map,key是标示request、session、……的字符串,值是其对应的对象
private static ThreadLocal
protected Map
创建Action上下文,初始化thread local
public ActionContext createActionContext(HttpServletRequest request,
HttpServletResponse response) {
Integer counter = Integer.valueOf(1);
Integer oldCounter = (Integer) request
.getAttribute("__cleanup_recursion_counter");
if (oldCounter != null) {
counter = Integer.valueOf(oldCounter.intValue() + 1);
}
//从ThreadLocal中获取此ActionContext变量
ActionContext oldContext = ActionContext.getContext();
ActionContext ctx;
if (oldContext != null) {
ctx = new ActionContext(new HashMap(oldContext.getContextMap()));
} else {
ValueStack stack = ((ValueStackFactory) this.dispatcher
.getContainer().getInstance(ValueStackFactory.class))
.createValueStack();
stack.getContext().putAll(
this.dispatcher.createContextMap(request, response,
(ActionMapping) null));
//stack.getContext()返回的是一个Map
ctx = new ActionContext(stack.getContext());
}
request.setAttribute("__cleanup_recursion_counter", counter);
//将ActionContext存到ThreadLocal
ActionContext.setContext(ctx);
return ctx;
}
createContextMap方法
public Map
HttpServletResponse response, ActionMapping mapping) {
RequestMap requestMap = new RequestMap(request);
HashMap params = new HashMap(request.getParameterMap());
SessionMap session = new SessionMap(request);
ApplicationMap application = new ApplicationMap(this.servletContext);
//requestMap、params、session等Map封装成为一个上下文Map,逐个调用了map.put(Map p).
HashMap extraContext = this.createContextMap(requestMap, params,
session, application, request, response);
if (mapping != null) {
extraContext.put("struts.actionMapping", mapping);
}
return extraContext;
}
executeAction方法
封装执行的上下文环境,主要将相关信息存储入map,根据执行上下文参数,命名空间,名称等创建用户自定义Action的代理对象,执行execute方法,并转向结果
public void executeAction(HttpServletRequest request,
HttpServletResponse response, ActionMapping mapping)
throws ServletException {
this.dispatcher.serviceAction(request, response, mapping);
}
看到这,会有一种不知所措的感觉,毕竟源码都是毕竟乏味的,我整理了张关系图如下,后面我们要实现的就是重写StrutsPrepareAndExecuteFilter来管理
重写并添加了自己规则的Dispatcher,也就是执行者,这里,我举个现实中关系的例子:
FilterHostConfig:开发的规范Dispatcher:程序员InitOperations:项目经理StrutsPrepareAndExecuteFilter:老板
程序员要严格按照规范来写代码,项目经理要负责跟进程序员的整个工作过程,项目经理则只需对老板负责,定期汇报
上面的源码就是我按照这种关系分析的,这样比较好理解,关系就一目了然
(提示:如果图片看不清,可以右击保存到桌面查看)
执行者管理器的流程是这样的,后面实现部分就是着重修改这里,添加字符的拦截规则
(提示:如果图片看不清,可以右击保存到桌面查看)
实现
继承StrutsPrepareAndExecuteFilter类,重写init方法,在web.xml引用这个filter
@Override
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
dispatcher = initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}
public Dispatcher initDispatcher(HostConfig filterConfig) {
Dispatcher dispatcher = createDispatcher(filterConfig);
dispatcher.init();
return dispatcher;
}
private Dispatcher createDispatcher(HostConfig filterConfig) {
Map
for (Iterator e = filterConfig.getInitParameterNames(); e.hasNext();) {
String name = (String) e.next();
String value = filterConfig.getInitParameter(name);
params.put(name, value);
}
return new MDispatcher(filterConfig.getServletContext(), params);
}
继承Dispatcher实现自己的MDispatcher重写createContextMap方法
@Override
public Map
ActionMapping mapping) {
// request map wrapping the http request objects
Map requestMap = new RequestMap(request);
//对参数进行xss过滤
Map
// parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately
Map params = new HashMap(paramMap);
// session map wrapping the http session
Map session = new SessionMap(request);
// application map wrapping the ServletContext
Map application = new ApplicationMap(servletContext);
Map
if (mapping != null) {
extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
}
return extraContext;
}
xssFilterParam
private Map
Map
Map
for (Map.Entry
String key = entry.getKey();
LOG.debug("Key = " + entry.getKey() + ", Value = " + entry.getValue());
if(!key.endsWith("$HTML")){
//过滤
String[] values = getParameterValues(request,key);
paramMap.put(key, values);
}else{
//如果以 “$HTML” 结尾的参数,不进行过滤,并且构造新参数值
String keyNew = key.substring(0,key.length()-5);
paramMap.put(keyNew, entry.getValue());
paramMap.put(key, entry.getValue());
}
}
return paramMap;
}
getParameterValues
private String[] getParameterValues(HttpServletRequest request,String name) {
name = XssShieldUtil.stripXss(name);
// 返回值之前 先进行过滤
String[] values = request.getParameterValues(name);
if(values != null){
for (int i = 0; i < values.length; i++) {
values[i] = XssShieldUtil.stripXss(values[i]);
}
}
return values;
}
我的xss字符处理工具类
public class XssShieldUtil {
private static List
private static List
List
ret.add(new Object[]{"<(no)?script[^>]*>.*?(no)?script>", Pattern.CASE_INSENSITIVE});
ret.add(new Object[]{"eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL});
ret.add(new Object[]{"expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL});
ret.add(new Object[]{"(javascript:|vbscript:|view-source:)*", Pattern.CASE_INSENSITIVE});
ret.add(new Object[]{"<(\"[^\"]*\"|\'[^\']*\'|[^\'\">])*>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL});
ret.add(new Object[]{"(window\\.location|window\\.|\\.location|document\\.cookie|document\\.|alert\\(.*?\\)|window\\.open\\()*", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL});
ret.add(new Object[]{"<+\\s*\\w*\\s*(oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|onerror=|onerroupdate|onfilterchange|onfinish|onfocus|onfocusin|onfocusout|onhelp|onkeydown|onkeypress|onkeyup|onlayoutcomplete|onload|onlosecapture|onmousedown|onmouseenter|onmouseleave|onmousemove|onmousout|onmouseover|onmouseup|onmousewheel|onmove|onmoveend|onmovestart|onabort|onactivate|onafterprint|onafterupdate|onbefore|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditocus|onbeforepaste|onbeforeprint|onbeforeunload|onbeforeupdate|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|onpaste|onpropertychange|onreadystatechange|onreset|onresize|onresizend|onresizestart|onrowenter|onrowexit|onrowsdelete|onrowsinserted|onscroll|onselect|onselectionchange|onselectstart|onstart|onstop|onsubmit|onunload)+\\s*=+", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL});
return ret;
}
private static List
if (patterns == null) {
List
String regex = null;
Integer flag = null;
int arrLength = 0;
for(Object[] arr : getXssPatternList()) {
arrLength = arr.length;
for(int i = 0; i < arrLength; i++) {
regex = (String)arr[0];
flag = (Integer)arr[1];
list.add(Pattern.compile(regex, flag));
}
}
patterns = list;
}
return patterns;
}
public static String stripXss(String value) {
if(StringUtils.isNotBlank(value)) {
Matcher matcher = null;
for(Pattern pattern : getPatterns()) {
matcher = pattern.matcher(value);
// 匹配
if(matcher.find()) {
// 删除相关字符串
value = matcher.replaceAll("");
}
}
value = value.replaceAll("<", "<").replaceAll(">", ">");
}
return value;
}
}
最后在web.xml引用这个StrutsPrepareAndExecuteFilter