微信网站在线登录网页版,免费制作图片加文字,何为响应式网站,网站里的内容都是什么作用最近扫出了一个SQL注入安全漏洞#xff0c;用户的非法输入可能导致数据泄露、数据篡改甚至系统崩溃#xff0c;为了有效防范 SQL 注入攻击#xff0c;除了在代码层面使用参数化查询和预编译语句外#xff0c;还可以通过实现一个Filter来过滤掉潜在的危险输入。本文将介绍如…
最近扫出了一个SQL注入安全漏洞用户的非法输入可能导致数据泄露、数据篡改甚至系统崩溃为了有效防范 SQL 注入攻击除了在代码层面使用参数化查询和预编译语句外还可以通过实现一个Filter来过滤掉潜在的危险输入。本文将介绍如何基于Filter接口实现SQL 注入过滤器 SQL注入拦截的原理很简单用请求参数匹配SQL关键字匹配上了就说明请求存在非法字符不予放行
SqlLnjectionFilter SQL注入过滤器
package com.largescreen.common.filter;import com.fasterxml.jackson.databind.ObjectMapper;
import com.largescreen.common.enums.HttpMethod;
import com.largescreen.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** sql注入过滤器*/
Slf4j
public class SqlLnjectionFilter implements Filter {/*** 白名单*/public ListString excludes new ArrayList();private static final AntPathMatcher matcher new AntPathMatcher();private static final String badStrReg \\b(and|or)\\b.{1,6}?(|||\\bin\\b|\\blike\\b)|\\/\\*.?\\*\\/|\\s*script\\b|\\bEXEC\\b|UNION.?SELECT|UPDATE.?SET|INSERT\\sINTO.?VALUES|(SELECT|DELETE).?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s(TABLE|DATABASE);/*** 整体都忽略大小写*/private static final Pattern sqlPattern Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);Overridepublic void init(FilterConfig filterConfig) throws ServletException {String tempExcludes filterConfig.getInitParameter(excludes);if (StringUtils.isNotEmpty(tempExcludes)){String[] urls tempExcludes.split(,);for (String url : urls){excludes.add(url);}}}Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request (HttpServletRequest) servletRequest;HttpServletResponse response (HttpServletResponse) servletResponse;String path request.getServletPath();if(matchAny(excludes,path)){filterChain.doFilter(request, response);return;}// 从request中获取当前请求中所有的参数名称String sql StringUtils.EMPTY;MapString, String[] parameterMap request.getParameterMap();for (Map.EntryString, String[] entry : parameterMap.entrySet()) {String[] values entry.getValue();for (int i 0; i values.length; i) {sql values[i];}}if (sqlValidate(sql)) {errorResp(response);} else {// 校验post请求String contentType request.getContentType();Boolean existSql false;if (HttpMethod.POST.matches(request.getMethod())) {BufferedReader reader new BufferedReader(new InputStreamReader(request.getInputStream()));String bodyString IOUtils.toString(reader);if(StringUtils.isNotBlank(bodyString)){if(StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE)){existSql sqlValidate(bodyString);}else if (StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {existSql sqlValidate(bodyString);} else if (StringUtils.startsWithIgnoreCase(contentType, MediaType.MULTIPART_FORM_DATA_VALUE)) {existSql sqlValidate(bodyString);}}// 如果存在sql注入,直接拦截请求if (existSql) {errorResp(response);return;};}}filterChain.doFilter(request, response);}Overridepublic void destroy() {}/*** 非法请求响应* param response* throws IOException*/private void errorResp(HttpServletResponse response) throws IOException {response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());response.setContentType(application/json; charsetutf-8);response.setCharacterEncoding(UTF-8);Map result new HashMap();result.put(code, HttpStatus.INTERNAL_SERVER_ERROR.value());result.put(msg,非法请求);ObjectMapper mapper new ObjectMapper();String str mapper.writeValueAsString(result);ServletOutputStream outputStream response.getOutputStream();outputStream.write(new String(str.getBytes(),utf-8).getBytes());outputStream.flush();}/*** 判断输入的字符串是否包含SQL注入** param str 输入的字符串* return 如果输入的字符串包含SQL注入返回 true否则返回 false。*/public static boolean sqlValidate(String str) {str str.toLowerCase();Matcher matcher sqlPattern.matcher(str);if (matcher.find()) {log.error(SqlInjectionFilter 参数[{}]中包含不允许sql的关键词, str);return true;}return false;}/*** 匹配多个路径** param patterns 路径模式列表* param path 需要匹配的实际路径* return 匹配的路径模式如果匹配成功否则返回 null*/public static boolean matchAny(ListString patterns, String path) {for (String pattern : patterns) {if (matcher.match(pattern, path)) {return true;}}return false;}}上面的过滤器除了会校验请求地址中拼接的参数还会校验post请求体中的参数但直接读请求体的数据是有问题的http请求的数据流只能读取一次在过滤器中读取请求体后serlvlet就会拿不到数据所以得增强request请求确保数据能重复读取
RepeatedlyRequestWrapper request可重复读实现类
package com.largescreen.common.filter;import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.largescreen.common.utils.http.HttpHelper;
import com.largescreen.common.constant.Constants;/*** 构建可重复读取inputStream的request* */
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
{private final byte[] body;public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException{super(request);request.setCharacterEncoding(Constants.UTF8);response.setCharacterEncoding(Constants.UTF8);body HttpHelper.getBodyString(request).getBytes(Constants.UTF8);}Overridepublic BufferedReader getReader() throws IOException{return new BufferedReader(new InputStreamReader(getInputStream()));}Overridepublic ServletInputStream getInputStream() throws IOException{final ByteArrayInputStream bais new ByteArrayInputStream(body);return new ServletInputStream(){Overridepublic int read() throws IOException{return bais.read();}Overridepublic int available() throws IOException{return body.length;}Overridepublic boolean isFinished(){return false;}Overridepublic boolean isReady(){return false;}Overridepublic void setReadListener(ReadListener readListener){}};}
}添加一个新的过滤器RepeatableFilter在doFilter方法中对request进行增强只要这个过滤器执行顺序足够靠前后续的过滤器就都能重复读参数了
RepeatableFilter 可重复读过滤器
package com.largescreen.common.filter;import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;import com.largescreen.common.enums.HttpMethod;
import com.largescreen.common.utils.StringUtils;
import org.springframework.http.MediaType;/*** Repeatable 过滤器* */
public class RepeatableFilter implements Filter
{Overridepublic void init(FilterConfig filterConfig) throws ServletException{}Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException{ServletRequest requestWrapper null;if (request instanceof HttpServletRequest HttpMethod.POST.matches(((HttpServletRequest) request).getMethod())){requestWrapper new RepeatedlyRequestWrapper((HttpServletRequest) request, response);}if (null requestWrapper){chain.doFilter(request, response);}else{chain.doFilter(requestWrapper, response);}}Overridepublic void destroy(){}
}将上面两个过滤器注册到过滤链中
FilterConfig 过滤器配置
package com.largescreen.framework.config;import java.util.HashMap;
import java.util.Map;
import javax.servlet.DispatcherType;import com.largescreen.common.filter.SqlLnjectionFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.largescreen.common.filter.RepeatableFilter;
import com.largescreen.common.filter.XssFilter;
import com.largescreen.common.utils.StringUtils;/*** Filter配置**/
Configuration
public class FilterConfig
{Value(${sql.excludes})private String sqlExcludes;Value(${sql.urlPatterns})private String sqlUrlPatterns;BeanConditionalOnProperty(value sql.enabled, havingValue true)public FilterRegistrationBean sqlFilterRegistration(){FilterRegistrationBean registration new FilterRegistrationBean();registration.setDispatcherTypes(DispatcherType.REQUEST);registration.setFilter(new SqlLnjectionFilter());registration.addUrlPatterns(StringUtils.split(sqlUrlPatterns, ,));registration.setName(sqlFilter);registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE 10);MapString, String initParameters new HashMap();initParameters.put(excludes, sqlExcludes);registration.setInitParameters(initParameters);return registration;}SuppressWarnings({ rawtypes, unchecked })Beanpublic FilterRegistrationBean someFilterRegistration(){FilterRegistrationBean registration new FilterRegistrationBean();registration.setFilter(new RepeatableFilter());registration.addUrlPatterns(/*);registration.setName(repeatableFilter);registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);return registration;}}在配置文件中加入SQL过滤的白名单等信息
application.yml
# 防止sql注入
sql:# 过滤开关enabled: true# 排除链接多个用逗号分隔excludes: /system/*# 匹配链接urlPatterns: /*