CodeQL上手笔记

VSole2021-11-18 14:54:52

前言

在挖了一段时间的漏洞后,逐渐感觉挖洞变成了一个体力活,虽然也使用正则匹配的方式减少了部分工作量,但这种方式还是有很大的缺陷,准确率比较低,因此希望找到一种新的方式来辅助挖洞,最近CodeQL比较火,很多师傅也写了相应的文章,相对来说学习成本已经算比较低了。尽管看了很多师傅的文章,但感觉上自己对原理或者语法的学习还是比较迟钝,因此打算去分析师傅们已经写好的一些query语法,辅助理解。

java-sec-code

第一个Demo来自文章Codeql 入门,师傅以java-sec-code项目为例编写了多个query语句。

查询所有内容为空的方法

import java
from Method m, BlockStmt block
where
  block = m.getBody() and
  block.getNumStmt() = 0
select m

from语句为变量定义,where语句相当于数据库查询中的搜索条件的限制语句,select为查询语句。在QL中,方法称作谓词

Method类型是方法类,表示获取当前项目中所有的方法。getBody谓词返回body体,BlockStmt代表一个语句块。getNumStmt谓词获取块child statements的数量。关于BlockStmt这部分应该是和AST有一些关系。

Local Data Flow分析SPEL

import java
import semmle.code.java.frameworks.spring.SpringController
import semmle.code.java.dataflow.TaintTracking
from Call call,Callable parseExpression,SpringRequestMappingMethod route
where
    call.getCallee() = parseExpression and 
    parseExpression.getDeclaringType().hasQualifiedName("org.springframework.expression", "ExpressionParser") and
    parseExpression.hasName("parseExpression") and 
   TaintTracking::localTaint(DataFlow::parameterNode(route.getARequestParameter()),DataFlow::exprNode(call.getArgument(0))) 
select route.getARequestParameter(),call

本地数据流

本地数据流是单个方法可调用对象中的数据流。本地数据流通常比全局数据流更容易、更快、更精确。

本地数据流库位于模块 DataFlow 中,该模块定义了表示数据可以流经的任何元素的类。Node节点分为表达式节点(ExprNode)和参数节点(ParameterNode)。您可以使用成员谓词 asExpr 和 asParameter在数据流节点和表达式/参数之间进行映射或使用谓词 exprNode 和 parameterNode。

如果存在从节点nodeFrom到节点nodeTo的即时数据流边,则谓词localFlowStep(Node nodeFrom, Node nodeTo)成立。您可以通过使用+和运算符*,或者通过使用定义的递归谓词localFlow(相当于localFlowStep)来递归应用该谓词。

例如,可以在零个或多个本地步骤中找到从参数源到表达式接收器的污点传

播:DataFlow::localFlow(DataFlow::parameterNode(source), DataFlow::exprNode(sink))

本地污点跟踪

本地污点跟踪通过包括非保留值步骤来扩展本地数据流。例如:

String temp = x;
String y = temp + ", " + temp;

如果x是一个污点字符串,那么y也是污点。

本地污染跟踪库位于TaintTracking模块中。像本地数据流一样,如果从nodeFrom

节点到nodeTo节点之间存在直接的污点传播边线,则谓词localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo)成立。您可以使用+和``*运算符,也可以使用预定义的递归谓词localTaint(等效于localTaintStep*)来递归地应用谓词。

所以我们再来看下面的代码,是不是就可以理解了。即使用本地污点跟踪的方式查询从参数节点route.getARequestParameter()到表达式节点call.getArgument(0)的数据流是否成立。

TaintTracking::localTaint(DataFlow::parameterNode(route.getARequestParameter()),DataFlow::exprNode(call.getArgument(0)))

Call和Callable

Callable表示可调用的方法或构造器的集合。

Call表示调用Callable的这个过程(方法调用,构造器调用等等)

那么call.getCallee() = parseExpression就代表获取方法调用为parseExpression

再通过下面的语句对parseExpression进行限制,也就是org.springframework.expression包下的ExpressionParser类的parseExpression方法,我们可以记住这个语句,用到的时候直接套也可以。

