记一次执行顺序问题导致的SQL注入绕过
字数 1305 2025-08-19 12:41:44
SQL注入绕过:执行顺序问题分析与防御
0x00 背景与漏洞概述
本案例展示了一个由于执行顺序问题导致的SQL注入绕过场景。目标应用使用MyBatis进行SQL交互,部分业务接口通过order by ${_parameter} desc实现排序功能,这种动态SQL拼接方式存在SQL注入风险。
关键漏洞代码
order by ${_parameter} desc // 直接拼接用户输入,存在SQL注入风险
0x01 现有防御措施分析
应用通过过滤器(Filter)对用户参数进行SQL注入检查:
过滤器实现
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
// 获取请求参数
Map<String, Object> paramsMaps = new TreeMap<>();
if ("POST".equals(req.getMethod().toUpperCase())) {
String body = requestWrapper.getBody();
paramsMaps = JSONObject.parseObject(body, TreeMap.class);
} else {
// GET请求处理
}
// 校验SQL注入
for (Object o : paramsMaps.entrySet()) {
Map.Entry entry = (Map.Entry) o;
Object value = entry.getValue();
if (value != null) {
boolean isValid = checkSqlInject(value.toString(), servletResponse);
if (!isValid) return;
}
}
chain.doFilter(requestWrapper, servletResponse);
}
SQL注入检查方法
private static final String SQL_REGX = ".*(\\b(select|update|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|drop|execute)\\b).*";
private boolean checkSqlInject(String value, ServletResponse servletResponse) throws IOException {
if (null != value && value.matches(SQL_REGX)) {
// 拦截并返回错误
return false;
}
return true;
}
0x02 拦截器实现分析
应用还存在一个拦截器(Interceptor),在其preHandle方法中使用Jsoup对所有用户输入进行HTML净化:
拦截器实现
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 对请求参数进行HTML净化
for (String key : request.getParameterMap().keySet()) {
String value = request.getParameter(key);
value = sanitizeInput(value);
request.getParameterMap().replace(key, value);
}
return true;
}
public static String sanitizeInput(String strHtml) {
String cleaned = "";
if (StringUtil.isNotBlank(strHtml)){
cleaned = Jsoup.clean(strHtml, whitelist);
return cleaned;
}
return cleaned;
}
0x03 执行顺序分析
Java Web请求处理流程
- 过滤器(Filter):Servlet容器级别,最先执行
- 拦截器(Interceptor):在DispatcherServlet中处理,Controller之前执行
- Controller:业务逻辑处理
- Service:业务服务层
具体调用链
- Tomcat的
StandardWrapperValve#invoke()创建FilterChain ApplicationFilterFactory.createFilterChain创建并执行过滤器链- 所有过滤器执行完毕后,调用
servlet.service(request, response) - Spring MVC的
DispatcherServlet处理请求 - 在
getHandlerExecutionChain中添加并执行拦截器 - 执行Controller方法
0x04 绕过原理
由于过滤器先于拦截器执行,攻击者可构造特殊输入绕过SQL注入检查:
- 输入:
selec</script>t - 过滤器检查:不匹配
SQL_REGX正则(因为被</script>分隔) - 拦截器处理:Jsoup清理HTML标签后变为
select - 最终SQL执行:
order by select desc,成功注入
示例代码
public static void main(String[] args) throws Exception {
String value = "selec</script>t";
System.out.println("用户输入:"+value);
// 过滤器检查
if (null != value && value.matches(SQL_REGX)) {
throw new Exception("SQL注入拦截");
}
// 拦截器处理
String cleaned = Jsoup.clean(value, whitelist);
System.out.println("处理后:"+cleaned); // 输出: select
}
0x05 防御建议
-
参数化查询:避免直接拼接SQL,使用预编译语句
order by #{parameter} desc // 使用#{}而非${} -
防御措施执行顺序:确保净化操作在检查之前执行
-
完善正则检测:
- 添加对HTML标签的检测
- 使用更严格的关键字匹配模式
-
统一防御层:将安全措施集中到同一层(如全部放在过滤器或拦截器)
-
白名单校验:对于order by字段,只允许特定字段名
-
日志监控:记录所有拦截的请求,便于分析攻击尝试
0x06 其他潜在问题
- JSON请求处理:过滤器与Controller的JSON解析可能存在差异
- 请求方法转换:GET请求在特定注解下可转为POST请求
- 多阶段处理:参数在不同处理阶段可能被多次编码/解码
0x07 总结
本案例展示了由于安全措施执行顺序不当导致的SQL注入绕过。关键在于:
- 理解Filter和Interceptor的执行顺序差异
- 净化操作应在检查之前执行
- 防御措施需要全面考虑各种输入变形
- 优先使用参数化查询等根本解决方案
通过全面分析执行流程和安全措施的交互,可以有效预防此类安全问题。