Springboot详解如何实现SQL注入过滤器过程

网友投稿 444 2022-07-22


目录1.过滤器SqlInjectFilter2.请求装饰类CustomRequestWrapper3.过滤器注册4.测试辅助类4.1 结果对象ResultObj4.2 Restful的Controller类5.测试5.1 POST请求测试5.2 GET请求测试15.3 GET请求测试2

场景:以过滤器(Filter)的方式,对所有http请求的入参拦截,使用正则表达式匹配入参中的字符串。存在SQL注入风险的参数,中断请求,并立即返回提示信息。不存在SQL注入风险的参数,校验通过后,放入过滤器链,继续后续业务。

环境:本例是基于springboot的web工程,版本:springboot 2.6.3

1.过滤器SqlInjectFilter

SqlInjectFilter,实现javax.servlet.Filter接口。即在doFilter方法中实现具体逻辑。

@Slf4j

public class SqlInjectFilter implements Filter {

private static final String SQL_REG_EXP = ".*(\\b(select|insert|into|update|delete|from|where|and|or|trancate" +

"|drop|execute|like|grant|use|union|order|by)\\b).*";

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) servletRequest;

CustomRequestWrapper requestWrapper = new CustomRequestWrapper(request);

Map parameterMap = new HashMap<>();

parameterMap =getParameterMap(parameterMap, request, requestWrapper);

// 正则校验是否有SQL关键字

for (Object obj : parameterMap.entrySet()) {

Map.Entry entry = (Map.Entry) obj;

Object value = entry.getValue();

if (value != null) {

boolean isValid = isSqlInject(value.toString(), servletResponse);

if (!isValid) {

return;

}

}

}

filterChain.doFilter(requestWrapper, servletResponse);

}

private Map getParameterMap(Map paramMap, HttpServletRequest request, CustomRequestWrapper requestWrapper) {

// 1.POST请求获取参数

if ("POST".equals(request.getMethod().toUpperCase())) {

String body = requestWrapper.getBody();

paramMap = jsONObject.parseObject(body, HashMap.class);

} else {

Map parameterMap = requestWrapper.getParameterMap();

//普通的GET请求

if (parameterMap != null && parameterMap.size() > 0) {

Set> entries = parameterMap.entrySet();

for (Map.Entry next : entries) {

paramMap.put(next.getKey(), next.getValue()[0]);

}

} else {

//GET请求,参数在URL路径型式,比如server/{var1}/{var2}

String afterDecodeUrl = null;

try {

//编码过URL需解码解码还原字符

afterDecodeUrl = URLDecoder.decode(request.getRequestURI(), "UTF-8");

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

}

paramMap.put("pathVar", afterDecodeUrl);

}

}

return paramMap;

}

private boolean isSqlInject(String value, ServletResponse servletResponse) throws IOException {

if (null != value && value.toLowerCase().matches(SQL_REG_EXP)) {

log.info("入参中有非法字符: " + value);

HttpServletResponse response = (HttpServletResponse) servletResponse;

Map responseMap = new HashMap<>();

// 匹配到非法字符,立即返回

responseMap.put("code", "999");

responseMap.put("message","入参中有非法字符");

response.setContentType("application/json;charset=UTF-8");

response.setStatus(HttpStatus.OK.value());

response.getWriter().write(JSON.toJSONString(responseMap));

response.getWriter().flush();

response.getWriter().close();

return false;

}

retuhttp://rn true;

}

}

2.请求装饰类CustomRequestWrapper

在拦截请求时,会读取HttpServletRequest的InputStream,而这种数据流一旦读取后,就没了。那么直接把请求放入过滤器链,后续的环节就读取不到数据了。因此,需要一个装饰类,读取了InputStream数据后,还得回写到请求中。然后把数据完整的装饰类放入过滤器链。这样拦截了请求,读取了数据,并回写了数据,数据完整性得到保证。

