企业级解决SQL注入、XSS攻击解决案例

  • SQL注入和XSS攻击实际上的原理都是通过拼接从而完成攻击,通过简单的黑名单或编码在有些时候并不能完全防御以上两种问题。本文中使用OWASP的owasp-java-html-sanitizer组件,通过Sanitizer(消毒)从而达到避免SQL注入和XSS攻击的效果。

    注意事项
    使用该组件进行消毒后,如"><alert(1)>“此字符串会被直接干掉,结果为”&#gt;"
    还有在涉及到邮箱@的时候也会被转义入库,但是实际上数据库中存HTML实体是问题不大的,只需要需要使用的时候注意一下即可。
    如果你的业务涉及到此类似情况,请特殊处理,酌情使用。
    且policy支持自定义,可扩展性非常强,可以根据你的实际需求完成自定义规则。

Q&A:

  1. 为什么要在工具类中做两次try…catch解码,在源码扫描中这么做的意义并不大,但在渗透测试中,面对一些二次编码的攻击手段,单一的对传入参数进行编码是毫无意义的,所以在其中做了两次解码。并且Sanitizer在调用之前,必须确保传入的参数必须是明文,是不能有任何编码格式的,否则消毒前后的结果也是没变化的。

更新:

2021-12-24 : 今天在使用AppScan扫描时发现仍然有一处XSS被扫出来了,仔细看了一下原来又是编码造成的,alert(1)为例,转义成"\x61\x6c\x65\x72\x74\x28\x31\x29",目前的应对方案是在SecurityUtils里二次解码的过程中
URLDecoder.decode(xStr.replaceAll("\\\\x", "%"), "utf-8")
针对不同编码的问题有一篇blog整理的比较详细,如下

https://blog.csdn.net/nigo134/article/details/118827542

正文:

  • 添加Filter处理Request中被污染的参数
  • SecurityFilter.java
public class SecurityFilter extends HttpServlet implements Filter{
	private Logger logger = Logger.getLogger(SecurityFilter.class);
	//默认不在Filter中处理post请求
	private boolean includePost = false;
	@Override
	public void init(FilterConfig filterConfig) throws ServletException{
		//读取web中设置的includePost参数
		String isIncludePost = filterConfig.getInitParameter("includePost");
		if(!StringUtils.isEmpty(isIncludePost)){
			if("true".equalsIgnoreCase(isIncludePost)){
				this.includePost = true;
			}else if("false".euqalsIgnoreCase("isIncludePost")){
				this.includePost = false;
			}else{
				throw new ServletException("Failed to convert property value of type 'java.lang.String' to required type 'boolean' for property 'isIncludePost'!");
			}
		}
	}
	
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException{
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
		//可扩展黑名单验证begin
		Enumeration<?> params = httpServletRequest.getParameterNames();
		String param = null;
		while(params.hasMoreElements()){
			param = (String)params.nextElement();
			String paramVal = httpServletRequest.getPrameter(param);
			//校验是否存在黑名单内的字段
			if(checkSecurity(paramVal)){
				logger.error("参数中存在黑名单内字段,已被拦截===" + paramVal);
				throw new ServletException("Illegal Request Parameters !");
			}
		}
		//POST请求默认在实体类绑定属性过程中处理,此处默认只处理get请求,如需处理post请求,在web.xml中想includePost参数设置为true
		if("GET".equals(httpServletRequest.getMethod())){
			chain.doFilter(new SecurityHttpServletRequestWrapper(httpServletRequest), response);
		}else if("POST".equals){
			if(includePost){
				chain.doFilter(new SercurityHttpServletRequestWrapper(httpServletRequest), response);
			}else{
				chain.doFilter(request, response)
			}
		}else{
			chain.doFilter(request, response)
		}
	}
	
	//此处提供一个通过黑名单拦截参数的方法
	public boolean checkSecurity(String input){
		if(StringUtils.isEmpty(input)){
			return false;
		}
		//黑名单列表(此处只给一些例子,有需要可以自己扩展)
		String[] blackList = {"script","alert","xss","onload","iframe"}
		//不区分大小写
		input = input.toLowerCase();
		for(int i = 0; i < input.length; i++){
			String reg = "([^a-zA-Z]+|^)" + blackList[i] + "($|[^a-zA-Z]+)";
			if(Pattern.compile(reg).matcher(input).find()){
				return true;
			}
		}
		return false;
	}
}
  • SecurityHttpServletRequestWrapper.java
public class SecurityHttpServletRequestWrapper extends HttpServletRequestWrapper{
	public SecurityHttpServletRequestWrapper(HttpServletRequest request){super(request);}

