初探 CodeQL 自动化代码分析(二)

使用场景

之前介绍了如何使用命令行形式的coedql,也提到它的适用之处。

这篇就介绍一下如何在 Visual Studio Code 中使用 codeql 插件,对项目代码做自动化分析。

这也是大家分析代码时最常用的方法。

.

.

搭建环境与安装

和命令行工具一样,首先要下载codeql核心引擎,

https://github.com/github/codeql-cli-binaries/releases/tag/v2.7.3

然后再下载配合核心引擎的仓库

https://github.com/github/codeql.git

注意要把上面两个目录放至同级目录里

最后配置一下环境变量

export PATH=$PATH:/data/CodeQL/codeql_cli

简单搭建好之后,输入以下命令验证安装成功

codeql resolve languages

codeql resolve qlpacks

image-20220518104448962

前面的步骤和上一篇完全一样,详情参考前文

.

.

漏洞靶场代码数据库构建

在网上找到一个适合用来练习codeql的靶场项目,注意到是一个Java Project

https://github.com/l4yn3/micro_service_seclab.git

克隆到本地之后使用如下命令搭建数据库

在有pom.xml的目录下:

codeql database create ./seclab-database –language=”java” –command=”mvn clean install –file pom.xml”

3.

踩坑记录:

如果显示fail [‘mvn’,’clean’,’install’],则去掉–command这个参数,使用codeql内置的编译打包器进行编译打包

如果还是不行,则尝试使用项目中自带的maven,自己下载的maven环境版本太高的话很可能会出错

最后构建数据库如下:

image-20220518104530227

.

.

使用 Visual Studio code 插件进行分析

首先打开VScode,插件搜索CodeQL,然后install安装

随后配置好核心引擎的执行目录

即填codeql核心引擎安装的目录位置

image-20220518104742075

image-20220518104802168

image-20220518104844352

安装好后,左上角选择FIle,打开我们的codeql仓库

image-20220518104925249

之后点击侧方选项框中的codeql插件按钮

在Databases模块中选择Add a CodeQL database : From a folder

然后找到刚才生成的代码数据库,选择导入

image-20220518104958630

导入成功:注意要有打勾符号,没有的话右边选择这个数据库,并使它作为当前数据库

Set Current Database

image-20220518105023345

再次回到刚才的Exploer,选择并查看刚刚打开的codeql仓库目录

因为这个项目是Java构建的,代码数据库刚才也是生成Java格式的

所以查询语句的使用自然是要用Java的查询语句

可以看到 仓库中的java目录下有一个ql目录,这里存放的就是大量codeql官方给出的查询语句文件

image-20220518105057021

在ql目录下继续看,定位到src文件夹的Security/CWE

点进去看可以发现,基本上所有的查询语句根据文件的名字就能知道是什么

比如CWE-074里的文件,JndiInjection.ql

字面上就是用来查询是否有JNDI注入查询语句

右键这个CWE-074目录,选择倒数第二个

image-20220518105259290

执行结果如下:显示为没有jndi注入

image-20220518105331205

使用CWE-089,sql注入模块来探测是否有漏洞

image-20220518105612591

.

探测报告:

image-20220518105816233

.

的确存在,这一结果也和靶场的漏洞符合(参考github的Readme.md)

image-20220518105859893

之后就可以根据报告提供的位置,人工验证一下是否真实有注入。

毕竟codeql只是辅助,它的结果不一定就是准确的,而且使用的是官方给的查询语句,就很大可能会报错

有时候同一个数据库,同一个查询语句,最后执行的结果不一样也是存在的。

至于为什么会这样,我目前还没有太明白

后续文章会通过解析codeql查询语句原理尝试解决。

.

.

总结

codeql 的VScode插件可以非常方便地辅助研究者进行代码审计,它核心的点就在于查询语句的编写,

虽然官方提供了许多查询语句,但并不是每一个都适合,会出现误报等情况。

所以很有必要了解查询语句编写和codeql查询原理

在后面的文章我会试着探讨怎么写出合适到手项目的codeql查询文件

.

.

./

初探 CodeQL 自动化代码分析(一)

实际需求

之前分析了两个Apache Kylin 框架的rce漏洞代码层原理,从正向到逆向地尝试探讨了漏洞发现者是如何发现这些漏洞的,

