洞察探索国产操作系统如何助力企业在物联网领域实现高效管理与合规运营,提升数字化转型的能力。
1338
2022-09-27
Springboot详解如何实现SQL注入过滤器过程
目录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 =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
// 1.POST请求获取参数
if ("POST".equals(request.getMethod().toUpperCase())) {
String body = requestWrapper.getBody();
paramMap = jsONObject.parseObject(body, HashMap.class);
} else {
Map
//普通的GET请求
if (parameterMap != null && parameterMap.size() > 0) {
Set
for (Map.Entry
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.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
return super.getParameterMap();
}
@Override
public Enumeration
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
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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~