目录
简介
codeql是一门类似SQL的查询语言,通过对项目源码(C/C++、C#、golang、java、JavaScript、typescript、python)进行完整编译,并在此过程中把项目源码文件的所有相关信息(调用关系、语法语义、语法树)存在数据库中,然后编写代码查询该数据库来发现安全漏洞(硬编码/XSS等)
hello world
1、安装codeql-cli 和 ql库
codeql-cli是执行文件,ql库是已经写好的可以查询安全漏洞的代码
下载地址:
codeql-cli:https://github.com/github/codeql-cli-binaries/releases
ql库:https://github.com/github/codeql/releases
两个下载的zip文件务必解压在同一目录(codeql-home)下(因为codeql-cli把同层目录作为查找路径),并将codeql-cli放在系统目录中(Linux添加export PATH=$PATH:/home/f/.local/bin:/mnt/c/Users/xx/codeql-home/codeql-cli到.bashrc文件末尾)
2、创建源码数据库
首先clone项目源码:git clone https://github.com/XiaoMi/galaxy-sdk-java.git
然后创建源码数据库:codeql database create source_database_name --language=java
3、编写hello world
下载的ql库是codeql代码,有包结构/目录结构要求(qlpack.yml定义一个package),才能正常编译、执行。
java语言的漏洞查询代码目录在qllib/java/ql/src/Security/CWE,这里新建test.ql(qllib/java/ql/src/Security/test/test.ql)查询源码,内容:
/**
* @name sectest2
* @description Writing sectest33
* @kind problem
* @problem.severity error
* @precision high
* @id java/sectest
* @tags security
* external/cwe/cwe-113
*/
import java
from Parameter p
where not exists(p.getAnAccess())
select p, "no used param"
test.ql用来查询函数参数未使用的函数
4、执行查询
执行了查询代码之后,输出的内容即有漏洞的项目源代码
命令行执行:codeql database analyze source_database_name qllib/java/ql/src/Security/test/test.ql --format=csv --output=java-results.csv
查询发现的漏洞会输出到java-results.csv文件
进阶
1、扫描结果快速定位到源码
命令行方式执行查询,多少有些不便,使用vscode codeql插件则方便的多
安装完插件,需要在插件中指定待查询的源码库(第2步产生的文件夹),
然后在vscode中打开test.ql,右键运行查询,结果会自动打开
2、用作白盒扫描器
ql库集成了许多常见的安全漏洞(参考:https://codeql.github.com/codeql-query-help/java/),可以拿来扫描项目源码
codeql analyze命令可以执行单个ql文件,目录下所有ql文件,和查询suite(.qls)
白盒扫描使用如下命令(执行所有漏洞类查询)
codeql database analyze source_database_name qllib/java/ql/src/codeql-suites/java-security-extended.qls --format=csv --output=java-results.csv
3、开发自己的查询代码
ql入门:https://codeql.github.com/docs/writing-codeql-queries/introduction-to-ql/
目前codeql文档较少,只能照已有的规则进行模仿
qll文件是库文件,有很多函数可以直接import使用;qhelp是帮助说明文件;
ql文件(qllib/java/ql/src/Security/CWE/CWE-614/InsecureCookie.ql)如下:
注释部分是meta信息(都有重要作用,比如tags可以用来归属规则suites),
import同其他编程语言类似,可以导入现成的函数使用(qllib/java/ql/src/semmle/code/java)
from用来定义变量,where是判断规则,select是输出
/**
* @name Failure to use secure cookies
* @description Insecure cookies may be sent in cleartext, which makes them vulnerable to
* interception.
* @kind problem
* @problem.severity error
* @precision high
* @id java/insecure-cookie
* @tags security
* external/cwe/cwe-614
*/
import java
import semmle.code.java.frameworks.Servlets
from MethodAccess add
where
add.getMethod() instanceof ResponseAddCookieMethod and
not exists(Variable cookie, MethodAccess m |
add.getArgument(0) = cookie.getAnAccess() and
m.getMethod().getName() = "setSecure" and
m.getArgument(0).(BooleanLiteral).getBooleanValue() = true and
m.getQualifier() = cookie.getAnAccess()
)
select add, "Cookie is added to response without the 'secure' flag being set."
有两类查询alert和path,从ql文件中的@kind problem/path-problem分辨,alert查询select返回两个字段(qllib/java/ql/src/Security/CWE/CWE-614/InsecureCookie.ql,select element, string),path查询(污点查找)select返回4个字段(qllib/java/ql/src/Security/CWE/CWE-079/XSS.ql,select element, source, sink, string),path查询带有调用链
4、分析GitHub上的开源项目
直接搜索即可
https://lgtm.com/search?q=xiaomi
该开源项目存在xss漏洞:https://github.com/XiaoMi/minos/issues/43
5、语法规则学习
①定义类/方法
类
/**
* A class that has `javax.servlet.Servlet` as an ancestor.
*/
class ServletClass extends Class {
ServletClass() { getAnAncestor().hasQualifiedName("javax.servlet", "Servlet") }
}
方法
/**
* 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
}
}
②查询所有外部输入
import java
import semmle.code.java.dataflow.FlowSources
from RemoteFlowSource rls
select rls, "t:"+rls.getSourceType()
③获取所有外部依赖
/**
* @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()
④获取某方法的所有调用
/**
* 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()
⑤获取某方法的调用链
/**
* @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"
⑥判断参数值
MethodAccess ma | ma.getArgument(1).(StringLiteral).getValue() = "Nonex"
⑦嵌套类定义
class JaxRsResponseBuilder2 extends Class {
JaxRsResponseBuilder2() { this.hasQualifiedName("javax.ws.rs.core", "Response$ResponseBuilder") }
}
codeql安全能力
java
1、zip slip(zip解压覆盖任意文件)
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-022/ZipSlip.ql
2、命令注入
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.ql
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-078/ExecTainted.ql
3、cookie安全
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-614/InsecureCookie.ql
4、XSS
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-079/XSS.ql
5、依赖漏洞
6、反序列化
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.ql
7、http头注入
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.ql
8、url跳转
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-601/UrlRedirect.ql
9、ldap注入
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-090/LdapInjection.ql
10、sql注入
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-089/SqlTainted.ql
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-089/SqlUnescaped.ql
11、file权限&目录注入
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql
12、xml注入
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-611/XXE.ql
13、SSL校验
14、弱加密
https://github.com/github/codeql/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql
15、随机数种子可预测
https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-335/PredictableSeed.ql
实战
宽泛的跨域配置
查看结果 https://lgtm.com/query/7135375648193136280/
代码:
/**
* @name access-control-allow-origin
* @description set access-control-allow-origin with *
* @kind problem
* @problem.severity error
* @precision high
* @id java/corserror
* @tags security
* external/cwe/cwe-113
*/
import java
import semmle.code.java.dataflow.FlowSources
class JaxRsResponseBuilder2 extends Class {
JaxRsResponseBuilder2() { this.hasQualifiedName("javax.ws.rs.core", "Response$ResponseBuilder") }
}
class JaxRsResponseBuilderMethod extends Method {
JaxRsResponseBuilderMethod() {
getDeclaringType() instanceof JaxRsResponseBuilder2 and
hasName("header")
}
}
predicate broadCors(MethodAccess ma){
exists(JaxRsResponseBuilderMethod m|
ma.getMethod() = m and ma.getArgument(0).(StringLiteral).getValue().toLowerCase() = "access-control-allow-origin" and ma.getArgument(1).(StringLiteral).getValue() = "*"
)
}
predicate broadCors2(MethodAccess ma){
exists(ResponseSetHeaderMethod m|
ma.getMethod() = m and ma.getArgument(0).(StringLiteral).getValue().toLowerCase() = "access-control-allow-origin" and ma.getArgument(1).(StringLiteral).getValue() = "*"
)
}
predicate broadCors3(MethodAccess ma){
exists(ResponseAddHeaderMethod m|
ma.getMethod() = m and ma.getArgument(0).(StringLiteral).getValue().toLowerCase() = "access-control-allow-origin" and ma.getArgument(1).(StringLiteral).getValue() = "*"
)
}
from MethodAccess ma
where broadCors(ma) or broadCors2(ma) or broadCors3(ma)
select ma, "CORS vulnerability due to this"+" "+ma.getCaller()