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"
语法规则 from用来定义变量,where是判断规则,select是输出
定义类/方法 类
1 2 3 4 5 6 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 class HttpServletResponse extends RefType { HttpServletResponse() { hasQualifiedName("javax.servlet.http", "HttpServletResponse") } } class ResponseAddCookieMethod extends Method { ResponseAddCookieMethod() { getDeclaringType() instanceof HttpServletResponse and hasName("addCookie") } } / / = = = = = = = = = = = = = = = = = = = = = = = = class TypeString extends Class { TypeString() { this.hasQualifiedName("java.lang", "String") } } library class HttpServletRequestGetHeaderMethod extends Method { HttpServletRequestGetHeaderMethod() { getDeclaringType() instanceof HttpServletRequest and hasName("getHeader") and getNumberOfParameters() = 1 and getParameter(0 ).getType() instanceof TypeString } }
查询类与方法 我们的类库实际上就是AST的对应关系。怎么理解呢?
我们经常会用到的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 rlsselect 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 import java import semmle.code.xml.MavenPom predicate getCompileDependency(PomDependency dependency) { dependency.getScope() = "compile" or dependency.getScope() = "runtime" } from PomDependency dependencywhere 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 import java class TypeAbstractRequestMatcherRegistry extends Class { TypeAbstractRequestMatcherRegistry() { this.hasQualifiedName("com.xiaomi.miui.lockServer.utils", "HttpUtils") } } class AnyRequestCall extends MethodAccess { AnyRequestCall() { getMethod().hasName("getRemoteUserIP") and getMethod().getDeclaringType() instanceof TypeAbstractRequestMatcherRegistry } } from Call c, Callable calleewhere callee = c.getCallee() and callee.getAReference() instanceof AnyRequestCallselect 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 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 confwhere 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