专栏名称: 合天网安实验室
为广大信息安全爱好者提供有价值的文章推送服务!
目录
相关文章推荐
浦东知识产权  ·  2025年度浦东新区创业孵化基地认定工作正在 ... ·  昨天  
浦东知识产权  ·  2025年度浦东新区创业孵化基地认定工作正在 ... ·  昨天  
纳指弹幕组  ·  早晨英伟达4Q业绩出了, 情况怎样? ·  2 天前  
纳指弹幕组  ·  早晨英伟达4Q业绩出了, 情况怎样? ·  2 天前  
内江头条  ·  啊?中国男性平均寿命仅69.9岁? ·  2 天前  
内江头条  ·  啊?中国男性平均寿命仅69.9岁? ·  2 天前  
51好读  ›  专栏  ›  合天网安实验室

记一次执行顺序问题导致的SQL注入绕过

合天网安实验室  · 公众号  · 互联网安全 科技自媒体  · 2024-06-06 16:30

正文

拦截器(Interceptor)和过滤器(Filter)在Java Web应用程序中都是用于处理HTTP请求和响应的组件,但它们属于不同的层次,并且具有不同的执行顺序和作用域。正确理解它们之间的区别和执行顺序对于确保应用程序的安全性至关重要。

0x00 背景

在Java Web开发中,SQL注入是一种常见的安全漏洞,它允许攻击者通过构造恶意的SQL查询语句来操纵数据库。在实际业务中发现一处SQL注入的绕过case,当前 漏洞已经修复完毕 。提取关键的的漏洞代码做下复盘。

目标应用使用mybatis进行SQL交互,部分业务接口通过orderby实现了排序的功能。因为动态SQL没办法进行预编译处理,若缺少对应的安全措施,会因为存在SQL直接拼接而引入SQL注入风险的:

 order by ${_parameter} desc

应用是通过过滤器Filter的方式对用户传递的参数进行检查,来防御SQL注入风险的。关键代码如下,大致思路是首先获取当前请求的参数以及对应的值,然后调用checkSqlInject方法进行对应的安全检查:

    @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;

MyRequestWrapper requestWrapper = new MyRequestWrapper(req);

// 获取请求参数
Map paramsMaps = new TreeMap<>();
if ("POST".equals(req.getMethod().toUpperCase())) {
String body = requestWrapper.getBody();
paramsMaps = JSONObject.parseObject(body, TreeMap.class);
} else {
Map parameterMap = requestWrapper.getParameterMap();
Set> entries = parameterMap.entrySet();
for (Map.Entry next : entries) {
paramsMaps.put(next.getKey(), next.getValue()[0]);
}
}

// 校验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);
}

checkSqlInject方法具体实现如下,通过正则匹配的方式如果检查到当前参数内容存在非法字符,会进行拦截:

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).*";

/**
* 检查SQL注入
*
* @param value 参数值
* @param servletResponse 相应实例
* @throws IOException IO异常
*/

private boolean checkSqlInject(String value, ServletResponse servletResponse) throws IOException {
if (null != value && value.matches(SQL_REGX)) {
log.error("您输入的参数有非法字符,请输入正确的参数");
HttpServletResponse response = (HttpServletResponse) servletResponse;

Map rsp = new HashMap<>();
rsp.put("code", HttpStatus.BAD_REQUEST.value() + "");
rsp.put("message", "您输入的参数有非法字符,请输入正确的参数!");

response.setStatus(HttpStatus.OK.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(rsp));
response.getWriter().flush();
response.getWriter().close();
return false;
}
return true;
}

这里过滤的规则比较粗糙,倒是也限制了类似select等关键字,防止进一步的数据获取,从某种意义上也防止了SQL注入的进一步利用。那么有没有办法可以绕过当前的关键字检测呢?从代码上看,这里没有考虑当JSON请求时,过滤器跟Controller JSON请求方式不一致可能导致潜在的参数走私问题。也没有考虑GET请求在特定注解的情况下可以转换成POST进行请求的情况。

抛开前面提到的思路,还有没有更多的缺陷需要进一步修复呢?下面是具体的分析过程。

0x01 绕过分析

在代码审计时筛选和整理当前应用使用的安全措施是一个非常好的习惯。能更直观的感知整个参数的调用过程。除了SQL注入过滤器以外,应用还存在另外一个拦截器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;
}

在sanitizeInput中,主要是通过Jsoup的clean方法对用户输入进行处理, clean() 方法可以接收一个HTML字符串,并对其进行清理,移除任何潜在的恶意脚本,只保留安全的HTML标签和属性:

public static String sanitizeInput(String strHtml) {
String cleaned = "";
if (StringUtil.isNotBlank(strHtml)){
cleaned = Jsoup.clean(strHtml, whitelist);
return cleaned;
}
return cleaned;
}