	@Override
	public String getHeader(String name){return SecurityUtils.sanitizeHTML(super.getHeader(name));}
	@Override
	public String getQueryString(){return SecurityUtils.sanitizeHTML(super.getQueryString());}
	@Override
	public String getParameter(String name){
		if(StringUtils.isNotBlank(super.getParameter(name))){
			return SecurityUtils.sanitize(super.getParameter(name));
		}
		return "";
	}
	@Override
	public String[] getParameterValues(String name){
		String[] values = super.getParameterValues(name);
		if(values != null){
			int length = values.length;
			for(int i = 0 ; i < length ; i++){
				values[i] = SecurityUtils.sanitizeHTML(values[i]);
			}
		}
	}
}
  • POST请求默认处理在InitBinder时
	@Override
	public void setAsText(String text){
		setValue(text == null ? null : SecurityUtils.sanitizeHTML(text));
	}
  • 下面是使用到的安全工具类
public class SecurityUtils{
	private static final Logger logger = Logger.getLogger(SecurityUtils.class);
	
	private static PolicyFactory policy = Sanitizer.FORMATTING.and(Sanitizers.LINKS);
	
	public static String SanitizeHTML(String value){
		String beforeSanitizeVal = value;
		String decoderAfter = null;
		String afterSanitizeVal = null;
		//一次解码
		try{
			decoderAfter = URLDecoder.decode(value, "UTF-8");
		}catch(Exception e){
			logger.debug(e.getMessage());
			decoderAfter = value;
			logger.debug("===decoderAfter:===" + deciderAfter + "===value:===" + value);
		}
		//二次解码
		try{
			beforeSanitizeVal = URLDecoder.decode(decoderAfter , "UTF-8");
		}catch(Exception e){
			logger.debug(e.getMessage());
			beforeSanitizeVal = decoderAfter ;
			logger.debug("===decoderAfter:===" + beforeSanitizeVal + "===value:===" + decoderAfter );
		}
		try{
			if(beforeSanitizeVal == null){
				return "";
			}
			afterSanitizeValue = policy.sanitize(beforeSanitizeVal);
			if(!beforeSanitizeVal.equals(afterSanitizeValue)){
				logger.debug("Value before Sanitizer : " + beforeSanitizeVal + ",After : " + afterSanitizeValue);
			}
			//对'%'进行编码,防止二次编码造成的攻击
			afterSanitizeValue = afterSanitizeValue.replaceAll("%","&#37;").replaceAll("=","&#61;");
		}catch(Exception e){
			logger.debug("An error occurred: " + e.getMessage() + ",so return the initVal", e);
			return beforeSanitizeVal;
		}
		return afterSanitizeValue;
	}
	
	//提供重载方法
	public static Object SanitizeHTML(Object object){
		String beforeSanitizeVal = "";
		String afterSanitizeVal = "";
		//获取对象所有属性,返回Field数组
		Field[] field = object.getClass().getDeclareFields();
		//遍历所有属性
		for(int j = 0; j < field.length ; j++){
			//获取属性名字
			String name = field[j].getName();
			//将属性首字符大写,方便构造get、set方法
			name = name.substring(0,1).toUpperCase() + name.substring(1);
			//获取属性类型
			String type = field[j].getGenericType().toString();
			if(type.equals("class java.lang.String")){
				try{
					Method getMethod = object.getClass().getMethod("get" + name);
					Method setMethod = object.getClass().getMethod("set" + name, String.class);
					//调用getter方法获取属性值
					beforeSanitizeVal = (String)getMethod.invoke(object);
					if(StringUtils.isNotBlack(beforeSanitizeVal)){
						afterSanitizeVal = SanitizeHTML(beforeSanitizeVal);
					}
					setMethod.invoke(object, afterSanitizeVal);
				}catch(Exception e){
					logger.debug("Could not find getter or setter : " + e.getMessage(), e);
				}
			}
		}
		return object;
	}
}

web.xml Filter配置

<filter>
	<filter-name>securityFilter</filter-name>
	<filter-class>com.xxx.xxx.filter.SecurityFilter</filter-class>
	<init-param>
		<param-name>includePost</param-name>
		<param-value>true</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>securityFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

pom.xml依赖

<!-- https://mvnrepository.com/artifact/com.googlecode.owasp-java-html-sanitizer/owasp-java-html-sanitizer -->
<dependency>
    <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
    <artifactId>owasp-java-html-sanitizer</artifactId>
    <version>20211018.2</version>
</dependency>

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值