public class CustomRequestWrapper extends HttpServletRequestWrapper {

private final String body;

public CustomRequestWrapper(HttpServletRequest request) throws IOException {

super(request);

StringBuilder sb = new StringBuilder();

BufferedReader bufferedReader = null;

try {

InputStream inputStream = request.getInputStream();

if (inputStream != null) {

bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));

char[] charBuffer = new char[512];

int bytesRead = -1;

while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {

sb.append(charBuffer, 0, bytesRead);

}

} else {

sb.append("");

}

} catch (IOException e) {

e.printStackTrace();

throw e;

} finally {

if (bufferedReader != null) {

try {

bufferedReader.close();

} catch (IOException e) {

e.printStackTrace();

throw e;

}

}

}

body = sb.toString();

}

@Override

public ServletInputStream getInputStream() throws IOException {

final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes("UTF-8"));

return new ServletInputStream() {

@Override

public boolean isFinished() {

return false;

}

@Override

public boolean isReady() {

return false;

}

@Override

public void setReadListener(ReadListener readListener) {

}

@Override

public int read() {

return bais.read();

}

};

}

@Override

public BufferedReader getReader() throws IOException {

return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));

}

public String getBody() {

return this.body;

}

@Override

public String getParameter(String name) {

return super.getParameter(name);

}

@Override

public Map getParameterMap() {

return super.getParameterMap();

}

@Override

public Enumeration getParameterNames() {

return super.getParameterNames();

}

@Override

public String[] getParameterValues(String name) {

return super.getParameterValues(name);

}

}

3.过滤器注册

过滤器生效,需注册。

@Configuration

public class FilterConfiguration {

@Bean("sqlFilter")

public SqlInjectFilter sqlInjectFilter() {

return new SqlInjectFilter();

}

@Bean

public FilterRegistrationBean sqlFilterRegistrationBean() {

FilterRegistrationBean filterReg = new FilterRegistrationBean<>();

filterReg.setFilter(sqlInjectFilter());

filterReg.addUrlPatterns("/*");

filterReg.setOrder(1);

return filterReg;

}

}

4.测试辅助类

4.1 结果对象ResultObj

Restful请求返回格式统一。

@Data

@NoArgsConstructor

@AllArgsConstructor

@Builder

public class ResultObj {

private String code;

private String message;

}

4.2 Restful的Controller类

SqlInjectionController,包括POST请求和GET请求测试。

@RestController

@Slf4j

@RequestMapping("/inject")

public class SqlInjectionController {

@PostMapping("/f1")

public Object f1(@RequestBody Object obj) {

log.info("SqlInjectionController->f1,接收参数,obj = " + obj.toString());

log.info("SqlInjectionController->f1,返回.");

return ResultObj.builder().code("200").message("成功").build();

}

@GetMapping("/f2")

public Object f2(@RequestParam(name = "var") String var) {

log.info("SqlInjectionController->f2,接收参数,var = " + var);

log.info("SqlInjectionController->f2,返回.");

return ResultObj.builder().code("200").message("成功").build();

}

@GetMapping("/f3/{var}")

public Object f3(@PathVariable("var") String var) {

log.info("SqlInjectionController->f3,接收参数,var = " + var);

log.info("SqlInjectionController->f3,返回.");

return ResultObj.builder().code("200").message("成功").build();

}

}

5.测试

5.1 POST请求测试

URL: http://127.0.0.1:18081/server/inject/f1

入参:

{  "userName": "Hangzhou select",  "password": "202206112219"}

返回:

{  "code": "999",  "message": "入参中有非法字符"}

5.2 GET请求测试1

URL: http://127.0.0.1:18081/server/inject/f2?var=56622 INSert

返回:

{  "code": "999",  "message": "入参中有非法字符"}

5.3 GET请求测试2

URL: http://127.0.0.1:18081/server/inject/f3/123 delete

返回:

{  "code": "9http://99",  "message": "入参中有非法字符"}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java单例模式的五种实现方式
下一篇:Java中sort排序函数实例详解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~