CodeQL语法
ssooking Lv5

QL基本语法

QL是一种逻辑编程语言,与SQL数据库查询语言类似。它涉及各种逻辑公式,因此会用到常见的逻辑连接词(and、or和not)、量词(forall和exists)以及其他重要的逻辑概念,如谓词等。QL还支持递归聚合,这使得我们可以通过简单的QL语法来编写复杂的递归查询。

编写QL

ql库符合包结构/目录结构要求(通过qlpack.yml定义),才能正常编译、执行。执行查询时,CodeQL会扫描qlpack.yml文件,文件中的元数据告诉CodeQL如何编译查询、包依赖于哪些库、以及在哪里可以找到查询套件定义。详细说明可参考About QL packs

如果想要创建自己的查询文件,我们必须在文件夹中创建一个qlpack.yml文件。最简单的qlpack.yml内容如下:

1
2
3
4
name: java-sec-code-query
version: 0.0.0
libraryPathDependencies: codeql/java-all
suites: my-custom-suites

libraryPathDependencies:该QL包所依赖的任何QL包的名称作为一个序列。这使pack可以访问依赖项中定义的任何库,数据库架构和查询套件。

因为我们现在是针对java的查询,添加codeql/java-all即可。

2.在QL库中创建自定义QL程序

java语言的漏洞查询代码目录在qllib/java/ql/src/Security/CWE,我们也可以直接这里新建ql文件,这样就无需创建qlpack.yml

https://github.com/github/vscode-codeql-starter/

为了方便起见,测试时也可以使用LGTM的在线查询控制台来执行我们的查询,详情可参考:Using the query console

hello world

编写一个最简单的查询:通过select子句返回字符串hello world

1
2
3
import java

select "hello world"
  • import java表示这里查询的代码是java语言;

  • select "hello world"表示返回字符串hello world

语法规则

from用来定义变量,where是判断规则,select是输出

定义类/方法

1
2
3
4
5
6
/**
* A class that has `javax.servlet.Servlet` as an ancestor.
*/
class ServletClass extends Class {
ServletClass() { getAnAncestor().hasQualifiedName("javax.servlet", "Servlet") }
}

方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* The interface `javax.servlet.http.HttpServletResponse`.
*/
class HttpServletResponse extends RefType {
HttpServletResponse() { hasQualifiedName("javax.servlet.http", "HttpServletResponse") }
}

/**
* The method `addCookie(Cookie)` declared in `javax.servlet.http.HttpServletResponse`.
*/
class ResponseAddCookieMethod extends Method {
ResponseAddCookieMethod() {
getDeclaringType() instanceof HttpServletResponse and
hasName("addCookie")
}
}

//========================

/** The class `java.lang.String`. */
class TypeString extends Class {
TypeString() { this.hasQualifiedName("java.lang", "String") }
}
/**
* The method `getHeader(String)` declared in `javax.servlet.http.HttpServletRequest`.
*/
library class HttpServletRequestGetHeaderMethod extends Method {
HttpServletRequestGetHeaderMethod() {
getDeclaringType() instanceof HttpServletRequest and
hasName("getHeader") and
getNumberOfParameters() = 1 and
getParameter(0).getType() instanceof TypeString
}
}

查询类与方法

我们的类库实际上就是AST的对应关系。怎么理解呢?

  • 比如说我们想获得所有的类当中的方法,在AST里面Method代表的就是类当中的方法;

  • 比如说我们想过的所有的方法调用,MethodAccess获取的就是所有的方法调用。

我们经常会用到的ql类库大体如下:

名称 解释
Method 方法类,Method method表示获取当前项目中所有的方法
MethodAccess 方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用
Parameter 参数类,Parameter表示获取当前项目当中所有的参数

结合ql语法,我们尝试获取项目当中定义的所有方法:

1
2
3
4
import java

from Method method
select method

我们再通过Method类内置的一些方法,把结果过滤一下。比如我们获取名字为getStudent的方法名称。

1
2
3
4
5
import java

from Method method
where method.hasName("getStudent")
select method.getName(), method.getDeclaringType()

method.getName() 获取的是当前方法的名称

method.getDeclaringType()获取的是当前方法所属class的名称。

谓词

和SQL一样,where部分的查询条件如果过长,会显得很乱。CodeQL提供一种机制可以让你把很长的查询语句封装成函数。这个函数,就叫谓词。

比如上面的案例,我们可以写成如下,获得的结果跟上面是一样的:

