工程地址:sakura-web
网上很多方案都只拦截了get请求,但是涉及到post请求的资料却很少。踩了一些坑,这里给大家分享一波,希望大家少踩坑吧,哪里有不妥的地方,欢迎各位斧正
SqlInjectFilter
-
由于实现
filter
只能处理一次post
请求流,所以用到了包装类sqlInjectHttpServletRequestWrapper
-
我这里添加了动态配置的开关,如果不需要动态配置的,可以注释掉,问题不大
import com.ly.mssp.wrapper.SQLInjectionHttpServletRequestWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* SQL注入过滤器
*
* @auther YangFan
* @Date 2020/11/20 9:37
*/
@ConfigurationProperties(prefix = "security.sql")
@WebFilter(filterName = "SqlInjectFilter", urlPatterns = "/*")
public class SqlInjectFilter implements Filter {
private final static Log logger = LogFactory.getLog(SqlInjectFilter.class);
/**
* 过滤器配置对象
*/
FilterConfig filterConfig = null;
/**
* 是否启用
*/
private boolean enable = true;
public void setEnable(boolean enable) {
this.enable = enable;
}
/**
* 忽略的URL
*/
private List<String> excludes;
public void setExcludes(List<String> excludes) {
this.excludes = excludes;
}
/**
* 初始化
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
/**
* 拦截
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
// 不启用或者已忽略的URL不拦截
if (!enable || isExcludeUrl(request.getServletPath())) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
String ContentType = request.getHeader("Content-Type");
if (request.getParameterMap().entrySet().size() > 0 || ContentType != null
&& ContentType.contains("application/json")) {
SQLInjectionHttpServletRequestWrapper sqlInjectHttpServletRequestWrapper = new SQLInjectionHttpServletRequestWrapper(
request);
filterChain.doFilter(sqlInjectHttpServletRequestWrapper, servletResponse);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
/**
* 销毁
*/
@Override
public void destroy() {
this.filterConfig = null;
}
/**
* 判断是否为忽略的URL
*
* @param url URL路径
* @return true-忽略,false-过滤
*/
private boolean isExcludeUrl(String url) {
if (excludes == null || excludes.isEmpty()) {
return false;
}
return excludes.stream().map(pattern -> Pattern.compile("^" + pattern)).map(p -> p.matcher(url))
.anyMatch(Matcher::find);
}
}
SQLInjectionHttpServletRequestWrapper
import org.apache.commons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
/**
* @auther YangFan
* @Date 2021/3/8 15:28
*/
public class SQLInjectionHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] bytes;
public SQLInjectionHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
// 读取输入流里的请求参数,并保存到bytes里
bytes = IOUtils.toByteArray(request.getInputStream());
}
public String getRequestBodyParame() {
return new String(bytes, Charset.forName("utf8"));
}
/**
*
* <p>
* Title: getInputStream
* </p>
* <p>
* Description:处理POST请求参数 RequestBody is missing 问题
* </p>
*
* @return
* @throws IOException
* @see javax.servlet.ServletRequestWrapper#getInputStream()
*/
@Override
public ServletInputStream getInputStream() {
String body = new String(this.bytes);
return new BufferedServletInputStream(body.getBytes());
}
class BufferedServletInputStream extends ServletInputStream {
private ByteArrayInputStream inputStream;
public BufferedServletInputStream(byte[] buffer) {
// 此处即赋能,可以详细查看ByteArrayInputStream的该构造函数;
this.inputStream = new ByteArrayInputStream(buffer);
}
@Override
public int available() throws IOException {
return inputStream.available();
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return inputStream.read(b, off, len);
}
@Override
public boolean isFinished() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setReadListener(ReadListener listener) {
// TODO Auto-generated method stub
}
}
@Override
public String[] getParameterValues(String parameter) {
// TODO Auto-generated method stub
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = values[i];
}
return encodedValues;
}
@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
return value;
}
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
return value;
}
}
SqlInterceptor
- 这里要是发现请求参数中存在自己设置的敏感字符,就会返回错误信息出去,告诉发请求的人,请求参数存在非法字符
- 根据实际情况配置需要拦截的字符
import com.ly.mssp.wrapper.SQLInjectionHttpServletRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @auther YangFan
* @Date 2021/3/8 16:08
*/
public class SqlInterceptor extends HandlerInterceptorAdapter {
private static final Logger log = LoggerFactory.getLogger(SqlInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("SqlInterceptor start pre ...");
String method = request.getMethod();
Enumeration<String> names = request.getParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
String[] values = request.getParameterValues(name);
for(String value: values){
//sql注入直接拦截
if (sqlValidate(response, value)) return false;
}
}
if("POST".equals(method)){
SQLInjectionHttpServletRequestWrapper wrapper = new SQLInjectionHttpServletRequestWrapper(request);
String requestBody = wrapper.getRequestBodyParame();
if(!StringUtils.isEmpty(requestBody)){
//sql注入直接拦截
if (sqlValidate(response, requestBody)) return false;
}
}
//TODO
return true;
}
private boolean sqlValidate(HttpServletResponse response, String requestBody) throws IOException {
if(sqlInject(requestBody)){
response.setContentType("text/html; charset=utf-8");
String jsonStr = "{\"code\":591,\"message\":\"请求参数含有非法字符!\",\"data\":\"null\"}";
response.getWriter().write(jsonStr);
response.setStatus(591);
return true;
}
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
super.afterConcurrentHandlingStarted(request, response, handler);
}
/**
*
* @Title: sqlInject
* @Description: TODO SQL 注入正在表达式(sql 函数关键字过滤)
* @param: @param value
* @param: @return
* @return: boolean
* @throws
*/
public boolean sqlInject(String value){
if(value == null || "".equals(value)){
return false;
}
/**
* 预编译SQL过滤正则表达式
*/
Pattern sqlPattern = Pattern.compile(
"and|exec|execute|insert|select|delete|update|count|drop|declare|sitename|net user|xp_cmdshell|like'|table|from|grant|group_concat|column_name|information_schema.columns|table_schema|union|where|order by|truncate|%|'|\\*|;|--|//|‘",
Pattern.CASE_INSENSITIVE);
Matcher matcher = sqlPattern.matcher(value);
return matcher.find();
}
}
添加拦截器配置类
import com.ly.mssp.auth.LocalUserInterceptor;
import com.ly.mssp.interceptor.SqlInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
//SQL注入拦截器
@Bean
public SqlInterceptor sqlInjectInterceptor () {
return new SqlInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(sqlInjectInterceptor());
}
}