admin管理员组文章数量:1563217
CodeQL环境搭建
参考:https://help.semmle/codeql/codeql-cli/procedures/get-started.html
下载
下载链接:https://github/github/codeql-cli-binaries/releases
下载最新的codeql.zip就可以了,比如:
https://github/github/codeql-cli-binaries/releases/download/v2.1.0/codeql.zip
不要下载下面的Source code
。
搭建本地环境
新建一个codeql目录用来放cli工具,比如$HOME/codeql-home
。
下载CodeQL依赖的库和examples:
https://github/Semmle/ql
重新组织目录
组织完之后是这样的:
77@ubuntu:~/repos/CodeqlHome$ ls codeql-repo/
change-notes CODE_OF_CONDUCT.md CODEOWNERS config CONTRIBUTING.md cpp csharp docs java javascript LICENSE misc python README.md
77@ubuntu:~/repos/CodeqlHome$ ls codeql-cli/
codeql codeql.cmd codeql.exe cpp csharp go java javascript legacy-upgrades LICENSE.md Open-Source-Notices python tools xml
codeQL常用命令
查看codeql执行哪些语言:
codeql resolve languages
根据已有的源码创建codeql工程:
codeql database create CodeQL_java-sec-code --source-root=/home/77/repos/java-sec-code --language=java
codeql自动识别了这个项目是maven的,然后编译了。
指定编译的命令,使用
--command="mvn clean install --file pom.xml"
参考:https://geekmasher.dev/posts/sast/codeql-introduction
如果是查询配置文件中的安全问题,则需要使用分步骤的命令:
codeql database init --source-root=<src> --language java <db>
codeql database trace-command --working-dir=<src> <db> <java command>
codeql database index-files --language xml --include-extension .xml --working-dir=<src> <db>
codeql database index-files --language properties --include-extension .properties--working-dir=<src> <db>
codeql database finalize <db>
参考:https://github/github/codeql/issues/3887
ERROR: Could not detect a suitable build command for the source checkout.
如果碰到这个问题,可能是
由于构造codeql数据库的时候,需要对源代码进行编译,这里应该是需要mvn等编译工具的命令在环境变量中。
然后得到了这样的一个codeql工程目录:
src.zip是源码的压缩包,
codeql命令行参考手册:
https://help.semmle/codeql/codeql-cli/commands.html
从LGTM中获取databases
LGTM已经使用CodeQL分析了上千个项目,可以从LGTM下载这些项目的databases。
一些CodeQL给Java写的示例
参考:https://help.semmle/QL/learn-ql/ql-training.html
//TODO
Windows环境搭建
参考:
https://www.angelwhu/paper/2019/12/30/CodeQL-introduction/#0x00-%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA
先下载VS Code,然后安装CodeQL插件:
下载这个官方准备好的环境:
vscode-codeql-starter:
git clone --recursive https://github/github/vscode-codeql-starter/
然后用VS Code打开:
打开之后,右键:
CodeQL: Run Query
发现不能运行成功,
但是VS帮忙自动下载CLI工具了
当VS帮忙下载完成之后,再点击,由于没有设置代码库(Databases),在弹出的对话框里选择Database即可。
选择java-sec-code之后,要给这个目录生成database:
在此之前需要将之前VS Code帮忙下载的命令行工具(https://github/github/codeql-cli-binaries/releases)的目录加入到PATH中,以便在命令行中直接调用。
C:\repos\codeql-win64\codeql
命令为:
codeql database create java-sec-code --language=java
运行完之后右边就生成了这样一个结果:
选一个点进去看一下,确实是根据ql查询出来的结果:
然后使用这个目录下的:
https://github/Semmle/ql/blob/master/java/ql/src/Security/CWE
有很多CWE的漏洞类型:
CWE类型参考:
https://cwe.mitre/top25/archive/2019/2019_cwe_top25.html
试一下不安全的反序列化(CWE 502)的效果:
这个代码还是比较简单:
具体实现要看一下UnsafeDeserializationSink
:
其实就是找
Java原生反序列化:
java.io.ObjectInputStream#readObject, readUnshared
和XMLDecoder的反序列化:
java.beans.XMLDecoder#readObject
查看一下查询结果:
sink点确实是找到了,source点可能需要改进一下。
CWE-22:路径穿越
参考:
https://cwe.mitre/data/definitions/22.html
CWE-78:OS Command Injection
参考:https://cwe.mitre/data/definitions/78.html
CWE-79:XSS
CWE-89:SQLi
CWE-90:LDAP Injection
CWE-113: HTTP响应拆分
CWE-129: Improper Validation of Array Index
CWE-134: Use of Externally-Controlled Format String
就不一一列举了,codeql里自带的Java的CWE并不多,主要就是SQLi,OS injection,XSS等。
学习struts-CVE-2018-11776的挖掘方法
参考:
https://securitylab.github/research/apache-struts-CVE-2018-11776
就是从以往的Struts2的RCE的source和sink来找可能存在类似漏洞的点。
S2-032 (CVE-2016-3081),
S2-033 (CVE-2016-3687) and
S2-037 (CVE-2016-4438).
这个三个漏洞都是OGNL表达式的RCE,
从methodName
到
OgnlUtil#getValue()
存在漏洞的代码如下:
String methodName = proxy.getMethod(); //<--- untrusted source, but where from?
LOG.debug("Executing action method = {}", methodName);
String timerKey = "invokeAction: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
Object methodResult;
try {
methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action); //<--- RCE
这里的可控变量methodName
是由proxy
对象产生的,它属于com.opensymphony.xwork2.ActionProxy
类型。
然后认为这个类型的getMethod
、getNamespace
、getActionName
方法可能产生用户可控的数据。
于是写出下面这样的QL代码:
class ActionProxyGetMethod extends Method {
ActionProxyGetMethod() {
getDeclaringType().getASupertype*().hasQualifiedName("com.opensymphony.xwork2", "ActionProxy") and
(
hasName("getMethod") or
hasName("getNamespace") or
hasName("getActionName")
)
}
}
predicate isActionProxySource(DataFlow::Node source) {
source.asExpr().(MethodAccess).getMethod() instanceof ActionProxyGetMethod
}
到这里是完成了确定了source。
然后接下来是寻找sink。
按照以往出漏洞的点,先是想到了OgnlUtil#getValue()
,以及TextParseUtil::translateVariables()
,但是这个不是很深入,没有跟进到更底层的方法调用,即他们共同的执行OGNL表达式的方法。
后来觉得
OgnlUtil#compileAndExecute()
和
OgnlUtl#compileAndExecuteMethod
可作为sink。然后写出以下的sink:
predicate isOgnlSink(DataFlow::Node sink) {
exists(MethodAccess ma | ma.getMethod().hasName("compileAndExecute") or ma.getMethod().hasName("compileAndExecuteMethod") |
ma.getMethod().getDeclaringType().getName().matches("OgnlUtil") and
sink.asExpr() = ma.getArgument(0)
)
}
即找出方法调用为compileAndExecute
或者compileAndExecuteMethod
的,且调用该方法的对象为OgnlUtil
(虽然不是全限定名。)
然后需要定义一个DataFlow Configuration
,其实就是继承DataFlow::Configuration
。代码如下:
class OgnlTaintTrackingCfg extends DataFlow::Configuration {
OgnlTaintTrackingCfg() {
this = "mapping"
}
override predicate isSource(DataFlow::Node source) {
isActionProxySource(source)
}
override predicate isSink(DataFlow::Node sink) {
isOgnlSink(sink)
}
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
TaintTracking::localTaintStep(node1, node2) or
exists(Field f, RefType t | node1.asExpr() = f.getAnAssignedValue() and node2.asExpr() = f.getAnAccess() and
node1.asExpr().getEnclosingCallable().getDeclaringType() = t and
node2.asExpr().getEnclosingCallable().getDeclaringType() = t
)
}
}
// 最后是执行查询(好像基本都是这个套路)
from OgnlTaintTrackingCfg cfg, DataFlow::Node source, DataFlow::Node sink
where cfg.hasFlow(source, sink)
select source, sink
反序列化查询学习
比如先用这样的代码找出调用了的java.io.ObjectInputStream#readObject
的地方:
import java
from MethodAccess call, Method readobject
where
call.getMethod() = readobject and
readobject.hasName("readObject") and
readobject.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream")
select call
参考:
https://securitylab.github/research/insecure-deserialization
结果如下:
但是这个结果可能包含了不被用户可控的ois
。
比如这个:
这里的文件路径并不可控。
为了找出那些读取了可控数据源的方法调用,我们就需要使用DataFlow这个库。
这个库包含两个有用的东西:
1、一个类RemoteUserInput
,这个类代表污染的数据的来源,比如从http请求的参数;
2、一个member predicate flowsTo
,则这个变量可以告诉我们数据能否从给定的source流到给定的sink。
接下来我们需要将刚才做的查询封装成一个类:
class UnsafeDeserializationSink extends Expr {
UnsafeDeserializationSink() {
exists(MethodAccess call, Method readobject |
call.getMethod() = readobject and
readobject.hasName("readObject") and
readobject.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream") and
this = call.getQualifier()
)
}
}
代表某种条件的表达式expressions (Expr),在其构造方法中写之前的查询逻辑。
然后可以写出完整的查询:
import java
import semmle.code.java.security.DataFlow
class UnsafeDeserializationSink extends Expr {
UnsafeDeserializationSink() {
exists(MethodAccess call, Method readobject |
call.getMethod() = readobject and
readobject.hasName("readObject") and
readobject.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream") and
this = call.getQualifier()
)
}
}
from RemoteUserInput source, UnsafeDeserializationSink sink
where source.flowsTo(sink)
select source, sink
不过可能时间有点久远了,以上的API有些失效了。
加了一个继承DataFlow::Configuration
的类作为cfg,
source.flowsTo(sink)
->
cfg.hasFlowPath(source, sink)
现在的套路是:
from source, sink, conf
where conf.hasFlowPath(source, sink)
select source, sink
其中这里的conf继承自TaintTracking::Configuration
关于CodeQL的source
标准CodeQL库提供了一个类叫做:RemoteUserInput
,用来表示可能的非可信的数据来源。一般来说,这个类可以较好的用于污点分析,包含多个来自HttpServletRequest
类的source。但是当作者找漏洞的时候,他喜欢扩展这个类,以包含更多的source,
在新的API中,这个叫做:RemoteFlowSource
杂
反序列化的修复方法:
仅在ObjectInputStream上读取基础类型的数据(比如readInt,因为任何类型都是Object的子类,所以不安全)
参考:
https://lgtm/rules/1823453799/
基础教程参考:
https://www.4hou/posts/yJOW
在线的控制台:
https://lgtm/query
选择语言,选择项目,然后执行QL查询即可!
或者直接执行这样的代码也行:
污点追踪
codeql提供了几种数据流的查询:
- local data flow
- local taint data flow
- global data flow
- global taint data flow
local data flow(DataFlow模块下面)基本是用在一个方法中的,比如想要知道一个方法的入参是否可以进入到某一个方法,就可以用local data flow
local taint data flow(TaintTracking模块下面)
global data flow(继承这个类DataFlow::Configuration)是用在整个项目的,比local data flow更强大,但是也更耗时,耗内存,而且没local data flow准确。
global taint data flow(继承这个类TaintTracking::Configuration)
参考:https://www.anquanke/post/id/203674
详细解释参考官方文档:
https://help.semmle/QL/learn-ql/java/dataflow.html
global data flow有这么几个predicates :
- isSource—defines where data may flow from
- isSink—defines where data may flow to
- isBarrier—optional, restricts the data flow
- isAdditionalFlowStep—optional, adds additional flow steps
global taint data flow有这么几个predicates:
- isSource—defines where taint may flow from
- isSink—defines where taint may flow to
- isSanitizer—optional, restricts the taint flow
- isAdditionalTaintStep—optional, adds additional taint steps
exprNode就比如这样的:
就是表达式节点。
下面以一个例子说明source和sink:
source:
sink:
代码为:
import java
import semmle.code.java.dataflow.DataFlow
from Constructor url, Call call, Parameter p, Expr src
where
url.getDeclaringType().hasQualifiedName("java", "URL") and
call.getCallee() = url and
//DataFlow::localFlow(DataFlow::exprNode(src), DataFlow::exprNode(call.getArgument(0)))
DataFlow::localFlow(DataFlow::parameterNode(p),DataFlow::exprNode(call.getArgument(0)))
select p, call
找出传入new java.URL() 的字符串常量:
修改待查询的java代码之后,要删除之前的database,然后重新进行查询。
目前没有比较方便的办法。
//TODO
global data flow 找传入new java.URL()的参数:
代码:
import semmle.code.java.dataflow.DataFlow
/**
找出全局的调用new java.URL()的硬编码字符串
*/
// 定义一个globa data flow
class TestConfiguration extends DataFlow::Configuration{
TestConfiguration(){
this = "TestConfiguration"
}
override predicate isSource(DataFlow::Node source){
source.asExpr() instanceof StringLiteral
}
override predicate isSink(DataFlow::Node sink){
// 存在这样一个方法调用
exists(Call call|
// sink是call的第一个参数
sink.asExpr() = call.getArgument(0) and
// call的调用者(一个构造器)
call.getCallee().(Constructor).getDeclaringType().hasQualifiedName("java", "URL")
)
}
}
// 开始查找
from DataFlow::Node src, DataFlow::Node sink, TestConfiguration config
where config.hasFlow(src, sink)
select src, "This string constructs a URL $@.", sink
完整的“找出所有global data flow从java.lang.System.getenv(…)到java.URL()的构造方法的通路”:
import java
import semmle.code.java.dataflow.DataFlow
// 查找调用java.lang.System.getenv(..)方法的source
class GetenvSource extends MethodAccess {
GetenvSource() {
exists(Method m | m = this.getMethod() |
m.hasName("getenv") and
//m.getDeclaringType() instanceof TypeSystem
m.getDeclaringType().hasQualifiedName("java.lang", "System")
)
}
}
/**
找出全局的调用new java.URL()的硬编码字符串
*/
// 定义一个globa data flow
class TestConfiguration extends DataFlow::Configuration{
TestConfiguration(){
this = "TestConfiguration"
}
override predicate isSource(DataFlow::Node source){
//source.asExpr() instanceof StringLiteral
source.asExpr() instanceof GetenvSource
}
override predicate isSink(DataFlow::Node sink){
// 存在这样一个方法调用
exists(Call call|
// sink是call的第一个参数
sink.asExpr() = call.getArgument(0) and
// call的调用者(一个构造器)
call.getCallee().(Constructor).getDeclaringType().hasQualifiedName("java", "URL")
)
}
}
// 开始查找
from DataFlow::Node src, DataFlow::Node sink, TestConfiguration config
where config.hasFlow(src, sink)
select src, sink
Demo:
来源:
https://help.semmle/QL/learn-ql/java/dataflow.html
具体的codeql中关于Java的方法调用,AST语法之类的详细示例参考:
https://help.semmle/wiki/display/CBJAVA
其他教程:
Java的注解
https://help.semmle/QL/learn-ql/java/annotations.html
Java的泛型(子类、父类、接口、继承)
https://help.semmle/QL/learn-ql/java/types-class-hierarchy.html
Java的表达式和声明
https://help.semmle/QL/learn-ql/java/expressions-statements.html
具体的可以参考这个文档,有详细的AST的说明。
https://help.semmle/QL/learn-ql/java/ast-class-reference.html
空语句;
表达式语句;
if语句;
while语句;
do语句
…
声明
表达式
其中常用到的是:
字面量表达式:
一元表达式:
略
二元表达式:
赋值表达式:
访问:
杂项:
Java的方法调用
https://help.semmle/QL/learn-ql/java/call-graph.html
简单来说,
Callable
是可以被调用的类,比如Method
and Constructor
Call
是调用别人的类。
QL语法教程
参考:
https://help.semmle/QL/ql-handbook/index.html
Predicates
参考:
https://help.semmle/QL/ql-handbook/predicates.html
从返回值来分:分为带结果的,和不带结果的。
三种:
characteristic predicates(构造器?)
member predicates(类的方法?)
non-member predicates(独立的方法?)
参考链接
- CodeQL官方教程
- CodeQL 若干问题思考及 CVE-2019-3560 审计详解
- CodeQL 扩展库开发指北 by RicterZ
- 使用codeql 挖掘 ofcms
- CodeQL API搜索
- how-does-semmle-core-codeql-works
- https://testbnull.medium/how-does-the-semmle-core-works-part-2-75feed1bb390
本文标签: CodeQL
版权声明:本文标题:CodeQL学习 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1727481369a1116870.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论