当时得出的结论是,可以通过找寻敏感的函数名,方法名,类名,锁定这些内容的位置之后,

从代码层面上,一步步逆推到参数传入的地方,然后再分析是否存在利用链。

但是这种方法难度较大,除了要掌握大量敏感函数、方法、类等信息,还需要对每一种框架结构有一定的了解,此外还要手动找到每一个潜在切入点。

所以我在想是否有一种自动化工具能帮我们完成这个过程,简化代码审计的工作量?

CodeQL 在很大程度上就可以简化上述的工作。

.

.

什么是 CodeQL ?

CodeQL 本质是一个代码分析引擎,使用它的时候,代码会被当作数据处理。

既然把它当成了一种数据,那么被当做数据的代码可以像查询mysql数据库一样被查询。

所有的bug,安全漏洞,或者某些错误代码都可以被查询出来(前提是查询语句要正确),这个过程本质上和查询mysql中userid为某一个值的过程没有区别。

而这里关键的一点就是CodeQL的查询语句,你可以运行GitHub研究人员和社区贡献者编写的标准CodeQL查询,或者编写你自己的查询语句来用于自定义分析。

.

总结一下 CodeQL 干的事:

  • 把要分析的代码准备好(通过建立 CodeQL 数据库 database)
  • 对这个建立好的数据库执行 CodeQL 查询语句
  • 输出查询结果

.

在这篇文章中,不会有太多关于自定义分析的语句的内容,目前主要是介绍一下 CodeQL 的基础用法,从搭建到使用别人写好的查询语句进行分析尝试。

.

.

搭建环境与安装

使用 CodeQL 的时候可以选择两种方式

  • 命令行模式 CLI (Command Line Interface)
  • Visual Studio Code 插件

.

虽然使用 VScode 插件比命令行更加方便,且功能也多一点,

但是我认为掌握命令行模式也很重要。

因为有时候项目文件太大导致静态代码的数据库太大,此时往往会放到服务器上去跑,那么图形化界面的优势就少了。

而且命令行模式本质其实和 VScode插件一样的,掌握命令行可以更好地理解VScode中插件都干了什么事

.

.

命令行模式安装

首先直接下载 CodeQL 引擎

https://github.com/github/codeql-cli-binaries/releases/tag/v2.7.3

image-20220517174631683

选择对应系统版本,我这里使用的是 Debian10,选择codeql-linux64.zip

这个文件可以看作是 CodeQL 的核心引擎,它是不开源的(所以是BINARIES)

下载完成之后解压,

建议设置好目录,因为 CodeQL默认搜索上级目录和当前目录,文件目录层级清晰一些有利于核心引擎查找它需要的文件

我把它放在了自定义的 CodeQL 目录

image-20220517174718891

之前提到过,有了核心引擎还不够,还需要查询语句等,可以把这类文件看作一个仓库

仓库里包含了查询语句,一些依赖,包,这些东西都是codeql分析C、C++、C#、Java、JavaScript、Python、Ruby等代码数据库时需要的

(注意go语言代码数据库所需要的仓库是需要另外下载的)

所以一个完整的codeql包括核心引擎和仓库

下载仓库,地址如下

proxychains4 git clone –recursive https://github.com/github/codeql.git

注意加入 –recursive 选项把全部子模块都下载了

最后将这个目录改名为 codeql_repo (目录原来默认为 codeql,改名主要是为了目录结构清晰)

关键一点是要将这个目录放到刚才下载核心引擎目录的同级目录

image-20220517174756637

这里有一点要注意的,如果下载了 go 语言的代码仓库,那么要把目录放至与核心引擎同一级

如下:

image-20220517174836175

以上都做好后,开始配置环境变量,目的是为了在任意处使用codeql

vim ~/.zshrc

在最下面填入下面内容

export PATH=$PATH:/data/CodeQL/codeql_cli

/data/CodeQL/codeql_cli 换成你 codeql* 或者 codeql.cmd 的目录

最后是验证 codeql 安装是否成功, 在任意目录执行一下两条命令:

codeql resolve languages (显示可以用来被创建代码数据库的语言有哪些)