parseExpression.getDeclaringType().hasQualifiedName("org.springframework.expression", "ExpressionParser") and
    parseExpression.hasName("parseExpression")

SpringRequestMappingMethod

SpringRequestMappingMethod可以获取所有的Spring Controller的方法。

getARequestParameter 获取请求的参数

getArgument 获取方法调用时的参数

所以再来看下面的代码,意思就是获取所有RequestMapping方法的参数到调用parseExpression方法第一个参数的数据流。

TaintTracking::localTaint(DataFlow::parameterNode(route.getARequestParameter()),DataFlow::exprNode(call.getArgument(0)))

照猫画虎

理解了上面代码的意思,我们完全可以照猫画虎,追踪所有Controller中的命令执行。

import java
import semmle.code.java.frameworks.spring.SpringController
import semmle.code.java.dataflow.TaintTracking
from Call call,Callable parseExpression,SpringRequestMappingMethod route
where
    call.getCallee() = parseExpression and 
    parseExpression.getDeclaringType().hasQualifiedName("java.lang", "Runtime") and
    parseExpression.hasName("exec") and 
   TaintTracking::localTaint(DataFlow::parameterNode(route.getARequestParameter()),DataFlow::exprNode(call.getArgument(0))) 
select route.getARequestParameter(),call

全局数据流

本地数据流虽然分析效率比较高,但是会存在一些遗漏,举个栗子。我想分析SSRF漏洞,假如我找到的Sink是new URL("xx"),但是在下面的Controller中并没有直接调用,而是调用了HttpUtils.URLConnection(url);。而在URLConnection创建了URL对象,那么我使用本地数据流分析是分析不到的,因为他只能在单个方法中分析,跨方法的调用就不行了,这个时候就需要全局数据流。

可以通过继承类DataFlow::Configuration来使用全局数据流库。如下所示:

import semmle.code.java.dataflow.DataFlow
class MyDataFlowConfiguration extends DataFlow::Configuration {
  MyDataFlowConfiguration() { this = "MyDataFlowConfiguration" }
  override predicate isSource(DataFlow::Node source) {
    ...
  }
  override predicate isSink(DataFlow::Node sink) {
    ...
  }
}

可能对QL中的Class比较陌生,Class简单介绍如下。

所以上例中的isSourceisSink都是父类DataFlow::Configuration的非私有谓词。predicate代表当前的谓词没有返回值。下面是关于DataFlow::Configuration谓词的介绍。

isSource-定义数据可能来源

isSink-定义数据可能流向的位置

isBarrier—可选,限制数据流

isAdditionalFlowStep—可选,添加额外的数据流步骤

这里的Source代表输入点,Sink代表执行点,isAdditionalFlowStep它的作用是将一个可控节点A强制传递给另外一个节点B,那么节点B也就成了可控节点。

全局污点追踪

全局污点跟踪是针对全局数据流而言,就像本地污点跟踪是针对本地数据流一样。也就是说,全局污点跟踪通过额外的non-value-preserving步骤扩展了全局数据流。我们可以通过扩展类TaintTracking::Configuration来使用全局污点跟踪库:

import semmle.code.java.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—可选,限制污点流

isAdditionalTaintStep—可选,添加额外污点步骤

这里解释下isSanitizer也就是净化函数,代表污点传播到这里就会被阻断。

下面我们用全局污点追踪分析SSRF漏洞,就可以分析到HttpUtils.URLConnection中的URL请求了。

import semmle.code.java.dataflow.DataFlow
import semmle.code.java.frameworks.spring.SpringController
import semmle.code.java.dataflow.TaintTracking
class Configuration extends DataFlow::Configuration {
  Configuration() {
    this = "Configer"
  }
  override predicate isSource(DataFlow::Node source) {
    exists( SpringRequestMappingMethod route| source.asParameter()=route.getARequestParameter() )
  }
  override predicate isSink(DataFlow::Node sink) {
    exists(Call call ,Callable parseExpression|
      sink.asExpr() = call.getArgument(0) and
      call.getCallee()=parseExpression and 
   parseExpression.getDeclaringType().hasQualifiedName("org.springframework.expression", "ExpressionParser") and
      parseExpression.hasName("parseExpression")
    )
  }
}
from  DataFlow::Node src, DataFlow::Node sink, Configuration config
where config.hasFlow(src, sink)
select src,sink

