codeql在mybatis某漏报场景下的研究学习

admin 2025年2月11日14:25:18评论16 views字数 6223阅读20分44秒阅读模式

“A9 Team 甲方攻防团队,成员来自某证券、微步、青藤、长亭、安全狗等公司。成员能力涉及安全运营、威胁情报、攻防对抗、渗透测试、数据安全、安全产品开发等领域,持续分享安全运营和攻防的思考和实践。”

01

前言
在对https://github.com/chillzhuang/SpringBlade 项目中发现其导出用户处,存在sql注入。数据流如下,但是利用codeql在对其代码进行分析过程中,未发现该漏洞。
codeql在mybatis某漏报场景下的研究学习
02
背景现状

使用开源工具下载地址如下:https://github.com/Chanzi-keji/chanzi/releases 它也是没有发现sql注入。

codeql在mybatis某漏报场景下的研究学习

用codeql生成原始的数据库(这里的原始指的是没有对其自己编写的jar包之类的进行反编译,可能会存在污点数据流跟进失败) 

codeql在mybatis某漏报场景下的研究学习

其实codeql也是无法识别出来的,这就很好奇对于代码审计这个赛道普遍无法处理的sql注入到底是怎样的 

codeql在mybatis某漏报场景下的研究学习

为什么都不能处理这个sql注入呢?所以想分析一下codeql对于mybatis场景下sql 的规则是怎么样的

codeql-mybatis-xml

codeql-mybatis-xml-sink

规则里对于sql注入的sink类型是 MyBatisMapperMethodCallAnArgument,其实看名字就可以知道他是用来查找 Mybatis 对应传入的参数的,他的实现如下图,下面的流程可以大致的理解为从使用 MyBatisMapperXmlFile 将 SQL 的 XML 文件找出来,利用 MyBatisMapperSqlOperation 将 MyBatisMapperXmlFile XML中的SQL操作与Java代码中的方法函数关联起来,获取到了方法函数后与 MethodCall 进行判断取出所有方法里的参数

codeql在mybatis某漏报场景下的研究学习

所以简而言之,这个 SQL 注入的 Sink 非常泛指,获取到所有 Mybatis 的 XML 方法的参数,那这个参数就是 Sink。但是这就奇怪了,明明是 ${} 这样没有预编译的写法的才会有问题,但是为什么把所有的参数都认为是sink??这未免有看太广泛了?这样的话岂不是全是误报了?继续往下看

codeql-mybatis-xml-isAdditionalFlowStep

isAdditionalFlowStep 谓词用于识别那些可能被遗漏的数据流步骤,特别是在用户输入被转换成SQL查询的一部分时。例如,如果用户输入被存储在一个对象的属性中,然后这个属性被用来构建SQL查询,那么这个属性的访问就是一个额外的数据流步骤。

        1.识别 toString 方法调用: 规则查找所有的 MethodCall其中方法名为 "toString",并且这个方法是对象 java.lang.Object 的方法

2.连接数据流节点:规则确保如果一个表达式 node1 通过 toString 方法调用转换成了另一个表达式 node2 ,那么这两个节点之间存在一个数据流步骤。这意味着,即使用户输入被转换成了字符串,CodeQL也能够继续追踪这个数据流 

codeql在mybatis某漏报场景下的研究学习

这个谓词在这里的作用是,当CodeQL追踪从用户输入到SQL查询的数据流时,在实际应用中,可能有一些自定义对象,它们内部保存着一些与数据库操作相关的数据(比如用户输入的数据被封装在对象属性里),当把这些对象转换为字符串用于日志记录、传递给其他组件或者参与到后续的数据库查询构建等操作时,就需要关注 toString 方法调用前后的数据变化以及其流向是否安全,这个谓词确保了即使在用户输入被转换成字符串之后,CodeQL也能够继续追踪这个数据流,尤其是当最终流向了可能引发 SQL 注入等安全隐患的位置时,这个谓词就能帮助 CodeQL 识别出这一完整的数据流转链路。

