特殊字符的过滤,防止xss攻击

概念

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 getInitParameterNames() {

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 instance = new ThreadLocal();

protected Map initParams;

创建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,根据此Map构造一个ActionContext

ctx = new ActionContext(stack.getContext());

}

request.setAttribute("__cleanup_recursion_counter", counter);

//将ActionContext存到ThreadLocal actionContext

ActionContext.setContext(ctx);

return ctx;

}

createContextMap方法

public Map createContextMap(HttpServletRequest request,

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 params = new HashMap();

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 createContextMap(HttpServletRequest request, HttpServletResponse response,

ActionMapping mapping) {

// request map wrapping the http request objects

Map requestMap = new RequestMap(request);

//对参数进行xss过滤

Map paramMap = xssFilterParam(request);

// 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 extraContext = createContextMap(requestMap, params, session, application, request, response);

if (mapping != null) {

extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);

}

return extraContext;

}

xssFilterParam

private Map xssFilterParam(HttpServletRequest request) {

Map paramMap = new HashMap();

Map reqParamMap = request.getParameterMap();

for (Map.Entry entry : reqParamMap.entrySet()) {

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 patterns = null;

private static List getXssPatternList() {

List ret = new ArrayList();

ret.add(new Object[]{"<(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 getPatterns() {

if (patterns == null) {

List list = new ArrayList();

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

struts2

MStrutsPrepareAndExecuteFilter

10款小爱音箱横评!小爱同学怎么选?智能音箱都有什么区别? #小爱同学 #智能音...
医保结算单在哪里打印