这里有必要讲下exists语句

它根据内部的子查询返回true or false,来决定筛选出哪些数据。格式为exists(Obj obj| somthing)

Shiro反序列化漏洞

之前有师傅也讲了如何使用CodeQL分析Shiro的反序列化漏洞,我们也学习一下思路,首先根据之前我们对Shiro反序列化漏洞的了解,这个洞还是稍微有点复杂的,所以肯定是要使用全局污点追踪的方法分析的。

数据库构建

首先从github下载shiro的源码并且切换到1.2.4版本。

git clone https://github.com/apache/shiro.git
cd shiro
git checkout 9549384

构建数据库

CodeQL database create shiro1.2.4 --language=java --overwrite --command="mvn clean install -Dmaven.test.skip=true-Dmaven.test.skip=true"

直接构建会有一个错误。

经过查找资料,主要是aspectj依赖包的问题。

aspectjweaver 1.8.9之前的版本不支持JDK1.8, aspectjweaver 1.8.9是在使用JDK1.8时的最低版本。

所以对于此有两种方法进行解决:

   一:降低JDK的版本,如果aspectjweaver的版本是1.8.9之前的,那么可以使用JDK1.7

   二:升级aspectjweaver的版本, 如果aspectjweaver的版本是1.8.9之前的,那么可以使用1.8.9来解决这个问题。

虽然说是这么说,但是我改了pom.xml里的版本后并没有解决问题,切换JDK版本为1.7可以顺利构建成功。

导入数据库后就可以通过下面的查询语句分析啦

代码分析

import java
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
predicate isCookiegetValue(Expr expSrc, Expr expDest) {
exists(Method method, MethodAccess call|
expSrc.getType().toString()="Cookie" and
expDest=call and
call.getMethod() = method and
method.hasName("getValue") and
method.getDeclaringType().toString() = "Cookie"
)
}
predicate isReadObject(Expr expSrc, Expr expDest) {
exists(Method method, MethodAccess call|
expSrc.getType().toString()="ObjectInputStream" and
expDest=call and
call.getMethod() = method and
method.hasName("readObject") and
method.getDeclaringType().toString() = "ObjectInputStream"
)
}
predicate isBase64(Expr expSrc, Expr expDest) {
exists(Method method, MethodAccess call|
expSrc.getType().toString()="String" and
expDest=call and
call.getMethod() = method and
method.hasName("decode") and
method.getDeclaringType().toString() = "Base64"
)
}
predicate isdecrypt(Expr expSrc, Expr expDest) {
exists(Method method, MethodAccess call|
expSrc.getType().toString()="byte" and
expDest=call and
call.getArgument(0)=expSrc and
call.getMethod() = method and
method.hasName("decrypt") and
method.getDeclaringType().toString() = "CipherService"
)
}
class VulConfig extends TaintTracking::Configuration {
VulConfig() { this = "shiroConfig" }
override predicate isSource(DataFlow::Node source) {
exists(MethodAccess call |
call.getMethod().getName()="getCookies" and
source.asExpr()=call
)
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess call |
call.getMethod().getName()="readObject" and
sink.asExpr()=call
)
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
isCookiegetValue(node1.asExpr(), node2.asExpr()) or
isReadObject(node1.asExpr(), node2.asExpr()) or
isBase64(node1.asExpr(), node2.asExpr()) or
isdecrypt(node1.asExpr(), node2.asExpr())
}
}
from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "source are"

但是还有一个坑,直接使用上述代码是切换不到alert中查看污点传播路径的,需要在开始加上下面的内容。

/**
 * @kind path-problem
 */

上面的内容虽然可以出现结果,但是好像比较麻烦,我们可以直接将readValue的返回值当作Source,将readObject当作Sink。

/**
 * @kind path-problem
 */
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
class VulConfig extends TaintTracking::Configuration {
    VulConfig() { this = "shiroConfig" }

    override predicate isSource(DataFlow::Node source) {
    exists(MethodAccess call |
    call.getMethod().getName()="readValue" and
    source.asExpr()=call
    )
    }

