代码审计工具CodeQL从入门到放弃

CodeQL介绍

在介绍CodeQL前,我们先了解一下静态代码审计工具的发展,最早的工具是基于关键字匹配,通过正则表达式进行自动化分析,作为安全人员,你永远没办法知道开发人员是怎么写代码的。于是选择用关键字匹配的你面临着两种选择:

  • 高覆盖性 – 宁错杀不放过
  • 高可用性 – 宁放过不错杀

问题显而易见,高覆盖性和高可用性是这种实现方法永远无法解决的硬伤,不但维护成本巨大,而且误报率和漏报率也是居高不下

关键字匹配最大的问题在于你无法得知开发人员的代码习惯,也就没办法通过正则的方式去查找漏洞,开发人员不同但是编译器是相同的,程序总是流式运行的,这个时候基于information flow的代码审计方式就诞生了。

  • source: 我们可以简单的称之为输入,也就是information flow的起点
  • sink: 我们可以称之为输出,也就是information flow的终点
  • information flow,则是指数据在source到sink之间流动的过程。

把这个概念放在代码审计过程中,source就是指用户可控的输入,而sink就是指我们要找到的敏感函数。如果某一个source到sink存在一个完整的流,那么我们就可以认为存在一个可控的漏洞,这也就是基于information flow的代码审计原理。

无论是基于哪种中间件建立的代码分析流程,都离不开3个概念,流、Source、Sink,这类代码分析的原理无论是正向还是逆向,都是通过在Source和Sink中寻找一条流。而这条流的建立围绕的是代码执行的流程,这种分析的方式就是数据流分析(Data Flow)。

而QL就是把这个流的每一个环节具象化,把每个节点的操作具像成状态的变化,并且储存到数据库中。这样一来,通过构造QL语言,我们就能找到满足条件的节点,并构造成流。在 CodeQL 中,代码被视为数据,安全漏洞被建模为可以从代码流数据库执行的查询。

CodeQL支持语言

可以看到CodeQL支持的语言种类丰富:

CodeQL运行

CodeQL有两种方式运行,第一种是在线运行,第二种方式是本地运行,我们这里先介绍一下如何在线运行:

在线运行

1、以jndi漏洞为例,首先访问:https://lgtm.com/,查询JNDI相关的ql规则,我们可以看到第一个是jndi注入:

2、选择打开查询控制台:

3、点击Select projects to query选择项目:

这里我已经在github上传了一个测试的项目,如果你需要审计第三方的代码或者希望审计自己的代码,可以点击右上角Project lists添加项目:

直接输入github地址,如log4j项目,直接点击Follow:

如下图代表项目添加成功,如果项目之前没有被编译过,需要等待编译完成:

4、然后返回到查询页面,勾选项目点击Run进行运行,等待结果:

5、这里以自定义的项目Demo为例,可以看到提示找到一个问题:

从source到sink找到了一条路径,认为存在jndi注入漏洞:

本地运行

本地运行和在线运行类似,但是需要我们先搭建本地运行环境

1、首先我们下载codeql

下载地址:https://github.com/github/codeql-cli-binaries/releases

根据自己的系统选择不同的包:

2、在Vscode中下载CodeQL插件:

3、转到Vscode设置页面设置CodeQL可执行文件路径:

4、以我们的Demo项目为例,在控制台打开项目目录,然后生成CodeQL数据库:

命令:

~/Documents/codeql/codeql database create demodb –language=java –overwrite –command=”mvn clean install -Dmaven.test.skip=true”

参数说明:

database create demodb 代表创建数据库,数据库路径为demodb

–language=java 代表语言为JAVA

–command=”mvn clean install -Dmaven.test.skip=true” 代表编译命令

如图所示代表创建数据库成功:

5、Vscode中打开数据库:

如下图所示代表数据库连接成功:

然后我们去运行一下示例的ql脚本,右键脚本然后选择CodeQL运行:

参考

Jndi注入查询ql脚本:

/**
 * @name JNDI lookup with user-controlled name
 * @description Performing a JNDI lookup with a user-controlled name can lead to the download of an untrusted
 *              object and to execution of arbitrary code.
 * @kind path-problem
 * @problem.severity error
 * @precision high
 * @id java/jndi-injection
 * @tags security
 *       external/cwe/cwe-074
 */

import java
import semmle.code.java.security.JndiInjectionQuery
import DataFlow::PathGraph

from DataFlow::PathNode source, DataFlow::PathNode sink, JndiInjectionFlowConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "JNDI lookup might include name from $@.", source.getNode(),
  "this user input"

jndi注入Demo Springboot代码:

@RestController
@RequestMapping("/test")
public class DemoController {
    @GetMapping("/demo")
    public void jndiLookup(HttpServletRequest request) throws NamingException {
        String name = request.getParameter("name");
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
        env.put(Context.PROVIDER_URL, "rmi://trusted-server:1099");
        InitialContext ctx = new InitialContext(env);
        ctx.lookup(name);
    }
}

发表评论