sleepykino
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

因版本变化,CodeQL部分写法已经完全变化,网上资料过于老旧,AI也使用的老版本资料。特此抛砖引玉,记录下当前版本如何编写CodeQL。此文章为入门文章,有了基本了解后再去看其他大佬的文章,或者AI协助会方便很多。
工具介绍
CodeQL是一款强大的代码分析工具,目前归属于GitHub,广泛用于代码审计和安全研究。
安装部署
准备环境
VSCode安装后安装CodeQL插件
下载解压后添加环境变量,方便执行命令
下载后备用即可,因为需要引用很多已有的包,大部分自己写的QL语句需要放在此文件夹内,这步等于是安装CodeQL的语言了
语言对应项目管理工具(比如想审计java装maven)
生成数据库
需要将代码生成为对应的数据库文件,方便工具进行查询。
使用CodeQL创建数据库
codeql.exe database create C:\xx\xx\JavaSec-database --language="java" --source-root=C:\xx\xx\JavaSec
codeql.exe database create C:\xx\xx\JavaSe-database //创建数据库和设置存储位置
--language="java" //语言java
--source-root=C:\xx\xx\JavaSec //源代码位置
最简单的语句,将由CodeQL自动脚本来执行项目编译工作。
但有时候编译过程不是那么顺利,可以通过手动命令来执行
codeql.exe database create C:\xx\xx\JavaSec-database --language="java" --source-root=C:\xx\xx\JavaSec --overwrite --command="mvn clean install"
--overwrite,失败后可以原地覆盖
--command="mvn clean install" 输入命令编译,这里java项目使用Maven命令
其他常用的Maven命令还有
-Dmaven.test.skip=true //跳过测试
-pl !xxx -am //跳过xxx模块
工作区
将CodeQL-cli源码加入VSCode工作区
直接拖进来,然后QL插件中就会自动识别可执行的QL文件
QL插件中选择数据库
选择数据库所在文件夹,选择完会有显示,此时右键点击可以将源码加入工作区,方便查看
保存工作区
建议保存下工作区,防止每次都要选
实例分析
实例分析
自带ql
CodeQL自带了很多ql文件,先看下官方是怎么写的。
(使用了java靶场一下也将讲解Java的QL语句,各种语言逻辑是相同的)
在java\ql\src\Security\CWE\目录下就是各种写好的ql,以xss.ql为例,路径为codeql-codeql-cli-v2.20.4\java\ql\src\Security\CWE\CWE-079\XSS.ql
此QL文件可以直接执行,在QL插件中点击文件右侧小三角即可
右侧会生成对应报告。
到这里其实已经可以用了,但了解工作原理可以帮助更好的理解和自己改造,甚至是借鉴优化出更好的工具
详细分析
下面根据XSS.ql进行QL介绍。
XSS.ql内容很短,整体分为三部分:
红框:注解,介绍当前的漏洞信息等
绿框:引用,这段代码很短,是因为很多逻辑都是在其他地方处理的,需要引用。(写过代码的小伙伴都懂)
蓝框:查询语句,先定义了source和sink,再用flowPath()查询是否连通,最后输出结果。
简单解释source-输入点,sink-触发点
那么关键地方就很明显了,source和sink是如何定义的?
注意到source和sink的类型都是XssFlow::PathNode,我们点进去看看XssFlow是什么。
ctrl+左键跟进,发现来到了java\ql\lib\semmle\code\java\security\XssQuery.qll
发现XssFlow在最下面,指向的是上面的XssConfig。看XssConfig和里面谓词的名字就能看出大概功能了,isSource和isSink就是source和sink的定义了。
sink
predicate isSink(DataFlow::Node sink) { sink instanceof XssSink}
判断sink是否实现了XssSink,那么玄机就在XssSink中,跟进看看。进入到XSS.qll文件,发现XssSink中什么都没有,是ApiSinkNode的子类,跟进去只有一个sinkNode的判断。这里属于COdeQL自身的判断功能,将在下面一起介绍。
同时注意到下面有个DefaultXssSink继承了XssSink,这里就是CodeQL的一个特点,所有子类实现的逻辑也都会检查。
跟进DefaultXssSink,发现分类两部分逻辑,
第一部分
sinkNode(this, ["html-injection", "js-injection"])
进入sinkNode
两个参数一个是Node,一个是Kind,这个判定条件的含义就是当前节点是否属于html-injection和js-injection这两种类型。
看着这里可能已有疑惑,Kind是在哪里定义的?我们从import入手,发现在java\ql\lib\semmle\code\java\dataflow\ExternalFlow.qll中存在部分解释
Codeql有一套数据格式来定义谓词进行判断,其中sink类型的第8列为Kind
官方提供了一些文件写了常见的类,在java\ql\lib\ext\文件夹下
以org.apache.http.model.yml为例
- ["org.apache.http", "HttpResponse", True, "setEntity", "(HttpEntity)", "", "Argument[0]", "html-injection", "manual"]
可以看到html-injection,符合这个谓词的是sink点,也就是org.apache.http.HttpResponse.setEntity。
sinkNode(this, ["html-injection", "js-injection"])
所以简单理解就是有一批标签,符合这个标签的就是sink。
第二部分
exists(MethodCall ma |
ma.getMethod() instanceof WritingMethod and
XssVulnerableWriterSourceToWritingMethodFlow::flowToExpr(ma.getQualifier()) and
this.asExpr() = ma.getArgument(_)
第二部分定义了一个MethodCall ma,而且需要同时满足三个条件。MethodCall ma表示ma是一个是对带有参数列表的方法的调用。
第一个条件
ma.getMethod() instanceof WritingMethod
ma调用的方法符合WritingMethod,跟进WritingMethod,还在XSS.qll文件中
这里可以看出用了需要用了Java.io下print、apped、format、write方法
第二个条件
XssVulnerableWriterSourceToWritingMethodFlow::flowToExpr(ma.getQualifier())
同理跟进也都在XSS.qll文件中
这里判断了是否有向Servlet、JSP或JSF响应写入操作
第三个条件
this.asExpr() = ma.getArgument(_)
判断表达式需要是这个ma的任一参数。
三个条件都成立时,也属于XssSink。
source
predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
source是ActiveThreatModelSource的实例化,那我们跟进这个“主动威胁模型来源”看看都包括什么。
和sink类似,也是通过标签的形式来判断的。
改编自写
网上的资料大多都过时了,包括AI的资料也是。但是没关系,只要有前面的了解,仿写也是毫无问题的。接下来将对仿写思路进行讲解,这样就算以后再变也可以快速修改。
后来查看发版日志找到了对照,也贴在这里https://github.blog/changelog/2023-08-14-new-dataflow-api-for-writing-custom-codeql-queries/
增加逻辑
经过上文分析我们已经知道了XSS.ql是如何实现逻辑的。我们直接将XssFlow和XssConfig复制过来并修改即可。
在java\ql\test\目录下新建一个XSS-NEW.ql文件,直接将Xss.ql内容复制过来。
XssFlow和XssConfig也复制过来并插入,注意import也要复制发过来。
![image.png]
(https://image.3001.net/images/20250314/1741946655_67d3ff1fa5f4650e4987c.png!small)
添加sink判断逻辑
我们在原有基础上增加逻辑,使用一个or来连接,并增加exists()谓词。在exists()中我们增加判断有一个地方调用了insert方法,那么他的第一个参数就判定为sink点。
结束
就这么简单,复制粘贴就可以完成新的ql编写。
自定source、sink
如果想好看一点,可以仿照如下格式,放到类里。
这样写出来方便自己阅读,如果想更近一步可以参考官方目录放在qll文件中再引用。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)

