什么是CodeQL
简单来说,CodeQL
就是一个静态分析(SAST
)工具,可以在白盒场景通过编写QL
制定的规则,自动化的扫描代码。
环境搭建
CodeQL
分引擎和SDK
两部分,引擎部分不开源,主要负责解析规则。SDK
是开源的,包含很多漏洞规则,也可以自己写漏洞规则进行使用。
引擎部分需要配置一下环境变量
SDK
部分直接拉源代码就可以了
接下来拉一个项目,尝试一下CodeQL
这里我拉了这个Java
靶场进行测试,拉下来后需要配一下数据库,确保项目可以正常运行。
接下来安装vscode
的codeql
插件,配置codeql
所在的目录
在java/ql/examples
目录下创建demo.ql
,内容为select "Hello World
,并且右键选择CodeQL: Run Query
到这里环境搭建的工作就完成了。
写起来比较简略,实际上还是踩了不少坑的,不过毕竟用Linux物理机,平常遇到奇奇怪怪的问题太多了,用搜索引擎结合一些文章就解决了
规则编写
ql的语法和sql的语法有一些相似的地方
由于CodeQL
实际的查询是对AST
的查询,因此QL
的类库是与AST
对应的。
可以看一下AST
的样子
QL语法
1 | from [datatype] var |
example:
1 | import java // 引入CodeQL类库 |
类库
Method
:方法类,Method method表示获取当前项目中所有的方法
MethodAccess
:方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用
Parameter
:参数类,Parameter表示获取当前项目当中所有的参数
example:
1 | import java |
谓词
当where
部分过长时,可以用谓词这个语法,把很长的查询语句封装成函数。
predicate
关键词用于声明谓词
exists
子查询,它根据内部的子查询返回true or false,来决定筛选出哪些数据。
利用上面这两个语法,我们可以把判断方法名称是否为getStudent
的where
部分,封装成函数。这个函数就被称为谓词。
1 | import java |
Source、Sink
SAST
的理念中通常会提到这个三元组(source,sink,sanitizer)
source
是指漏洞污染链条的输入点。比如获取http请求的参数部分,就是非常明显的Source。
sink
是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exeSql,或者其它)。
sanitizer
又叫净化函数,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer。
如何定义source
source
,在我们这个java靶场中,具体来看就是后端接口的参数
1 | @RequestMapping(value = "/one") |
例如这段代码,source
就是username
参数
对于source
的检测,我们可以用CodeQL
的SDK
提供的检测规则
1 | override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource } |
其中,DataFlow::Node
表示一个数据流节点,可以是数据流源、汇或传输节点。RemoteFlowSource
是一个表示远程数据流源的CodeQL类。
通俗的说,这里定义了一个叫isSource
的谓词,来判断传入的这个节点是不是远程数据流源(RemoteFlowSource
)。
如何定义sink
这里我们以找sql注入的漏洞为例,sink
就应该是qurey
方法
1 | override predicate isSink(DataFlow::Node sink) { |
这里我们声明谓词isSink
,通过exists
来判断名为query
的方法,并且设置第一个参数为sink
Data Flow
从Source
向Sink
的数据流是否能够走通决定了是否有可能存在漏洞,可以用CodeQL
的语法config.hasFlowPath(source, sink)
来判断。
尝试编写Demo检测SQL注入
前面分别讲了Source
、Sink
、Data Flow
的定义方法,还需要一些语法将他们串起来,才是一个完成的demo。
source
和sink
的定义使用到的方法,需要继承自TaintTracking::Configuration
类。
完整Demo:
1 | /** |
非常的有意思,不过这里仍然存在问题,就是误报问题。
解决误报问题
这里也就是前面提到的三元组中,sanitizer
的问题。
CodeQL
默认规则中,没有对List<Long>
这样的复合类型做判断,因此需要手动写一个isSantizer
的谓词做判断,来解决误报的问题。
这段的意思是,如果当前node
节点为基础类型、数字类型、泛型数字类型时,就切断数据流。
1 | override predicate isSanitizer(DataFlow::Node node) { |
解决漏报问题
对于靶场中这段代码,没有捕获到。
1 | public List<Student> getStudentWithOptional(Optional<String> username) { |
这里是因为CodeQL
的默认规则,没有对Optional
这种类型做判断,这里可以选择手动添加对于Optional
这种类型的检测。
1 | predicate isTaintedString(Expr expSrc, Expr expDest) { |
文章主要是本人的学习笔记,内容大多数都是对其他文章的参考,还是建议看一手文章进行学习
最后的ql文件 https://gist.github.com/ek1ng/f1c10a42a07b467a3989b422137b265a