codeql resolve qlpacks (显示哪一些qlpacks可以被codeql命令行工具找到,qlpacks就是查询语句的包)

如下图就成功安装

image-20220517174913805

image-20220517174933659

.

.

命令行模式构建代码数据库

要对代码进行分析,首先要将代码构建为一个代码数据库database,而构建数据库的命令如下:

codeql database create –languages=

  • 写你要创建的代码数据库的路径,且注意不能是已经存在的目录

  • :

    写语言标识符,分析的代码是什么语言就选择什么标识符

image-20220517174955944

  • 基本参数选项如上,还有几个参数

  • –source-root :

    创建数据库时使用的主要源文件的根目录路径。默认情况下,该命令假定当前目录是源文 件的根目录–使用该选项可以指定一个不同的位置。

  • –db-cluster :

    可以用来创建多语言代码数据库

  • –command :

    使用一种或多种编译型语言创建数据库时使用,

    如果指定语言只有Python和JavaScript这种非编译型语言,则省略。

    主要是用来加入编译器的 build command 的

    (如果省略,codeql会自动检测并使用自己的工具来编译创建 )

  • –no-run-unnecessary-builds:

    与-db-cluster一起使用,来抑制CodeQL CLI不需要监控构建的语言

    (例如,Python和JavaScript/TypeScript)的构建命令。

    代码分为编译型语言和非编译型语言,使用 codeql 构建不同类型的代码数据库的时候也有所不同。

    而编译型语言在创建代码数据库的时候codeql会监视整个过程

.

.

命令行构建Java项目的代码数据库

首先随便从网上下载一个Java 项目,最好别太大

我选择的是这个:某博客项目

https://github.com/saysky/ForestBlog.git

\2.

之后进入到该项目中找到 pom.xml

使用如下命令构建代码数据库

codeql database create java-database –language=java –command=’mvn clean install’

也可以省略掉后面 –command 选项,让codeql 用它内置的编译器

创建成功之后如下:

image-20220517175024931

image-20220517175044461

除了自己通过源码构建代码数据库,还可以直接从 LGTM 这个网站上下载一些开源项目的代码数据库,

https://lgtm.com/projects/g/apache/kafka?mode=list

直接下载之后就可以使用 codeql 查询探测这个代码数据库中是否有漏洞了

可以省去codeql构建代码数据库的过程

.

.

基础 codeqld 探测语法

创建成功代码数据库之后就要使用查询语句进行漏洞探测

基础语法:

codeql database analyze –format= –output=

  • :

    构建好的代码数据库的存放目录

  • –format :

    输出结果文件的格式,可以是CSV,SARIF,图表形式

  • –output :

    输出结果文件的路径

  • :

    写你想要使用的查询语句,可以是一个或者多个单独的查询语句文件。

    或者是一个装有查 询语句文件的目录。

    如果不选这个的话,codeql会根据代码数据库的语言默认使用对应的 查询语句

  • –threads :

    设置线程数量,默认为1。线程越多查询结果输出越快

.

.

实例演示漏洞探测

使用下面命令即可开始对博客项目代码漏洞探测,

codeql database analyze /构建项目代码数据库的路径 –format=csv –output=./result –threads=5

结果如图:

image-20220517175110219

.

image-20220517175127462

.

.

总结

codeql 可以更好帮助我们进行代码审计,当遇到一个项目源代码的时候,如果不清楚其架构,甚至对其语言没有足够的了解,可以先尝试使用该工具进行探测。

网上大部分资料介绍的都是使用codeql是通过VScode插件来使用的形式,

但命令行模式下的codeql使用,更加适合批量地对大批源码进行探测,放在服务器上跑也不需要考虑图形化界面带来的不便。

codeql的核心点在于选择或者编写适合的查询语句(文件),后续的文章会以VScode的方式介绍如何用查询语句,并尝试对一个已知漏洞的代码进行审计

.

.

./

Windows access token 介绍及利用

1 背景

我第一次看到 windows access token 是在打靶场的时候,通过 msf 的工具获取主机上管理员用户的令牌来伪造管理员权限,并通过进程迁移实现完全的提权。

当时对于 windows access token 还有很多疑问,这里尝试对其机制进行一个比较完整的解释,以及总结一下如何利用这个 access token 进行用户的伪造。

