-
SQL注入和XSS攻击实际上的原理都是通过拼接从而完成攻击,通过简单的黑名单或编码在有些时候并不能完全防御以上两种问题。本文中使用OWASP的owasp-java-html-sanitizer组件,通过Sanitizer(消毒)从而达到避免SQL注入和XSS攻击的效果。
注意事项
使用该组件进行消毒后,如"><alert(1)>“此字符串会被直接干掉,结果为”&#gt;"
还有在涉及到邮箱@的时候也会被转义入库,但是实际上数据库中存HTML实体是问题不大的,只需要需要使用的时候注意一下即可。
如果你的业务涉及到此类似情况,请特殊处理,酌情使用。
且policy支持自定义,可扩展性非常强,可以根据你的实际需求完成自定义规则。
Q&A:
- 为什么要在工具类中做两次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("%","%").replaceAll("=","=");
}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>