codeql-mybaits-xml-query

codeql在mybatis某漏报场景下的研究学习

其实这里的where查询条件非常好理解。条件很好解释: 1. 首先用 flowPath 确保找到的sink和source在数据流上是可以连通的(当然这个数据流是codeql来说) 2. ma.getAnArgument() = sink.getNode().asExpr() :定义了 3. 使用 myBatisMapperXmlElementFromMethod 来确保获取到的方法为 Mybatis 里的sql执行方法 4. 从 Mybatis 中取出所有的参数并且是存在 ${}这样sql注入的参数来,从而认定这个 Mybatis里的执行方法是sink点# codeql

/** * @name SQL injection in MyBatis Mapper XML * @description Constructing a dynamic SQL statement with input that comes from an *              untrusted source could allow an attacker to modify the statement's *              meaning or to execute arbitrary SQL commands. * @kindpath-problem * @problem.severity error * @precision high * @id java/mybatis-xml-sql-injection * @tags security *       experimental *       external/cwe/cwe-089 */import javaimportMyBatisCommonLibimportMyBatisMapperXmlSqlInjectionLibimport semmle.code.xml.MyBatisMapperXMLimport semmle.code.java.dataflow.FlowSourcesprivateimport semmle.code.java.security.SanitizersimportMyBatisMapperXmlSqlInjectionFlow::PathGraphprivatemoduleMyBatisMapperXmlSqlInjectionConfigimplementsDataFlow::ConfigSig {  predicate isSource(DataFlow::Node source) { source instanceofThreatModelFlowSource }  predicate isSink(DataFlow::Node sink) { sink instanceofMyBatisMapperMethodCallAnArgument }  predicate isBarrier(DataFlow::Node node) { node instanceofSimpleTypeSanitizer }  predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {exists(MethodCall ma |      ma.getMethod().getDeclaringType() instanceofTypeObject and      ma.getMethod().getName() = "toString" and      ma.getQualifier() = node1.asExpr() and      ma = node2.asExpr()    )  }}privatemoduleMyBatisMapperXmlSqlInjectionFlow =TaintTracking::Global<MyBatisMapperXmlSqlInjectionConfig>;fromMyBatisMapperXmlSqlInjectionFlow::PathNode source,MyBatisMapperXmlSqlInjectionFlow::PathNode sink, MyBatisMapperXmlElement mmxe, MethodCall ma,string unsafeExpressionwhereMyBatisMapperXmlSqlInjectionFlow::flowPath(source, sink) and  ma.getAnArgument() = sink.getNode().asExpr() andmyBatisMapperXmlElementFromMethod(ma.getMethod(), mmxe) and  unsafeExpression = getAMybatisXmlSetValue(mmxe) and  (isMybatisXmlOrAnnotationSqlInjection(sink.getNode(), ma, unsafeExpression)    or    mmxe instanceofMyBatisMapperForeach andisMybatisCollectionTypeSqlInjection(sink.getNode(), ma, unsafeExpression)  )select sink.getNode(), source, sink,"MyBatis Mapper XML SQL injection might include code from $@ to $@.", source.getNode(),"this user input", mmxe, "this SQL operation"
03
分析

走了一遍 codeql 对于的 sink,发现其实它是把user作为了传入的 source 的,所以在获取到用户输入参数上面是没有问题的。

codeql在mybatis某漏报场景下的研究学习

试试了sink能否获取到

codeql在mybatis某漏报场景下的研究学习

那问题出在哪呢?我尝试着去调用它的 select 查询发现 MyBatisMapperXmlSqlInjectionFlow::PathNode source 这一段并没有找到对应的source

codeql在mybatis某漏报场景下的研究学习

CodeQL 的数据流分析依赖于 DataFlow::Configuration 类来定义整个数据流规则,包括 Source(起点)、Sink(终点)、Barrier(阻断点)等。通过这些定义,CodeQL 能够查找潜在的从 Source 到 Sink 的数据流路径。如果路径没有被 Barrier 阻断,那么它将被标记为潜在的安全风险。