1
2
3
4
5
6
7
8
9
import java

predicate isStudent(Method method) {
exists(|method.hasName("getStudent"))
}

from Method method
where isStudent(method)
select method.getName(), method.getDeclaringType()

语法解释:

predicate 表示当前方法没有返回值。

exists子查询,是CodeQL谓词语法里非常常见的语法结构,它根据内部的子查询返回true or false,来决定筛选出哪些数据。

查询所有外部输入

1
2
3
4
5
import java
import semmle.code.java.dataflow.FlowSources

from RemoteFlowSource rls
select rls, "t:"+rls.getSourceType()

查询所有外部依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**

* @name dependency version
* @description version with vulnerabilities
* @kind problem
* @problem.severity error
* @precision high
* @id java/dependency-version
* @tags security
* external/cwe/cwe-113
*/

import java
import semmle.code.xml.MavenPom

predicate getCompileDependency(PomDependency dependency) {
dependency.getScope() = "compile" or dependency.getScope() = "runtime"
}

from PomDependency dependency
where getCompileDependency(dependency)
select dependency,
"dependency: " + dependency.getShortCoordinate() + ":"+ dependency.getVersionString()

查询某方法的所有调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**

* @name dependency version
* @description version with vulnerabilities
* @kind problem
* @problem.severity error
* @precision high
* @id java/dependency-version
* @tags security
* external/cwe/cwe-113
*/

import java

/**
* The class `com.xiaomi.miui.lockServer.utils. HttpUtils `.
*/

class TypeAbstractRequestMatcherRegistry extends Class {
TypeAbstractRequestMatcherRegistry() {
this.hasQualifiedName("com.xiaomi.miui.lockServer.utils",
"HttpUtils")
}
}

/** A call to `HttpUtils. getRemoteUserIP ` method. */
class AnyRequestCall extends MethodAccess {
AnyRequestCall() {
getMethod().hasName("getRemoteUserIP") and
getMethod().getDeclaringType() instanceof TypeAbstractRequestMatcherRegistry
}
}

from Call c, Callable callee
where callee = c.getCallee() and callee.getAReference() instanceof AnyRequestCall
select c, "t:"+c.getQualifier()+" "+c.getCallee()

// //另外一种简便方式
// from Call c
// where c.getQualifier().toString() = "httpUtils" and c.getCallee().toString() = "getRemoteUserIP"
// select c, "t:"+c.getQualifier()+c.getCallee()ency.getVersionString()

获取某方法的调用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* @name sectest2
* @description Writing sectest33
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/sectest
* @tags security
* external/cwe/cwe-113
*/

import java
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph

class GetOrderIdByIMEIMethod extends Method {
GetOrderIdByIMEIMethod() {
getDeclaringType().hasQualifiedName("com.xiaomi.miui.lockServer.utils", "HttpUtils") and
hasName("getRemoteUserIP")
}
}

//ql自动调用实现类中的方法
abstract class HeaderSplittingSink extends DataFlow::Node { }

//sink为目标函数的参数
class OrderHeaderSplittingSink extends HeaderSplittingSink{
OrderHeaderSplittingSink(){
exists(GetOrderIdByIMEIMethod m, MethodAccess ma |
ma.getMethod() = m and
this.asExpr() = ma.getArgument(0)
)
}
}

class ResponseSplittingConfig extends TaintTracking::Configuration {
ResponseSplittingConfig() { this = "ResponseSplittingConfig" }
override predicate isSource(DataFlow::Node source) {
//source instanceof RemoteFlowSource
exists(source.asExpr())
}

override predicate isSink(DataFlow::Node sink) { sink instanceof HeaderSplittingSink }
}

from DataFlow::PathNode source, DataFlow::PathNode sink, ResponseSplittingConfig conf

where conf.hasFlowPath(source, sink)

select sink.getNode(), source, sink, "vulnerability due to this $@.",
source.getNode(), "user-provided value"

Source和Sink

https://www.buaq.net/go-82569.html

https://www.jianshu.com/p/338d14e723c0

Reference

CodeQL编写指南:

https://www.buaq.net/go-82569.html

https://codeantenna.com/a/fnmZS3Qg4F

https://cloud.tencent.com/developer/article/1645870

  • Post title:CodeQL语法
  • Post author:ssooking
  • Create time:2022-01-05 14:23:00
  • Post link:https://ssooking.github.io/2022/01/codeql语法/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.