.

.

2 Windows access token 机制介绍

一 简介

access token 翻译过来就是访问令牌,官方给的解释是:一个进程或者线程的描述安全上下文(the security context)的对象。安全上下文可以理解成一段关于系统安全的信息。

令牌中的信息包括与进程或线程相关的用户账户的身份和权限。

当用户登录系统之后,windows 会产生一个令牌(access token)颁发给用户,这是通过 LSASS.exe 这个程序完成的。

然后这个用户开启的所有进程都会从用户那里复制一份 access token 给进程自己。这也是为啥有时候 A 用户的进程 B 用户没有权限去使用,因为 A 、B 用户的 access token 不同,而且该进程的代码中写了令牌校验的机制。

当一个程序(线程)需要和系统安全相关的对象进行交互,或者需要权限执行某些系统任务的时候,windows 系统就会通过 access token 去校验身份。至于具体的校验机制,后面再补充。

.

二 令牌包含的内容

  • The security identifier (SID) for the user’s account
    • 用户帐户的安全标识符(SID)
  • SIDs for the groups of which the user is a member
    • 用户所属的组的SID
  • A logon SID that identifies the current logon session
    • 用于标识当前登录会话的登录SID
  • A list of the privileges held by either the user or the user’s groups
    • 用户或用户组所拥有的权限列表
  • An owner SID
    • 所有者SID
  • The SID for the primary group
    • 主要组的SID
  • The default DACL that the system uses when the user creates a securable object without specifying a security descriptor
    • 访问控制列表
  • The source of the access token
    • 访问令牌的来源
  • Whether the token is a primary or impersonation token
    • 令牌是主要令牌还是模拟令牌
  • An optional list of restricting SIDs
    • 限制SID的可选列表
  • Current impersonation levels
    • 目前的模拟等级
  • Other statistics
    • 其他统计数据

.

.

3 主要令牌和模拟令牌

一 主要令牌 (primary token)

1 主要令牌的产生

主要令牌是用户登录的时候系统就会给该用户先颁发一个主要令牌

这里的登录主要指交互式登录,比如本地登录,远程桌面

.

这里补充一下关于主要令牌产生的细节

用户使用凭据(用户密码)进行认证 –> 登录 session 创建 –> windows 返回用户的 sid 和用户所在组的 sid –> LSA 创建一个 Access token —> 使用凭据成功认证 –> 登录 session —> token —> 进程、线程

image-20220208160257180

.

2 主要令牌的传递

用户登录之后,用户开启的所有进程都会先复制一份主要令牌给进程自己

因此每个进程都有一个主要令牌,用于描述与该进程关联的用户帐户的安全上下文。

默认情况下,当进程的线程与安全对象进行交互时,系统使用主令牌。

此外,主要令牌有个机制,前面我们说了每个进程都复制了一份创建者的主要令牌,而这些进程开启的子进程也会从父进程那继承一份主要令牌(前提是子进程的代码中没有指定使用特定的 access token,且这个过程还是有 LSASS.exe 执行的)

.

二 模拟令牌(impersonation token)

1 模拟令牌的定义

主要令牌很好理解,就是用户登录后系统颁发的描述安全信息的对象

而模拟令牌的定义则是:

当进程(线程)需要以其他用户的身份获取系统资源或执行某些功能的时候,就需要用到其他用户的模拟令牌

impersonation tokens: these allow a particular process(or thread in a process) to gain access to resources using the token of another (user/client) process

注意:一个模拟其他用户(客户端)的进程或线程是同时具有自己的主令牌和其他用户的模拟令牌的

.

2 模拟令牌的产生

当用户注销后,系统将会使主令牌切换为模拟令牌并保存在系统中,

且不会将令牌清除,只有在重启机器后才会清除。

.

3 为什么会有模拟令牌?(我们拿模拟令牌来干嘛)

impersonation token is for impersonation .(模拟令牌是用来模拟的)

看起来像句废话,但是这里重点在于 模拟(impersonation) 这个行为

官方原文:

Impersonation is the ability of a thread to execute using different security information than the process that owns the thread. Typically, a thread in a server application impersonates a client. This allows the server thread to act on behalf of that client to access objects on the server or validate access to the client’s own objects.