这里针对SQL和XSS分别使用了Filter和interceptor进行处理。那么有没有可能因为两者的解析顺序不同,可能导致了潜在的绕过风险呢?下面对具体的执行顺序进行简单的分析:

  • 过滤器Filter

过滤器位于请求处理链的最外层,可以拦截请求并进行对应的处理。如果某资源已经配置对应filter进行处理的话,那么每次访问这个资源都会执行doFilter()方法,该方法也是过滤器的核心方法。例如上面SQL注入的风险识别就是基于该方法实现的。

Spring Boot默认内嵌Tomcat作为Web服务器。简单查看Filter的具体调用过程。

Filter调用时会在org.apache.catalina.cor.StandardWrapperValve#invoke()方法中被创建。会通过ApplicationFilterFactory.createFilterChain创建FilterChain:

查看createFilterChain方法的具体实现,首先检查 servlet 是否为 null ,若为 null ,表示没有指定Servlet,就没有需要创建的过滤器链。否则根据实际的情况创建一个 ApplicationFilterChain 对象,或者获取已存在的过滤器链对象。而过滤器链对象会负责对一系列的过滤器进行管理:

接着获取所有的filter的映射对象,在filterMaps中保存的是各个filter的元数据信息,若filterMaps不为null且length不为0,则对前面创建的filterChain进一步的封装,这里首先会获取与当前请求相关的标识信息,例如请求的调度类型(dispatcher)和请求的路径(requestPath):

然后遍历所有过滤器映射,根据一定的条件判断将匹配的过滤器添加到过滤器链中。条件包括与调度类型的匹配和与请求路径或Servlet名称的匹配:

最后,返回创建的过滤器链,该过滤器链包含了所有匹配的过滤器。如果没有找到匹配的过滤器,则返回一个空的过滤器链。创建了filterChain之后,就开始执行ApplicationFilterChain的doFilter进行请求的链式处理:

具体的逻辑在org.apache.catalina.core.ApplicationFilterChain#internalDoFilter方法,这里会通过pos索引判断是否执行完了所有的filter,如果没有,取出当前待执行的索引filter,调用其doFilter方法:

当所有的filter执行完后,会释放掉过滤器链及其相关资源。然后执行servlet具体的业务模块 servlet.service(request, response);

以上是tomcat中整个Filter的调用过程。

也就是说, 过滤器主要在Servlet容器级别处理请求的,会在Spring的其他组件之前执行 。在Spring中, DispatcherServlet 是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派。其也是在这个环节中进行解析处理的。业务场景中Controller 中收到的请求,都是经过 Tomcat 容器解析后交给 DispatcherServlet ,再由其转交给对应 Controller 的。

  • 拦截器Interceptor(preHandle)

拦截器(Interceptor)是一个设计用于在请求处理流程之前或之后执行的组件。它们可以用于多种目的,包括日志记录、安全控制、事务管理、错误处理等。其可以拦截进入Controller之前的请求,也可以拦截Controller处理完请求之后的响应。

这里只讨论 preHandle 方法,其在请求进入Controller之前执行,可以返回一个布尔值,决定是否继续执行后续的Interceptor或Controller。看看具体的调用过程。在 DispatcherServlet 的解析过程中,找到了拦截器的解析逻辑。

Spring MVC在接收到请求时,会调用DispatcherServlet的service方法进行处理。主要是调用doDispatch方法来获取对应的mappedHandler:

在getHandler方法中,顺序循环调用HandlerMapping的getHandler方法进行解析:

这里首先会通过RequestMappingHandlerMapping,在其getHandler方法中通过getHandlerInternal获取handler构建HandlerExecutionChain并返回,这里会添加当前请求相关的所有Interceptor:

在getHandlerExecutionChain方法中,一开始会创建一个 HandlerExecutionChain 对象,用于存储处理器和拦截器。然后遍历 adaptedInterceptors 的拦截器集合,如果拦截器是 MappedInterceptor 的实例,并且它的 matches(request) 方法返回 true (表示请求的URL路径匹配该拦截器),则将该拦截器中的实际拦截器添加到 chain 中。否则直接将它添加到 chain







请到「今天看啥」查看全文


推荐文章
纳指弹幕组  ·  早晨英伟达4Q业绩出了, 情况怎样?
2 天前
纳指弹幕组  ·  早晨英伟达4Q业绩出了, 情况怎样?
2 天前
内江头条  ·  啊?中国男性平均寿命仅69.9岁?
2 天前
内江头条  ·  啊?中国男性平均寿命仅69.9岁?
2 天前
第一财经YiMagazine  ·  自行车又回来了,它还在改变城市设计|CBNweekly
7 年前