介绍(解决漏报)
上面一篇文章没有讲到如果存在漏报的情况怎么处理,出现漏报大概率是因为存在一些codeql不能识别的方法或者类等,从而导致整个数据流断掉了
我们的解决方法也很简单,只需要将数据流断掉地方的两个节点强行接在一起就行了,也就是通过isAdditionalTaintStep()
方法,用一张大师傅的生动图
isAdditionalTaintStep
方法也是TaintTracking::Configuration
类中提供的方法,简而言之:如果node1可控,我们强行将node2和node1连接上,那么node2也是可控的。
此次shiro反序列化中,就存在CodeQL不能把cookie
和cookie.getvalue()
连起来,因此我们可以通过isAdditionalTaintStep()
方法告诉污点追踪把这两个节点连起来。
准备源码
https://github.com/apache/shiro/tree/shiro-root-1.2.4
下载即可
创建数据库
先本地试试能不能编译jar成功,大概率是有问题的:
mvn package -DskipTests
如果和我问题一样,可以参考:https://blog.csdn.net/qq_38376348/article/details/108962790
如果上面那个存在其他异常问题,建议给上面的jdk8全都换成jdk7的,最终我成功的版本:
本地可以编译成功了,这个时候再用codeql
来生成数据库(codeql分析必须要求本地能打包成功)
codeql database create shiro_codeql_db --language=java --command="mvn package -DskipTests" --source-root=./shiro-shiro-root-1.2.4/
编写QL规则
导入到VS Code里面,就可以编写QL规则来分析了。
SDK自带source和sink
先直接用自带的QL规则
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.UnsafeDeserializationQuery
import DataFlow::PathGraph
class TestShiro extends TaintTracking::Configuration {
TestShiro() { this = "TestShiro" }
override predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node sink) {
sink instanceof UnsafeDeserializationSink
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, TestShiro ts
where ts.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ flows to here and unsafe deserialization.",
source.getNode(), "User-provided value"
可以看到codeql确实牛,但是这毕竟是别人写好的规则,我们尝试自己来写一下,以便新洞出来后第一时间写好最新的规则去批量。
自写:定位source
输入点就是cookie,那我们只需要找到所有的获取cookie值的地方即可;根据分析是调用了getCookie()
这个方法,所以我们筛选出来这个方法的点即可。
可以先单独写一下规则,看看能不能定位到source
import java
import semmle.code.java.dataflow.FlowSources
from MethodAccess m, DataFlow::Node source
where m.getMethod().getName() = "getCookie" and source.asExpr() = m
select m, source.asExpr()
能定位到再稍加修改重写到isSource()
函数中即可
override predicate isSource(DataFlow::Node source) {
exists(MethodAccess m| m.getMethod().getName() = "getCookie" and source.asExpr() = m)
}
自写:定位sink
和上面一样,还是先测试能不能给执行点定位到
import java
import semmle.code.java.dataflow.FlowSources
from MethodAccess m, DataFlow::Node sink
where m.getMethod().getName() = "readObject" and sink.asExpr() = m
select m, sink.asExpr()
定位到了再改
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess m |
m.getMethod().getName() = "readObject" and sink.asExpr() = m)
}
自写:flow(连接首尾)
就是给source和sink连接起来,最终的代码
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.UnsafeDeserializationQuery
import DataFlow::PathGraph
class TestShiro extends TaintTracking::Configuration {
TestShiro() { this = "TestShiro" }
override predicate isSource(DataFlow::Node source) {
exists(MethodAccess m|
m.getMethod().getName() = "getCookie" and source.asExpr() = m)
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess m |
m.getMethod().getName() = "readObject" and sink.asExpr() = m)
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, TestShiro ts
where ts.hasFlowPath(source, sink)
select source, sink
头尾看着都没问题,但是并没有结果,所以肯定是流中间某个地方出问题了
自写:分析解决问题
根据分析的文章,不断的修改sink
的点,看看到哪一步断的
发现从getCookie
到readValue
中间都是断的
猜测原因是CodeQL不认为this.cookie
和cookie.getValue()
是连续的,所以这两个节点之间是断的,那么也就认为无法形成一条完整的流。
要解决这个问题,我们就需要用到最开始说到的isAdditionalTaintStep()
方法,将两个节点强行连接起来,也就是让this.cookie
和cookie.getValue()
连接起来
所以编写代码如下:
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.UnsafeDeserializationQuery
import DataFlow::PathGraph
/**
* 根据分析的连贯性,定位第一个节点和第二个节点
*/
predicate isCookie(Expr expSrc, Expr expDest) {
exists(MethodAccess ma |
expSrc.getType().toString() = "Cookie" // 第一个节点类型是Cookie
and expDest = ma
and ma.getMethod().getName() = "getValue" // 第二个节点的函数名
and ma.getMethod().getDeclaringType().toString() = "Cookie" // 第二个节点函数的返回类型
)
}
class TestShiro extends TaintTracking::Configuration {
TestShiro() { this = "TestShiro" }
override predicate isSource(DataFlow::Node source) {
exists(MethodAccess m|
m.getMethod().getName() = "getCookie" and source.asExpr() = m)
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess m |
m.getMethod().getName() = "readObject" and sink.asExpr() = m)
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
isCookie(node1.asExpr(), node2.asExpr())
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, TestShiro ts
where ts.hasFlowPath(source, sink)
select source, sink
结果: