tabby从入门到新链挖掘
字数 4282 2025-09-23 19:27:46
Tabby从入门到新链挖掘教学文档
一、前言
Tabby是一款基于代码属性图(CPG)和Neo4j图数据库的静态代码分析工具,主要用于Java反序列化利用链(Gadget Chain)的挖掘与分析。本文档系统介绍Tabby的安装、基础语法、实战老链复现与新链挖掘方法。
二、Tabby安装
推荐参考详细安装教程:
👉 Tabby安装指南
安装步骤概要:
- 安装JDK 11+、Maven、Neo4j 4.x。
- 克隆Tabby项目并编译。
- 配置Neo4j数据库并导入CPG数据。
- 启动Neo4j并访问Web界面进行查询。
三、语法学习
1. 基础语法
模式匹配与节点匹配
- 使用
MATCH进行模式匹配(类似SQL的SELECT)。 - 节点类型:
(c:Class):类节点(m:Method):方法节点(f:Field):字段节点
示例:
MATCH (m:Method {NAME:"exec"})
等价于:
MATCH (m:Method) WHERE m.NAME = "exec"
节点属性
| 字段名 | 说明 |
|---|---|
SIGNATURE |
方法签名(类全名+方法名+参数类型) |
NAME |
方法名(如readObject) |
CLASSNAME |
类全限定名(如java.util.HashMap) |
IS_SINK |
是否为危险方法(Sink) |
IS_SOURCE |
是否为入口方法(Source) |
INTERFACES |
实现的接口列表 |
SUPERCLASS |
父类全限定名 |
字符串匹配操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
= |
精确匹配 | m.NAME = "readObject" |
CONTAINS |
包含子串 | m.CLASSNAME CONTAINS "Map" |
STARTS WITH |
前缀匹配 | m.NAME STARTS WITH "get" |
ENDS WITH |
后缀匹配 | m.CLASSNAME ENDS WITH "Impl" |
=~ |
正则匹配 | m.NAME =~ "read.*" |
IN |
集合匹配 | m.NAME IN ["readObject", "toString"] |
2. 关系匹配
基本关系类型
| 关系类型 | 说明 |
|---|---|
:CALL |
方法调用关系 |
:ALIAS |
别名关系(同一对象引用) |
:EXTENDS |
类继承关系 |
:HAS |
类包含字段或方法 |
:INTERFACE |
接口实现关系 |
语法示例
- 单向调用关系:
(source:Method)-[:CALL]->(sink:Method) - 双向关系(不指定方向):
(a)-[:CALL]-(b) - 多关系类型与可变路径长度:
(a)-[:CALL|ALIAS*1..4]->(b)
3. 函数语法
APOC插件常用函数
| 函数名 | 说明 | 示例 |
|---|---|---|
apoc.algo.allSimplePaths |
查找所有简单路径 | CALL apoc.algo.allSimplePaths(a, b, "CALL", 5) |
apoc.path.expand |
自定义遍历路径 | CALL apoc.path.expand(a, "CALL>", null, 0, 5) |
apoc.path.subgraphAll |
获取子图(所有节点和关系) | CALL apoc.path.subgraphAll(a, {maxLevel:3}) |
apoc.path.spanningTree |
最小生成树遍历 | CALL apoc.path.spanningTree(a, {maxLevel:5}) |
Tabby内置函数
| 函数名 | 参数说明 | 返回值 |
|---|---|---|
tabby.algo.findPath |
source, direct, sink, maxNodeLength, isDepthFirst |
path, weight |
tabby.algo.findPathWithState |
同上,增加sinkState(污点条件) |
path, weight |
tabby.algo.findJavaGadget |
同上 | path, weight |
tabby.algo.findJavaGadgetWithState |
同上,增加sinkState |
path, weight |
四、老链练手(以Commons Collections为例)
1. 找到目标方法(Sink)
常见终点方法:
TiedMapEntry#toString/hashCode/equalsTransformedMap#transformInvokerTransformer#transform
2. 使用Tabby进行查询
方法一:逐节点查找(可靠但繁琐)
示例:查找BadAttributeValueExpException#readObject → TiedMapEntry#toString
MATCH (source:Method {NAME:"readObject", CLASSNAME:"javax.management.BadAttributeValueExpException"})
MATCH (sink:Method {NAME:"toString", CLASSNAME:"org.apache.commons.collections.keyvalue.TiedMapEntry"})
CALL tabby.algo.findJavaGadget(source, true, sink, 10, true) YIELD path RETURN path
方法二:直接起点到终点(高效但可能含噪声)
示例:查找HotSwappableTargetSource → TiedMapEntry#equals
MATCH (source:Method {CLASSNAME:"org.apache.commons.proxy.HotSwappableTargetSource"})
MATCH (sink:Method {NAME:"equals", CLASSNAME:"org.apache.commons.collections.keyvalue.TiedMapEntry"})
CALL tabby.algo.findJavaGadget(source, true, sink, 10, true) YIELD path RETURN path
五、新链挖掘实战案例
案例:Transform方法触发toString
挖掘过程:
- 使用模糊查询寻找调用
toString的方法:MATCH (m:Method) WHERE m.NAME CONTAINS "transform" AND m.CLASSNAME CONTAINS "Transform" MATCH (sink:Method {NAME:"toString"}) CALL tabby.algo.findJavaGadget(m, true, sink, 5, true) YIELD path RETURN path - 发现某Transform类中的
valueOf方法调用了toString,且参数可控。 - 构造Payload触发计算器。
漏洞代码片段:
public Object transform(Object input) {
return String.valueOf(input); // 触发input.toString()
}
利用条件:
- 可控输入传入
transform方法。 - 触发链最终调用到
toString方法。
六、总结与建议
- 熟练Cypher语法:尤其是路径查询与条件过滤。
- 理解Java反射与反序列化机制:有助于识别关键Sink点。
- 结合代码审计:Tabby辅助发现链段,人工分析确认可行性。
- 关注新组件:新库或版本更新可能引入新链。
如有错误或补充,欢迎指出!
👉 原文作者:follycat
👉 来源:先知社区(2025-09-09)
此文档已过滤无关内容,保留核心知识点与操作步骤,适合初学者系统学习与实战参考。