我的学习习惯是上去就是干,看看基本语法就行遇到不会的再去查,文章可能有疏漏错误之处,请见谅,欢迎一起学习交流,自己学太累了,codeql 太难了。
CodeQL
CodeQL 是开发人员用来自动化安全检查的分析引擎,安全研究人员用来执行变体分析。
在 CodeQL 中,代码被视为数据。安全漏洞、错误和其他错误被建模为可以针对从代码中提取的数据库执行的查询。您可以运行由 GitHub 研究人员和社区贡献者编写的标准 CodeQL 查询,也可以编写自己的查询以用于自定义分析。查找潜在错误的查询直接在源文件中突出显示结果。
CodeQL 分析包括三个步骤:
-
通过创建 CodeQL 数据库准备代码 -
针对数据库运行 CodeQL 查询 -
解释查询结果
安装
codeql-cli
在命令行(cli)的环境下运行codeql
项目地址 : github/codeql-cli-binaries ,打开项目地址之后进入Releases库,下载对应操作系统的压缩包解压到任意一个文件夹。最好配置到环境变量中,方便使用。
codeql
开源的codeql标准库和查询库
项目地址: github/codeql, 进入到上面解压codeql-cli的文件夹,并把该仓库clone下来,保证codeql-cli和codeql在同一个目录下
vscode-codeql
vscode的codeql插件,直接在插件市场安装
基本语法
查询(Query)
查询是CodeQL的输出。查询有两种类型,分别是
-
选择子句 -
查询谓词,这意味着我们可以在当前模块中定义或者从其他模块中导入
选择子句
在编写查询模块时,select语句通常位于文件的末尾,其一般格式如下所示:
from /* ... 变量声明... */
where /* ... 逻辑公式 ... */
select /* ... 表达式 ... */
其中,from和where子句是可选的
from int x, int y
where x = 6 and y = 7
select x + y
在select语句中我们还可以使用一些关键字:
-
as
关键字,后面跟随一个名字。作用相当于sql中的as
,为结果列提供了一个"标签",并允许在后续的select表达式中使用它们。 -
order by
关键字,后面跟随一个一个结果列名。作用相当于sql中的order by
,用于排序结果,并且在结果列名后可选asc
(升序)或desc
(降序)关键字。
全局数据流
全局数据流跟踪整个程序的数据流,因此比本地数据流更强大。但是,全局数据流不如本地数据流精确,并且分析通常需要更多的时间和内存来执行。
DataFlow::Configuration
class Configuration extends DataFlow::Configuration {
Configuration() { this = "OpenUrlRedirect" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
...
}
override predicate isBarrierOut(DataFlow::Node node) {
......
}
deprecated override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
guard instanceof BarrierGuard
}
}
配置中定义了以下谓词:
-
isSource
——定义数据可能从哪里流出 -
isSink
- 定义数据可能流向的位置 -
isBarrier
--可选,限制数据流 -
isBarrierGuard
--可选,限制数据流 -
isAdditionalFlowStep
— 可选,添加额外的流程步骤
特征谓词Configuration()
定义了配置的名称
使用谓词执行数据流分析:hasFlow(DataFlow::Node source, DataFlow::Node sink)
from Configuration dataflow, DataFlow::Node source, DataFlow::Node sink
where dataflow.hasFlow(source, sink)
select source, "Data flow to $@.", sink, sink.toString()
全局污点跟踪
全局污点跟踪针对全局数据流,就像本地污点跟踪针对本地数据流一样。也就是说,全局污点跟踪通过额外的非保值步骤扩展了全局数据流。通过扩展类使用全局污点跟踪库TaintTracking::Configuration
,如下所示:
import semmle.code.cpp.dataflow.TaintTracking
class MyTaintTrackingConfiguration extends TaintTracking::Configuration {
MyTaintTrackingConfiguration() { this = "MyTaintTrackingConfiguration" }
override predicate isSource(DataFlow::Node source) {
...
}
override predicate isSink(DataFlow::Node sink) {
...
}
}
配置中定义了以下谓词:
-
isSource
- 定义污点可能从哪里流出 -
isSink
- 定义污点可能流向的位置 -
isSanitizer
— 可选,限制污点流 -
isSanitizerGuard
— 可选,限制污点流 -
isAdditionalTaintStep
— 可选,添加额外的污点步骤
与全局数据流类似,特征谓词MyTaintTrackingConfiguration()
定义了配置的唯一名称,因此"MyTaintTrackingConfiguration"
应替换为自己的类的名称。
污点跟踪分析是使用谓词执行的。hasFlow(DataFlow::Node source, DataFlow::Node sink)
污点分析定义
污点分析可以抽象成一个三元组<sources,sinks,processor>
的形式,其中:
-
source 即污点源,代表直接引入不受信任的数据或者机密数据到系统中 -
sink 即污点汇聚点,代表直接产生安全敏感操作(违反数据完整性)或者泄露隐私数据到外界(违反数据保密性) -
sanitizer 即无害处理,代表通过数据加密或者移除危害操作等手段使数据传播不再对软件系统的信息安全产生危害
另类 Debug - Partial flow
Partial Data Flow
允许寻找从一个给定的source
到任何可能的sink
的流动,让sink
不受限制,同时限制从source
到sink
的搜索步骤的数量。因此,可以使用这个来跟踪污点数据从源头到所有可能的汇的流动,并查看流动在哪里停止被进一步track
。
数据流中断时,使用 Partial flow 进行跟踪
导入import DataFlow::PartialPathGraph
,注意:不能同时与 import DataFlow::PathGraph
使用
import go
import semmle.go.security.SqlInjection
import DataFlow::PartialPathGraph
from SqlInjection::Configuration cfg,DataFlow::PartialPathNode source, DataFlow::PartialPathNode sink
where cfg.hasPartialFlow(source, sink,_)
select sink, source, sink, "Partial flow from unsanitized user data"
同时qlInjection::Configuration
要实现
....
Configuration() { this = "SqlInjection" }
override int explorationLimit() { result = 50 } // 实现该谓词,表示探索深度
.....
感觉不太好用,也有可能是我不会使用
Go ...隐式数组的问题
func TT(query ...*QuotaQuery) string {
return query[0].Sort
}
func TT1(query *QuotaQuery) string {
return query.Sort
}
数据经过TT
函数后,当前数据流就会中断,而TT1
不会中断, 这是因为 codeql 本身就无法对调用含有可变参数的函数起作用,是本身的一个缺陷。
官方给的解决方案(我不会写,求助的官方,哈哈):
/*
* @Auth Chris Smowton
*/
private predicate isVarargsParam(Parameter p) {
exists(ParameterDecl pd, FuncTypeExpr tp | pd = tp.getAParameterDecl() |
p.getDeclaration() = pd.getANameExpr() and
pd.getTypeExpr() instanceof Ellipsis
)
}
class TaintConfig extends TaintTracking::Configuration {
TaintConfig() { this = "taint-configuration" }
override predicate isAdditionalTaintStep(DataFlow::Node source, DataFlow::Node sink) {
exists(Parameter p, DataFlow::ArgumentNode an, int idx |
an = source and
p = an.getCall().getACallee().getAParameter() and
isVarargsParam(p) and
not an.getCall().hasEllipsis() and
an.argumentOf(_, idx) and
idx >= p.getIndex() and
sink.asParameter() = p
)
}
}
This will do what you want so long as
query
itself is tainted on callingTT
-- it won't work if just theSort
field ofTT
is tainted, because additional steps only apply to "top level" taint.To use this with a
DataFlow::Configuration
instead of aTaintTracking::Configuration
we'd need to target not the varargs parameter itself, but an array-read from it, likesink.(Read).readsElement(p.getARead(), _)
. This would also only work ifquery
is the value we're tracking, not a value stored in one of its members.So these workarounds are not great, but perhaps they will help with your case -- otherwise please bear with us while we implement a higher quality solution!
期待更好吧
参考
保姆级安装教程 https://longlone.top/%E5%AE%89%E5%85%A8/%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6/CodeQL%E4%B8%8Egolang%20sql%E6%B3%A8%E5%85%A5%E6%A3%80%E6%B5%8B/#%E4%BF%9D%E5%A7%86%E7%BA%A7%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B
代码分析平台CodeQL学习手记(一)https://www.4hou.com/posts/yJOW
GitHub Java CodeQL CTF https://sumsec.me/2022/GitHub%20Java%20CodeQL%20CTF.html
CodeQL 提升篇 https://tttang.com/archive/1415/#toc_0x03-partial-flow
原文始发于微信公众号(谁不想当剑仙):CodeQL 学习使用杂记(一)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论