    override predicate isSink(DataFlow::Node sink) {
    exists(MethodAccess call |
    call.getMethod().getName()="readObject" and
    sink.asExpr()=call
    )
    }
    }

    from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
    where config.hasFlowPath(source, sink)
    select sink.getNode(), source, sink, "source are"

如果将getCookie的返回值当作Source并不能获取结果,所以我们要将getCookiereadValue链接起来。这里用到了isAdditionalTaintStep谓词。

predicate isCookiegetValue(Expr expSrc, Expr expDest) {
    exists(Method method, MethodAccess call|
    expSrc.getType().toString()="Cookie" and
    expDest=call and
    call.getMethod() = method and
    method.hasName("readValue") and
    method.getDeclaringType().toString() = "Cookie"
    )
    }
    override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
        isCookiegetValue(node1.asExpr(), node2.asExpr()) 
        }

主要分析下exists中的语句,

  • expSrc.getType().toString()="Cookie"主要是获取第一个表达式的类型为Cookie的。

  • expDest=call第二个表达式为方法调用,后面是对调用的限制

  • call.getMethod() = method and method.hasName("readValue")限制调用的方法为readValue

  • method.getDeclaringType().toString() = "Cookie"限制方法类型为Cookie。也就是调用Cookie类里的readValue方法。

上面代码实现的功能是将所有返回类型为Cookie的表达式和readValue表达式连接起来。但是假如我只想把getCookie表达式和readValue表达式链接起来呢?

再次照猫画虎

predicate isCookiegetValue(Expr expSrc, Expr expDest) {
    exists(Method method,Method methodsrc, MethodAccess call,MethodAccess callsrc|
        expSrc=callsrc and callsrc.getMethod() = methodsrc and methodsrc.hasName("getCookie")  and methodsrc.getDeclaringType().toString() = "CookieRememberMeManager" and
    expDest=call and
    call.getMethod() = method and
    method.hasName("readValue") and
    method.getDeclaringType().toString() = "Cookie"
    )
    }

apache kylin命令执行漏洞

这个项目比较大,我们就不自己构建数据库了,可以从https://lgtm.com/projects/g/apache/kylin/直接下载现成的数据库

甚至可以直接在lgtm.com中直接编写查询语句。

这个漏洞的Source是SpringMapping中获取的,Sink是ProcessBuilder,编写查询语句也比较简单

/**
 * @kind path-problem
 */
import semmle.code.java.frameworks.spring.SpringController
import semmle.code.java.dataflow.TaintTracking
import DataFlow::PathGraph
import semmle.code.java.dataflow.FlowSources
class Configuration extends TaintTracking::Configuration {
  Configuration() {
    this = "Configer"
  }
  override predicate isSource(DataFlow::Node source) {
    exists( SpringRequestMappingMethod route| source.asParameter()=route.getARequestParameter() )
  }
  override predicate isSink(DataFlow::Node sink) {
    exists(Call call ,Callable parseExpression|
        sink.asExpr() = call.getArgument(0) and
        call.getCallee()=parseExpression and 
     parseExpression.getDeclaringType().hasQualifiedName("java.lang", "ProcessBuilder") and
        parseExpression.hasName("ProcessBuilder")
      )
  }
}
from  DataFlow::PathNode src, DataFlow::PathNode sink, Configuration config
where config.hasFlowPath(src, sink)
select sink.getNode(), src, sink, "source are"

除了上面的方式获取source还可以使用下面的方式。

复制override predicate isSource(DataFlow::Node source) {
    source instanceof RemoteFlowSource 
  }

RemoteFlowSource类内置了主流的获取参数的方式,因此也可以使用这种方式获取source。

总结

通过上面的学习,已经可以编写一些简单的查询语句分析了,CodeQL上手虽然并不复杂,而且比较高效,而且官方也对很多漏洞提供了相应的查询语法,用来分析一些有源码的项目还是可以的,但是似乎目前不能分析jar包里的代码,目前也没有比较好的解决方法。

参考

  • CodeQL从0到1(内附Shiro检测demo)

  • Codeql 入门
  • 浅谈利用codeql进行java代码审计分析(1)

  • CodeQL从入门到放弃