大概意思是,线程有时候要使用到其他用户的安全信息时,就会需要到模拟令牌。模拟(impersonation)就是去线程去模拟其他用户的身份,从而可以更加方便地执行各种功能逻辑。

简单来说就是我们希望不同用户(客户端)来访问主机上的服务时,服务可以模拟客户端的身份去访问服务,而不是用自己的主进程Token身份去访问,从而解决一些权限问题。

.

4 模拟令牌的级别

我们说用户注销之后会产生模拟令牌,且模拟令牌的意义就是用来被线程模拟其他用户身份的,但是并不是所有的用户都想被线程模拟身份。

所以模拟令牌就设置了一个级别机制

只有当模拟令牌具有 Impersonation 和 Delegation 级别的时候才可以被用来模拟。

.

模拟令牌的所有级别:

  • SecurityAnonymous:current user/client cannot impersonate another user/client
    • 其他用户(客户端)无法通过这个模拟令牌模拟该用户(模拟令牌的所有者)或者客户端。
  • SecurityIdentification:current user/client can get the identity and privileges of a client, but cannot impersonate the client
    • 其他用户(客户端)可以通过这个模拟令牌获取到该用户的身份信息和权限,但是不能伪造该用户(客户端)
  • SecurityImpersonation:current user/client can impersonate the client’s security context on the local system
    • 其他用户可以在本地系统上通过这个模拟令牌伪造该用户,伪造指的是获取到该用户的安全上下文
  • SecurityDelegation:current user/client can impersonate the client’s security context on a remote system
    • 其他用户可以在远程系统上通过这个模拟令牌伪造该用户

.

.

4 不同登录方式获取的令牌

一 登录方式

  • 交互式登录
    • console login (type 2)
    • rdp login (type 10)
    • psexec (type 2)
  • 网络登录
    • wmi (type 3)
    • winrm (type 3)

.

二 各自获取的令牌种类

  • primary token 这种令牌通常用于本地及远程 RDP 登录

  • impersonation token 这种则通常用于各种非交互式的登录,比如,netuse,wmi,winrm等等

.

.

5 盗取模拟令牌攻击 (Token Kidnapping)

一 原理

经过上面的介绍,

我们知道了用户注销的时候会在系统上保存一个模拟令牌,而在特定情况下,当前用户(进程)又可以使用模拟令牌来伪造模拟令牌所属者的身份。

所以这里就存在一个提权的可能。

假设现在拿到主机的一个普通用户 Normal 的权限

只要满足下面几点条件,就能够通过盗取管理员用户的模拟令牌来实现提权

  • Normal 用户拥有 SeImpersonatePrivilege 权限,这意味着该用户能够使用别人的模拟令牌
  • 管理员用户注销之后,系统并没有重启,此时管理员的模拟令牌仍保留在系统上
  • 管理员的模拟令牌的等级是 SecurityDelegation (远程)或者 SecurityImpersonation(本地)

.

不过需要注意的是,有时候盗取管理员令牌之后,仍不算百分百的提权。还需要进行进程迁移才能够实现百分百提提权。

关于这点我没有找到特别明确的解释,以下的解释只是个人理解

首先用户登录的时候,系统会给当前用户颁发 primary token (主要令牌),然后该用户开启的所有进程都会复制一份这个 token 给进程自己。

而当用户注销后,系统将会使主令牌切换为模拟令牌,并保存在系统上。系统此时不会将令牌清除,只
有在重启机器后才会清除。

所以我们只要能够拿到保存在系统上的其他用户的模拟令牌(impersonation token),就能够对该用户
进行一定程度上的伪造。
为啥说是一定程度,而不是完全伪造呢?

因为有的进程是认模拟令牌(impersonation token)的,所以对于这些进程,只要有模拟令牌,就会被进程认为当前用户是合法的,并允许当前用户使用该进程。

但并不是所有的进程都认,有很多进程只认 primary token(主要令牌),所以只有模拟令牌的话,并不能算完全伪造目标用户。

后续如果要完全模拟的话,还是需要进行进程迁移。

由于进程有个机制,在进程被创建的时候,如果代码没有写明要指定获取某个特定的令牌,那么该进程就会从父进程那里继承父进程的令牌。

