无框架

首先贴一下检测SQL注入的一个简单的规则来看。

  1. import java.util.regex.*;
  2. rule sql_injection {
  3. meta {
  4. description = "检测 SQL 注入攻击"
  5. severity = "high"
  6. }
  7. // 检查所有的 SQL 查询语句
  8. select t from SqlStatement t where exists(
  9. // 检查输入中是否包含 SQL 关键字
  10. select 1 from DataFlow::PathNode input where input.asExpr().toString().matches("(?i).*\\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|GROUP BY|ORDER BY|HAVING|LIMIT)\\b.*") and
  11. // 检查查询中是否包含未经过滤或转义的输入
  12. exists(DataFlow::PathNode query where query.asExpr().toString().matches("(?i).*\\b" + input.asExpr().toString() + "\\b.*"))
  13. ) and
  14. // 检查错误消息是否包含数据库结构信息或敏感数据
  15. exists(DataFlow::PathNode error where error.asExpr().toString().matches("(?i).*\\b(database|user|password|table|column|index|constraint)\\b.*"))
  16. }

import 导入的是正则表达式的包,因为后面会用到正则去匹配关键字。

rule是关键字,这里是一个规则,而在这里规则里面的关键字meta则是表明是CodeQL的一个元数据块,通过定义规则的元数据信息,可以帮助用户更好地理解该规则的用途和严重程度,同时也可以帮助工具自动化地处理规则。自定义的元数据块规则一般还会在定义作者的名字或者其他标志等。descriptionseverity 属性是元数据信息,可以根据实际需要自定义,比如修改规则描述或更改漏洞的严重程度评级。

查询语句是规则的核心部分,用于检测 SQL 注入漏洞。通常情况下,查询语句具有一定的固定格式,可以在不同的代码库中使用,只需要根据实际情况进行一些适当的修改。例如,对于 SQL 注入漏洞的检测,查询语句可能需要对 SQL 语句中的参数进行分析,检查是否存在潜在的注入漏洞。这些查询语句通常是固定的,需要根据实际情况进行调整和优化,以达到更好的检测效果。

然后SqlStatement是CodeQL中的一个类,这个类代表代码中所有的SQL查询语句,也就是可以直接将携带有SQL语句的代码罗列出来,然后加上控制条件,也就是where后面的内容,这里利用了正则表达式,筛选过程过于繁琐,因为它需要去匹配关键字,而且很多情况可能没有漏洞,误报率会很高。当然使用规则时需要针对代码的情况进行更改规则。这里只是一个例子,需要有针对性的去找危险方法,比如这里是在所有的sql语句中查找包含sql关键词的语句。

在这个例子中,select t from SqlStatement t where exists 表示检索 SqlStatement表中的所有数据,并使用 exists 子查询来检查是否存在满足条件的数据行。t 是一个变量名,用于代表查询结果中的每一行数据。这个查询语句的意思是,如果存在一个 DataFlow::PathNode 节点,代表一个代码中的输入,且输入中包含 SQL 关键字,同时在查询语句中存在未经过滤或转义的输入,则认为存在 SQL 注入漏洞。

MyBatis框架

规则

  1. import java
  2. class StatementMethod extends Method{
  3. StatementMethod(){
  4. this.getDeclaringType().getQualifiedName()="java.sql.Statement" and
  5. this.getName()="executeQuery"
  6. }
  7. }
  8. class PrepareStatementMethod extends Method{
  9. PrepareStatementMethod(){
  10. this.getDeclaringType().getQualifiedName()="java.sql.PreparedStatement" and
  11. this.getName()="executeQuery"
  12. }
  13. }
  14. class MapperAnnotation extends Annotation{
  15. MapperAnnotation(){
  16. this.getType().hasQualifiedName("org.apache.ibatis.annotations", "Mapper")
  17. }
  18. }
  19. from MethodAccess methodaccess
  20. where
  21. methodaccess.getMethod() instanceof StatementMethod or
  22. methodaccess.getMethod() instanceof PrepareStatementMethod or
  23. methodaccess.getMethod().getDeclaringType().getAnAnnotation() instanceof MapperAnnotation
  24. //漏洞名称,漏洞类名,漏洞方法
  25. select "SQLI_INJECTION",methodaccess,methodaccess.getCaller(),methodaccess.getFile().getAbsolutePath()

<font style="color:#DF2A3F;">getDeclaringType() </font>方法返回方法的声明类型。

<font style="color:#DF2A3F;">getQualifiedName() </font>方法返回类型的全限定名,即包含包名和类名的完整名称。

首先,定义了一个名为 StatementMethod 的类,它继承了 Method类。StatementMethod 类的构造函数中使用了两个过滤条件:getDeclaringType().getQualifiedName()="java.sql.Statement"getName()="executeQuery"。这两个条件分别表示该方法的声明类型为 java.sql.Statement,方法名为 executeQuery。同样地,还定义了一个名为 PrepareStatementMethod 的类,该类继承了 Method类,并且在构造函数中也使用了上述两个过滤条件,但声明类型为 java.sql.PreparedStatement

接着,定义了一个名为 MapperAnnotation 的类,它继承了 Annotation 类。MapperAnnotation 类的构造函数中使用了一个过滤条件:getType().hasQualifiedName("org.apache.ibatis.annotations", "Mapper"),表示该注解的类型为 org.apache.ibatis.annotations.Mapper

this.getName()="executeQuery" 检查了当前方法的名称是否为 executeQuery

hasQualifiedName("org.apache.ibatis.annotations", "Mapper")方法用于检查注解类型是否为指定的全限定名,其中 "org.apache.ibatis.annotations" 是包名,"Mapper"是类名。

利用webgoat生成一个ql数据库,来运行这个规则

下面是结果,可以得出的几个结果,这就已经是符合条件的SQL注入

🥝SQL in Java - 图1

然后细看一个,非常精准的定位到executeQuery方法。而且对应的路径也是SQL注入关卡的位置。

🥝SQL in Java - 图2