cookienode
本作品采用《CC 协议》,转载必须注明作者和本文链接
Netskope 的研究人员正在跟踪一个使用恶意 Python 脚本窃取 Facebook 用户凭据与浏览器数据的攻击行动。攻击针对 Facebook 企业账户,包含虚假 Facebook 消息并带有恶意文件。攻击的受害者主要集中在南欧与北美,以制造业和技术服务行业为主。
Netskope 的研究人员正在跟踪一个使用恶意 Python 脚本窃取 Facebook 用户凭据与浏览器数据的攻击行动。攻击针对 Facebook 企业账户,包含虚假 Facebook 消息并带有恶意文件。攻击的受害者主要集中在南欧与北美,以制造业和技术服务行业为主。
Ducktail 窃取恶意软件背后的越南威胁行为者与 2023 年 3 月至 10 月初开展的一项新活动有关,该活动针对印度的营销专业人士,旨在劫持 Facebook 企业帐户。
Bleeping Computer 网站披露,某黑客组织通过一个伪造和受损的 Facebook 账户网络,发送数百万条 Messenger 钓鱼信息,利用密码窃取恶意软件攻击 Facebook 企业账户。
jsproxy功能特点服务端开销低传统在线代理几乎都是在服务端替换 HTML/JS/CSS 等资源中的 URL。它能让 JS 拦截网页产生的请求,并能自定义返回内容,相当于在浏览器内部实现一个反向代理。API 虚拟化传统在线代理大多只针对静态 URL 的替换,忽视了动态 URL 以及和 URL 相关的网页 API。这可能导致某些业务逻辑出现问题。同时得益于 nginx 丰富的功能,很多常用需求无需重新造轮子,通过简单配置即可实现。并且无论性能还是稳定性,都远高于自己实现。
浅谈DNS-rebinding
2023-04-13 09:34:13
在这种攻击中,恶意网页会导致访问者运行客户端脚本,攻击网络上其他地方的计算机。同源策略对Web应用程序具有特殊意义,因为Web应用程序广泛依赖于HTTP cookie来维持用户会话,所以必须将不相关网站严格分隔,以防止丢失数据泄露。值得注意的是同源策略仅适用于脚本,这意味着某网站可以通过相应的HTML标签访问不同来源网站上的图像、CSS和动态加载 脚本等资源。即TTL的数值越小,修改记录后所受的影响生效越快。等恶意脚本响应受害者。
docker run -it -d -p 13443:3443 -p 8834:8834 leishianquan/awvs-nessus:v1. 如XSS,XSRF,sql注入,代码执行,命令执行,越权访问,目录读取,任意文件读取,下载,文件包含,远程命令执行,弱口令,上传,编辑器漏洞,暴力破解等验证码与邮箱以及token的返回泄露,以及后台为校验从而可删除的参数。从某个成功请求中捕获数据包观察cookie或者token是否存在规律或加密。token的key参数解密构建获取真实user密钥,可拼接、规律、时间戳……winodws桌面:TeamViewerQS单文件windows下载文件;certutil -urlcache -split -f?
Security Joes在分析了来自去年9月份发生的一起事件的数据后,抢在这伙黑客攻陷目标之前成功应对了另外三起攻击。研究人员已经发布了一份技术报告,描述了这伙威胁分子的作案手法及其后门的工作机理。YARA规则也已发布,以帮助组织检测恶意软件。
在分析了 9 月份发生的一起事件的数据后,Security Joes 能够在黑客攻破目标之前对其他三起攻击做出响应。研究人员表示,他们能找到的 IceBreaker 威胁演员的唯一公开证据是10 月份来自 MalwareHunterTeam的一条推文。研究人员发布了一份技术报告,描述了威胁行为者的作案手法及其后门的工作原理。YARA 规则也已发布,以帮助组织检测恶意软件。此外,Security Joes 建议怀疑 IceBreaker 存在漏洞的公司查找在启动文件夹中创建的快捷方式文件,并检查开源工具 tsocks.exe 是否未经授权执行。
一种新的信息窃取恶意软件正在迅速蔓延,因为其背后的开发人员继续添加功能并最近在 GitHub 上发布了源代码。
VSole
网络安全专家