所以,只要我们能够进程迁移到拥有目标用户主要令牌的进程上,我们就能通过主要令牌完全伪造目标用户了。

.

.

二 实战 (取自 tryhackme Alfred 部分靶场笔记)

这里使用 tryhackme 的 Alfred 靶场进行演示,

当我们拿到一个 meterpreter session , sessions -i 1 连上

getuid 命令查看下当前用户,发现是普通用户 alfred\bruce

image-20220208171226852

然后在 meterpreter shell 中打开一个 cmd 命令行 shell

接下来查看当前用户的权限,看看是否有 SeImpersonatePrivilege 权限whoami /priv 命令)

image-20220125155510832

.

发现的确存在,那么当前用户就可以使用系统中保存的其他用户的模拟令牌

接下来退出 cmd shell ,并使用 msf 中的 incognito 模块尝试列举都有什么用户的模拟令牌

load incognito 加载 incognito 模块

list_tokens -g 列举系统保存了什么用户的模拟令牌

image-20220125172816401

image-20220125172843607

可以看到,

使用 list_tokens -g 命令列举系统保存了什么模拟令牌之后,

出现了两个表,

Impersonation Tokens Availlable 和

Delegation Tokens Available

其实他们都是模拟令牌,只不过级别不一样。

Delegation 是最高级别,允许当前用户在远程系统上来使用这个模拟令牌

我们主要查看 Delegation 中的模拟令牌,因为是远程连接进去的

恰好这里有一个属于管理员的模拟令牌

用它!

使用如下命令

impersonate_token “指定的用户” 拿取制定用户的模拟令牌

impersonate_token “BUILTIN\Administrators” 拿取 Administrators 的 impersonation token

image-20220125182701837

.

成功拿到管理员的模拟令牌之后

此时使用 getuid 命令,查看当前用户是不是管理员

image-20220125182900616

可以看到,确实显示的是管理员

但是即使是现在,我们拿到了管理员的模拟令牌,也不一定能够完全伪造管理员的身份

就像之前说的那样,有些进程通过 impersonation token (模拟令牌) 来认当前用户身份,

而有些进程只认 primary token (主要令牌) 来确认用户的身份

所以我们需要进行进程迁移

.

那么现在的情况是,我们只是通过拿取模拟令牌实现了一定程度上的伪造管理员用户

接下来就是通过进程迁移,迁移到拥有管理员用户的主要令牌的进程

通过进程会继承父进程主要令牌的机制,完全伪造管理员

实现百分百的提权

这里推荐一个最保险的进程,services.exe

.

先用 ps 命令查看进程:

image-20220125185603130

.

migrate 668 迁移到 services.exe 去

image-20220125203947224

此时已经拿到了管理员的主要令牌了,

已经可以百分百伪造管理员了

接下来为了进一步确认,尝试去读取靶标 (C:\Windows\System32\config\root.txt)

.

开启一个 cmd shell

type C:\Windows\System32\config\root.txt

即可成功读取到靶标

image-20220125204530203

.

.

6 参考文章

https://www.anquanke.com/post/id/204721

https://hackergu.com/powerup-stealtoken-rottenpotato/

https://docs.microsoft.com/en-us/windows/win32/secauthz/access-tokens

https://docs.microsoft.com/en-us/windows/win32/secauthz/client-server-access-control

https://docs.microsoft.com/en-us/windows/win32/secauthz/impersonation-levels

https://rootclay.gitbook.io/windows-access-control/access-token

https://www.anquanke.com/post/id/204721

https://msrc-blog.microsoft.com/2009/04/14/token-kidnapping/

http://www.4k8k.xyz/article/qq_41874930/111963586

https://www.offensive-security.com/metasploit-unleashed/fun-incognito/

https://payloads.online/archivers/2018-11-30/1/#0x03-windows-access-token

https://www.exploit-db.com/papers/42556

https://lengjibo.github.io/token/

https://www.geekby.site/2020/05/域渗透之-windows-access-token-攻击/

https://shanfenglan.blog.csdn.net/article/details/111926058

https://3gstudent.github.io/渗透技巧-Windows九种权限的利用

https://blog.csdn.net/sch0120/article/details/70226903

.

.

/