但是为什么 source instanceof ThreatModelFlowSource 可以匹配到 Source 点,MyBatisMapperXmlSqlInjectionFlow::PathNode source 却无法匹配到 Source 点呢?

source instanceof ThreatModelFlowSource

只是检查某个节点是否是 ThreatModelFlowSource 的实例。这种查询是 独立的判断,不依赖于任何数据流配置。换句话说,这个判断仅仅是静态地识别某种类型的节点,而不是在数据流上下文中查找 Source。

MyBatisMapperXmlSqlInjectionFlow::PathNode source

MyBatisMapperXmlSqlInjectionFlow::PathNode 是基于数据流配置的分析,它要求在定义的 DataFlow::Configuration 范围内查找 Source。这意味着要找到一个 PathNode source,它必须满足 MyBatisMapperXmlSqlInjectionConfig 中的所有配置(包括 isSource、isSink、isBarrier、isAdditionalFlowStep)仅仅匹配 isSource 是不够的,还需要在 整体数据流 中有路径能够从这个 Source 连接到一个 Sink,而路径中不能被 Barrier 阻断。

04
总结

特性

source instanceof ThreatModelFlowSource

MyBatisMapperXmlSqlInjectionFlow::PathNode source

独立性

独立查询,忽略数据流配置

依赖于DataFlow::Configuration的整体配置

匹配的条件

只需满足ThreatModelFlowSource条件即可

必须符合isSource,并能到达isSink且不被isBarrier阻断

路径分析

不进行路径分析

需要有从 Source 到 Sink 的有效路径

用途

简单的节点类型匹配

复杂的数据流分析,用于查找潜在的漏洞

所以也就说问题出在了 MyBatisMapperXmlSqlInjectionConfig 的位置上,找到的 Source 点数据流是无法判断到 Sink 上的,其实现在也大概可以知道这是数据流的问题。

而在这里的代码中user 是 Source,而 exportUser 是 Sink。从 User 类型变为 QueryWrapper<User> 类型 CodeQL 默认情况下会认为这是一个新的数据节点,而不是数据流的延续。在 Condition.getQueryWrapper() 方法中,user 对象被封装成 QueryWrapper,这实际上是对数据的一种 重新包装。在这种情况下,CodeQL 认为封装后的数据与原始数据不再相关,因此数据流在这里被截断。所以我们需要拼接user到queryWrapper数据流。

如下就需要进行改进

  predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {exists(MethodCall ma |      ma.getMethod().getDeclaringType() instanceof TypeObject and      ma.getMethod().getName() = "toString"and      ma.getQualifier() = node1.asExpr() andma = node2.asExpr()    ) or    // 新增 Condition.getQueryWrapper() 的追踪逻辑exists(MethodCall mc |      mc.getMethod().getDeclaringType().hasQualifiedName("org.springblade.core.mp.support""Condition"and      mc.getMethod().getName() = "getQueryWrapper"and      mc.getArgument(0) = node1.asExpr() and      mc = node2.asExpr()    )  }

•新增了对 Condition.getQueryWrapper() 方法的处理。通过 exists 语句,配置了对 Condition.getQueryWrapper(user, User.class) 方法的识别。

•通过匹配 getArgument(0)(即传入的 user 参数)和 mc = node2.asExpr() 来建立 Map 类型的 user 与返回的 QueryWrapper 对象之间的数据流关系。

这样就走找到了我们的。

codeql在mybatis某漏报场景下的研究学习

这样就可以找到对应的sql注入的问题,且解决了这个场景下sql注入的漏报 

codeql在mybatis某漏报场景下的研究学习

原文始发于微信公众号(A9 Team):codeql在mybatis某漏报场景下的研究学习

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月11日14:25:18
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   codeql在mybatis某漏报场景下的研究学习http://cn-sec.com/archives/3661025.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息