├── src ├── main │ └── java │ │ └── tabby │ │ ├── result │ │ ├── StringResult.java │ │ ├── PathResult.java │ │ └── HelpResult.java │ │ ├── expander │ │ ├── processor │ │ │ ├── ProcessorFactory.java │ │ │ ├── Processor.java │ │ │ ├── JavaGadgetBackwardProcessor.java │ │ │ ├── CommonProcessor.java │ │ │ ├── BaseProcessor.java │ │ │ ├── BackwardedProcessor.java │ │ │ ├── JavaGadgetProcessor.java │ │ │ └── ForwardedProcessor.java │ │ ├── SimplePathExpander.java │ │ └── TabbyPathExpander.java │ │ ├── util │ │ ├── Transformer.java │ │ ├── PositionHelper.java │ │ ├── JsonHelper.java │ │ └── Types.java │ │ ├── calculator │ │ ├── Calculator.java │ │ ├── ForwardedCalculator.java │ │ └── BackwardCalculator.java │ │ ├── path │ │ ├── BasePathFinder.java │ │ ├── BidirectionalTraversalPathFinder.java │ │ ├── TabbyTraversalPathFinder.java │ │ ├── TabbyBidirectionalTraversalPathFinder.java │ │ └── MonoDirectionalTraversalPathFinder.java │ │ ├── evaluator │ │ ├── CollisionDetector.java │ │ ├── MultiMonoPathEvaluator.java │ │ ├── MonoPathEvaluator.java │ │ └── TabbyEvaluator.java │ │ ├── data │ │ ├── Cache.java │ │ ├── EdgeCache.java │ │ ├── State.java │ │ ├── TabbyState.java │ │ └── Pollution.java │ │ ├── algo │ │ ├── release │ │ │ ├── PathFinding.java │ │ │ └── JavaGadgetPathFinding.java │ │ ├── BasePathFinding.java │ │ └── beta │ │ │ └── PathFinding.java │ │ └── help │ │ └── Help.java └── test │ └── java │ └── Test.java ├── .gitignore ├── README_EN.md ├── README.md └── pom.xml /src/main/java/tabby/result/StringResult.java: -------------------------------------------------------------------------------- 1 | package tabby.result; 2 | 3 | /** 4 | * @author mh 5 | * @since 11.04.16 6 | */ 7 | public class StringResult { 8 | public String data; 9 | 10 | public StringResult(String data) { 11 | this.data = data; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/tabby/result/PathResult.java: -------------------------------------------------------------------------------- 1 | package tabby.result; 2 | 3 | import org.neo4j.graphdb.Path; 4 | 5 | /** 6 | * @author mh 7 | * @since 11.04.16 8 | */ 9 | public class PathResult { 10 | public Path path; 11 | 12 | public PathResult(Path path) { 13 | this.path = path; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/tabby/expander/processor/ProcessorFactory.java: -------------------------------------------------------------------------------- 1 | package tabby.expander.processor; 2 | 3 | /** 4 | * @author wh1t3p1g 5 | * @since 2022/5/7 6 | */ 7 | public class ProcessorFactory { 8 | 9 | public static Processor newInstance(String name){ 10 | if ("JavaGadget".equals(name)) { 11 | return new JavaGadgetProcessor(); 12 | }else if("JavaGadgetBackward".endsWith(name)){ 13 | return new JavaGadgetBackwardProcessor(); 14 | }else if("Tabby".equals(name)){ 15 | return new ForwardedProcessor(false); 16 | } 17 | return new CommonProcessor(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/tabby/util/Transformer.java: -------------------------------------------------------------------------------- 1 | package tabby.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.Set; 5 | import java.util.stream.Collectors; 6 | 7 | /** 8 | * @author wh1t3p1g 9 | * @since 2022/6/2 10 | */ 11 | public class Transformer { 12 | 13 | public static int[] setToIntArray(Set set){ 14 | return set.stream().mapToInt(Integer::intValue).toArray(); 15 | } 16 | 17 | public static Set intArrayToSet(int[] array){ 18 | return Arrays.stream(array).boxed().collect(Collectors.toSet()); 19 | } 20 | 21 | public static Set flat(int[][] array){ 22 | int[] ret = Arrays.stream(array).flatMapToInt(o -> Arrays.stream(o)).toArray(); 23 | return intArrayToSet(ret); 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/tabby/calculator/Calculator.java: -------------------------------------------------------------------------------- 1 | package tabby.calculator; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * @author wh1t3p1g 7 | * @since 2022/5/10 8 | */ 9 | public interface Calculator { 10 | 11 | int[] v2(int[][] callSite, Set polluted); 12 | int[][] v3(int[][] callSite, int[][] polluted); 13 | 14 | default int[] calculate(int[][] callSite, Set polluted){ 15 | try{ 16 | return v2(callSite, polluted); 17 | }catch (Exception e){ 18 | } 19 | return new int[0]; 20 | } 21 | 22 | default int[][] calculate(int[][] callSite, int[][] polluted){ 23 | try{ 24 | return v3(callSite, polluted); 25 | }catch (Exception e){ 26 | } 27 | return new int[][]{new int[]{-3}}; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/Test.java: -------------------------------------------------------------------------------- 1 | import tabby.data.Pollution; 2 | 3 | import java.io.IOException; 4 | import java.util.Set; 5 | 6 | /** 7 | * @author wh1t3p1g 8 | * @since 2023/8/24 9 | */ 10 | public class Test { 11 | 12 | public static void main(String[] args) throws IOException { 13 | Pollution pollution = new Pollution(); 14 | pollution.getSpecialPolluted().put(1, 111); 15 | pollution.getSpecialPolluted().put(1, 222); 16 | pollution.getSpecialPolluted().put(2, 333); 17 | pollution.getTypes().add(Set.of("test")); 18 | pollution.getPolluted().add(Set.of(123)); 19 | pollution.getPolluted().add(Set.of(2222)); 20 | Pollution copied = pollution.copy(); 21 | System.out.println(copied); 22 | copied.getSpecialPolluted().remove(1, 111); 23 | System.out.println(pollution); 24 | System.out.println(copied); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | temp/ 40 | *.class 41 | .idea/ 42 | cases/ 43 | 44 | .mvn/ 45 | mvnw* 46 | logs/ 47 | cache/* 48 | lib/* 49 | 50 | runtime.json 51 | *.csv 52 | *.db 53 | ignores.json 54 | knowledges.json 55 | cql.txt 56 | *.jar 57 | config/settings.properties 58 | 59 | .DS_Store 60 | 61 | target/ 62 | cyphers -------------------------------------------------------------------------------- /src/main/java/tabby/expander/processor/Processor.java: -------------------------------------------------------------------------------- 1 | package tabby.expander.processor; 2 | 3 | import org.neo4j.graphdb.GraphDatabaseService; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.graphdb.Relationship; 6 | import org.neo4j.graphdb.Transaction; 7 | import tabby.calculator.Calculator; 8 | 9 | /** 10 | * @author wh1t3p1g 11 | * @since 2022/5/7 12 | */ 13 | public interface Processor { 14 | 15 | void init(Node node, STATE preState, Relationship lastRelationship); 16 | 17 | Relationship process(Relationship next); 18 | 19 | /** 20 | * 判断当前节点是否有必要继续往下扩展 21 | * 类似PathEvaluator的功能 22 | * @return 23 | */ 24 | boolean isNeedProcess(); 25 | 26 | STATE getNextState(); 27 | 28 | void setCalculator(Calculator calculator); 29 | 30 | boolean isLastRelationshipTypeAlias(); 31 | 32 | Processor copy(); 33 | 34 | Processor reverse(); 35 | 36 | void setDBSource(GraphDatabaseService db); 37 | void setTransaction(Transaction tx); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/tabby/result/HelpResult.java: -------------------------------------------------------------------------------- 1 | package tabby.result; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | /** 7 | * @author mh 8 | * @since 11.04.16 9 | */ 10 | public class HelpResult { 11 | public String type; 12 | public String name; 13 | public String text; 14 | public String signature; 15 | public List roles; 16 | public Boolean writes; 17 | public Boolean core; 18 | 19 | public HelpResult(String type, String name, String text, String signature, List roles, Boolean writes, Boolean core) { 20 | this.type = type; 21 | this.name = name; 22 | this.text = text; 23 | this.signature = signature; 24 | this.roles = roles; 25 | this.writes = writes; 26 | this.core = core; 27 | } 28 | 29 | public HelpResult(Map row, Boolean core) { 30 | this((String)row.get("type"),(String)row.get("name"),(String)row.get("description"),(String)row.get("signature"),null,(Boolean)row.get("writes"), core); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/tabby/util/PositionHelper.java: -------------------------------------------------------------------------------- 1 | package tabby.util; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * @author wh1t3P1g 7 | * @since 2022/2/8 8 | */ 9 | public class PositionHelper { 10 | 11 | public final static int THIS = -1; 12 | public final static int SOURCE = -2; 13 | public final static int NOT_POLLUTED_POSITION = -3; 14 | public final static int DAO = -4; 15 | public final static int RPC = -5; 16 | public final static int AUTH = -6; 17 | 18 | public static boolean isNotPollutedPosition(Object pos){ 19 | if(pos instanceof int[]){ 20 | int[] val = (int[]) pos; 21 | return val.length == 1 && NOT_POLLUTED_POSITION == val[0]; 22 | } 23 | 24 | return NOT_POLLUTED_POSITION == (int) pos; 25 | } 26 | 27 | public static boolean isThisPolluted(int[][] polluted){ 28 | for(int[] pos:polluted){ 29 | Set set = Transformer.intArrayToSet(pos); 30 | if(set.contains(PositionHelper.THIS)) return true; 31 | } 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/tabby/path/BasePathFinder.java: -------------------------------------------------------------------------------- 1 | package tabby.path; 2 | 3 | import org.neo4j.graphalgo.EvaluationContext; 4 | import org.neo4j.graphalgo.impl.path.TraversalPathFinder; 5 | import org.neo4j.graphdb.PathExpander; 6 | import org.neo4j.graphdb.traversal.Uniqueness; 7 | 8 | /** 9 | * @author wh1t3p1g 10 | * @since 2022/1/5 11 | */ 12 | public abstract class BasePathFinder extends TraversalPathFinder { 13 | 14 | public final PathExpander expander; 15 | public final EvaluationContext context; 16 | public final int maxDepth; 17 | public final boolean depthFirst; 18 | 19 | public BasePathFinder(EvaluationContext context, PathExpander expander, int maxDepth, boolean depthFirst) { 20 | this.expander = expander; 21 | this.context = context; 22 | this.maxDepth = maxDepth; 23 | this.depthFirst = depthFirst; 24 | } 25 | 26 | protected Uniqueness uniqueness() 27 | { 28 | // 从边的角度,会非常全,但相应的也会增加分析时间 29 | // 从node的角度,会丢失相同节点的另一种通路,但是对漏洞挖掘来说可接受?不可接受! 30 | return Uniqueness.RELATIONSHIP_PATH; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # tabby-path-finder 2 | 3 | [中文版本](https://github.com/wh1t3p1g/tabby-path-finder/blob/master/README.md) 4 | 5 | ## #0 Introduction 6 | A neo4j plugin for tabby 7 | 8 | ## #1 Configuration 9 | 10 | Configure jdk17 on your machine 11 | ```bash 12 | mvn clean package -DskipTests 13 | ``` 14 | 15 | Add the compiled jar file to Neo4j's plugin directory 16 | 17 | ## #2 Query syntax 18 | 19 | #### help 20 | View all procedures 21 | 22 | ``` 23 | call tabby.help("tabby") 24 | ``` 25 | 26 | #### beta procedures 27 | ```cypher 28 | tabby.beta.findPath(source, direct, sink, maxNodeLength, isDepthFirst) YIELD path, weight 29 | tabby.beta.findPathWithState(source, direct, sink, sinkState, maxNodeLength, isDepthFirst) YIELD path, weight 30 | tabby.beta.findJavaGadget(source, direct, sink, maxNodeLength, isDepthFirst) YIELD path, weight 31 | ``` 32 | - ">": Forward algorithm, starting from source to sink 33 | - "<": Backward algorithm, starting from sink to source 34 | 35 | #### Generic syntax 36 | 37 | ``` 38 | match (source:Method {NAME:"readObject",IS_SERIALIZABLE:true}) 39 | match (sink:Method {IS_SINK:true}) 40 | call tabby.beta.findJavaGadget(source, "<", sink, 6, true) yield path where none(n in nodes(path) where n.CLASSNAME in ["java.io.ObjectInputStream","Add the class you want to remove"]) 41 | return path limit 1 42 | ``` 43 | 44 | See neo4j cypher syntax for more information on usage 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/tabby/evaluator/CollisionDetector.java: -------------------------------------------------------------------------------- 1 | package tabby.evaluator; 2 | 3 | import org.neo4j.graphdb.Node; 4 | import org.neo4j.graphdb.Path; 5 | import org.neo4j.graphdb.Relationship; 6 | import org.neo4j.graphdb.impl.traversal.StandardBranchCollisionDetector; 7 | import org.neo4j.graphdb.traversal.Evaluator; 8 | import org.neo4j.graphdb.traversal.TraversalBranch; 9 | import tabby.data.Pollution; 10 | import tabby.data.TabbyState; 11 | 12 | import java.util.function.Predicate; 13 | 14 | /** 15 | * @author wh1t3p1g 16 | * @since 2023/8/26 17 | */ 18 | public class CollisionDetector extends StandardBranchCollisionDetector { 19 | 20 | public CollisionDetector(Evaluator evaluator, Predicate pathPredicate) { 21 | super(evaluator, pathPredicate); 22 | } 23 | 24 | 25 | @Override 26 | protected boolean includePath(Path path, TraversalBranch startPath, TraversalBranch endPath) { 27 | Pollution startPol = getPollution(startPath); 28 | Pollution endPol = getPollution(endPath); 29 | return Pollution.compare(startPol, endPol); 30 | } 31 | 32 | public Pollution getPollution(TraversalBranch path){ 33 | Relationship relationship = path.lastRelationship(); 34 | TabbyState state = (TabbyState) path.state(); 35 | String id; 36 | if(relationship == null){ 37 | Node node = path.endNode(); 38 | id = "node_"+node.getId(); 39 | }else{ 40 | id = String.valueOf(relationship.getId()); 41 | } 42 | return state.get(id); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/tabby/calculator/ForwardedCalculator.java: -------------------------------------------------------------------------------- 1 | package tabby.calculator; 2 | 3 | import tabby.util.PositionHelper; 4 | import tabby.util.Transformer; 5 | 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | /** 10 | * @author wh1t3p1g 11 | * @since 2022/5/10 12 | */ 13 | public class ForwardedCalculator implements Calculator{ 14 | 15 | @Override 16 | public int[] v2(int[][] callSite, Set polluted) { 17 | Set nextPolluted = new HashSet<>(); 18 | int length = callSite.length; 19 | for(int pos=0; pos ret = new HashSet<>(); 34 | int length = callSite.length; 35 | Set pol = Transformer.flat(polluted); 36 | pol.add(PositionHelper.SOURCE); 37 | for(int index=0; index T parseObject(String polluted, Type type){ 38 | if(polluted == null || polluted.isBlank()) return null; 39 | 40 | try{ 41 | return gson.fromJson(polluted, type); 42 | }catch (Exception e){ 43 | } 44 | return null; 45 | } 46 | 47 | public static T deepCopy(Object obj, Type type){ 48 | try{ 49 | String json = gson.toJson(obj); 50 | return gson.fromJson(json, type); 51 | }catch (Exception e){ 52 | } 53 | return null; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/tabby/data/Cache.java: -------------------------------------------------------------------------------- 1 | package tabby.data; 2 | 3 | import com.google.common.cache.CacheBuilder; 4 | import com.google.common.cache.CacheLoader; 5 | import com.google.common.cache.LoadingCache; 6 | import org.neo4j.graphdb.Relationship; 7 | 8 | import java.util.LinkedList; 9 | import java.util.List; 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | /** 14 | * @author wh1t3p1g 15 | * @since 2023/1/28 16 | */ 17 | public class Cache { 18 | 19 | public static Cache rel = new Cache(); 20 | private LoadingCache, Pollution> caching; 21 | 22 | public Cache() { 23 | caching = CacheBuilder.newBuilder() 24 | .maximumSize(30000) 25 | .expireAfterAccess(10, TimeUnit.MINUTES) 26 | .build(new CacheLoader<>(){ 27 | @Override 28 | public Pollution load(List objs) throws Exception { 29 | Relationship edge = (Relationship) objs.get(0); 30 | boolean isCheckType = (boolean) objs.get(1); 31 | return Pollution.of(edge, isCheckType); 32 | } 33 | }); 34 | } 35 | 36 | public Pollution get(Relationship edge, boolean isCheckType){ 37 | try { 38 | List objs = new LinkedList<>(); 39 | objs.add(edge); 40 | objs.add(isCheckType); 41 | return caching.get(objs); // 内存中可能会存两份同样的edge数据,有types和无types 42 | } catch (ExecutionException e) { 43 | return null; 44 | } 45 | } 46 | 47 | public long size(){ 48 | return caching.size(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/tabby/expander/processor/JavaGadgetBackwardProcessor.java: -------------------------------------------------------------------------------- 1 | package tabby.expander.processor; 2 | 3 | import org.neo4j.graphdb.Relationship; 4 | import tabby.data.EdgeCache; 5 | import tabby.util.PositionHelper; 6 | import tabby.util.Types; 7 | 8 | /** 9 | * @author wh1t3p1g 10 | * @since 2022/5/7 11 | */ 12 | public class JavaGadgetBackwardProcessor extends JavaGadgetProcessor{ 13 | 14 | @Override 15 | public Relationship process(Relationship next){ 16 | Relationship ret = null; 17 | long nextEdgeId = next.getId(); 18 | String key = nextEdgeId + ""; 19 | 20 | if(Types.isAlias(next)){ 21 | nextState.put(key, polluted); 22 | nextState.addAliasEdge(nextEdgeId); 23 | ret = next; 24 | }else{ 25 | int[][] callSite = EdgeCache.rel.get(next); 26 | 27 | if(isLastRelationshipTypeAlias && callSite.length > 0 28 | && PositionHelper.isNotPollutedPosition(callSite[0])){ 29 | return null; 30 | } 31 | 32 | int[][] nextPos = calculator.calculate(callSite, polluted); 33 | 34 | if(nextPos != null && nextPos.length > 0){ 35 | nextState.put(key, nextPos); 36 | ret = next; 37 | } 38 | } 39 | return ret; 40 | } 41 | 42 | @Override 43 | public boolean isNeedProcess() { 44 | return isFirstNode || isSerializable() || isStatic() || 45 | isAbstract() || isFromAbstractClass() || 46 | "java.lang.Object".equals(getClassname()); 47 | } 48 | 49 | @Override 50 | public Processor copy() { 51 | return new JavaGadgetBackwardProcessor(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/tabby/evaluator/MultiMonoPathEvaluator.java: -------------------------------------------------------------------------------- 1 | package tabby.evaluator; 2 | 3 | import org.neo4j.graphdb.Node; 4 | import org.neo4j.graphdb.Path; 5 | import org.neo4j.graphdb.traversal.BranchState; 6 | import org.neo4j.graphdb.traversal.Evaluation; 7 | import org.neo4j.graphdb.traversal.PathEvaluator; 8 | import tabby.data.State; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * 一对多 14 | * @author wh1t3p1g 15 | * @since 2022/1/6 16 | */ 17 | public class MultiMonoPathEvaluator extends PathEvaluator.Adapter { 18 | 19 | private List endNodes; 20 | private int maxDepth; 21 | 22 | public MultiMonoPathEvaluator(List endNodes, int maxDepth) { 23 | this.endNodes = endNodes; 24 | this.maxDepth = maxDepth; 25 | } 26 | 27 | @Override 28 | public Evaluation evaluate(Path path, BranchState state) { 29 | boolean includes = true; 30 | boolean continues = true; 31 | int length = path.length(); 32 | if(length == 0) return Evaluation.of(false, true); 33 | 34 | Node node = path.endNode(); 35 | 36 | if(length >= maxDepth){ 37 | continues = false; // 超出长度 不继续进行 38 | if(endNodes != null && !endNodes.contains(node)){ 39 | includes = false; // 最后的节点不是endNode,不保存当前结果 40 | } 41 | }else if(endNodes != null && endNodes.contains(node)){ 42 | // 长度没到,但已经找到了endNode,停止进行 43 | continues = false; 44 | } else { 45 | includes = false; 46 | } 47 | 48 | return Evaluation.of(includes, continues); 49 | } 50 | 51 | public static MultiMonoPathEvaluator of(List endNodes, int maxDepth){ 52 | return new MultiMonoPathEvaluator(endNodes, maxDepth); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/tabby/data/EdgeCache.java: -------------------------------------------------------------------------------- 1 | package tabby.data; 2 | 3 | import com.google.common.cache.CacheBuilder; 4 | import com.google.common.cache.CacheLoader; 5 | import com.google.common.cache.LoadingCache; 6 | import org.neo4j.graphdb.Relationship; 7 | import tabby.util.JsonHelper; 8 | 9 | import java.util.concurrent.ExecutionException; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * @author wh1t3p1g 14 | * @since 2023/1/28 15 | */ 16 | public class EdgeCache { 17 | 18 | public static EdgeCache rel = new EdgeCache(); 19 | private LoadingCache caching; 20 | 21 | public EdgeCache() { 22 | caching = CacheBuilder.newBuilder() 23 | .maximumSize(30000) 24 | .expireAfterAccess(10, TimeUnit.MINUTES) 25 | .build(new CacheLoader<>(){ 26 | @Override 27 | public int[][] load(Relationship edge) throws Exception { 28 | try{ 29 | String pollutedStr = (String) edge.getProperty("POLLUTED_POSITION"); 30 | if(pollutedStr == null) return new int[0][]; 31 | return JsonHelper.parse(pollutedStr); 32 | }catch (Exception e){ 33 | return new int[0][]; 34 | } 35 | } 36 | }); 37 | } 38 | 39 | public int[][] get(Relationship edge){ 40 | try { 41 | return caching.get(edge); 42 | } catch (ExecutionException e) { 43 | return new int[0][]; 44 | } 45 | } 46 | 47 | public void put(Relationship edge, int[][] values){ 48 | caching.put(edge, values); 49 | } 50 | 51 | public long size(){ 52 | return caching.size(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/tabby/evaluator/MonoPathEvaluator.java: -------------------------------------------------------------------------------- 1 | package tabby.evaluator; 2 | 3 | import org.neo4j.graphdb.Node; 4 | import org.neo4j.graphdb.Path; 5 | import org.neo4j.graphdb.traversal.BranchState; 6 | import org.neo4j.graphdb.traversal.Evaluation; 7 | import org.neo4j.graphdb.traversal.PathEvaluator; 8 | import tabby.data.State; 9 | 10 | /** 11 | * 一对一 12 | * @author wh1t3p1g 13 | * @since 2022/1/6 14 | */ 15 | public class MonoPathEvaluator extends PathEvaluator.Adapter { 16 | 17 | private Node endNode; 18 | private int maxDepth; 19 | private boolean checkAuth; 20 | 21 | public MonoPathEvaluator(Node endNode, int maxDepth, boolean checkAuth) { 22 | this.endNode = endNode; 23 | this.maxDepth = maxDepth; 24 | this.checkAuth = checkAuth; 25 | } 26 | 27 | @Override 28 | public Evaluation evaluate(Path path, BranchState state) { 29 | boolean includes = true; 30 | boolean continues = true; 31 | int length = path.length(); 32 | Node node = path.endNode(); 33 | 34 | if(length >= maxDepth){ 35 | continues = false; // 超出长度 不继续进行 36 | if(endNode != null && !endNode.equals(node)){ 37 | includes = false; // 最后的节点不是endNode,不保存当前结果 38 | } 39 | }else if(length == 0){ // 开始节点 40 | includes = false; 41 | } else if(endNode != null && endNode.equals(node)){ 42 | // 长度没到,但已经找到了endNode,停止进行 43 | continues = false; 44 | } else { 45 | includes = false; 46 | } 47 | 48 | return Evaluation.of(includes, continues); 49 | } 50 | 51 | public static MonoPathEvaluator of(Node endNode, int maxDepth){ 52 | return new MonoPathEvaluator(endNode, maxDepth, false); 53 | } 54 | 55 | public static MonoPathEvaluator of(Node endNode, int maxDepth, boolean checkAuth){ 56 | return new MonoPathEvaluator(endNode, maxDepth, checkAuth); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/tabby/data/State.java: -------------------------------------------------------------------------------- 1 | package tabby.data; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.util.*; 7 | 8 | /** 9 | * @author wh1t3p1g 10 | * @since 2022/4/26 11 | */ 12 | @Getter 13 | @Setter 14 | public class State { 15 | 16 | private Map positions; 17 | private List alias; 18 | private List staticCalls; 19 | private List nextAlias; // 是否允许下一个节点进行alias操作 20 | 21 | public State() { 22 | this.positions = Collections.synchronizedMap(new HashMap<>()); 23 | this.alias = new ArrayList<>(); 24 | this.staticCalls = new ArrayList<>(); 25 | this.nextAlias = new ArrayList<>(); 26 | } 27 | 28 | public int[][] getInitialPositions(long nodeId){ 29 | return getPositions("node_"+nodeId); 30 | } 31 | 32 | public void addInitialPositions(long nodeId, int[][] positions){ 33 | this.positions.put("node_"+nodeId, positions); 34 | } 35 | 36 | public int[][] getPositions(String id){ 37 | return positions.get(id); 38 | } 39 | 40 | public void put(String id, int[][] position){ 41 | positions.put(id, position); 42 | } 43 | 44 | public boolean isEmpty(){ 45 | return positions == null || positions.isEmpty(); 46 | } 47 | 48 | public boolean isAlias(long id){ 49 | return alias.contains(id); 50 | } 51 | 52 | public boolean isStaticCall(long id){ 53 | return staticCalls.contains(id); 54 | } 55 | 56 | public void addAliasEdge(long id){ 57 | alias.add(id); 58 | } 59 | 60 | public void addStaticCallEdge(long id){ 61 | staticCalls.add(id); 62 | } 63 | 64 | public static State newInstance(){ 65 | return new State(); 66 | } 67 | 68 | public State copy(){ 69 | State state = new State(); 70 | state.setAlias(new ArrayList<>(alias)); 71 | state.setNextAlias(new ArrayList<>(nextAlias)); 72 | state.setStaticCalls(new ArrayList<>(staticCalls)); 73 | state.setPositions(new HashMap<>(positions)); 74 | return state; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/tabby/expander/processor/CommonProcessor.java: -------------------------------------------------------------------------------- 1 | package tabby.expander.processor; 2 | 3 | import org.neo4j.graphdb.Node; 4 | import org.neo4j.graphdb.Relationship; 5 | import tabby.calculator.BackwardCalculator; 6 | import tabby.data.EdgeCache; 7 | import tabby.data.State; 8 | import tabby.util.PositionHelper; 9 | import tabby.util.Types; 10 | 11 | /** 12 | * @author wh1t3p1g 13 | * @since 2022/5/7 14 | */ 15 | public class CommonProcessor extends BaseProcessor{ 16 | 17 | @Override 18 | public void init(Node node, State preState, Relationship lastRelationship) { 19 | super.init(node, preState, lastRelationship); 20 | } 21 | 22 | @Override 23 | public Relationship process(Relationship next) { 24 | Relationship ret = null; 25 | String nextId = next.getId() + ""; 26 | if(Types.isAlias(next)){ 27 | if(calculator instanceof BackwardCalculator || PositionHelper.isThisPolluted(polluted)){ 28 | nextState.put(nextId, polluted); 29 | nextState.addAliasEdge(next.getId()); 30 | ret = next; 31 | } 32 | }else{ 33 | int[][] callSite = EdgeCache.rel.get(next); 34 | 35 | if(isNecessaryProcess(callSite)){ 36 | int[][] nextPos = calculator.calculate(callSite, polluted); 37 | 38 | if(nextPos != null && nextPos.length > 0){ 39 | nextState.put(nextId, nextPos); 40 | ret = next; 41 | } 42 | } 43 | } 44 | return ret; 45 | } 46 | 47 | public boolean isNecessaryProcess(int[][] callSite){ 48 | if(calculator instanceof BackwardCalculator){ 49 | // isLastRelationshipTypeAlias 用于排除 50 | if(isLastRelationshipTypeAlias && callSite.length > 0 51 | && PositionHelper.isNotPollutedPosition(callSite[0])){ 52 | return false; 53 | } 54 | } 55 | return true; 56 | } 57 | 58 | @Override 59 | public boolean isNeedProcess() { 60 | return true; 61 | } 62 | 63 | @Override 64 | public Processor copy() { 65 | return new CommonProcessor(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/tabby/path/BidirectionalTraversalPathFinder.java: -------------------------------------------------------------------------------- 1 | package tabby.path; 2 | 3 | import org.neo4j.graphalgo.EvaluationContext; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.graphdb.PathExpander; 6 | import org.neo4j.graphdb.Transaction; 7 | import org.neo4j.graphdb.traversal.InitialBranchState; 8 | import org.neo4j.graphdb.traversal.TraversalDescription; 9 | import org.neo4j.graphdb.traversal.Traverser; 10 | import tabby.data.State; 11 | 12 | import static org.neo4j.graphdb.traversal.Evaluators.toDepth; 13 | 14 | /** 15 | * @author wh1t3p1g 16 | * @since 2022/1/6 17 | */ 18 | public class BidirectionalTraversalPathFinder extends BasePathFinder{ 19 | 20 | private InitialBranchState.State sourceState; 21 | private InitialBranchState.State sinkState; 22 | 23 | public BidirectionalTraversalPathFinder(EvaluationContext context, 24 | PathExpander expander, 25 | int maxDepth, 26 | State sourceState, 27 | State sinkState, 28 | boolean depthFirst) { 29 | super(context, expander, maxDepth, depthFirst); 30 | this.sourceState = new InitialBranchState.State<>(sourceState, sourceState.copy()); 31 | this.sinkState = new InitialBranchState.State<>(sinkState, sinkState.copy()); 32 | } 33 | 34 | @Override 35 | protected Traverser instantiateTraverser(Node start, Node end) { 36 | Transaction transaction = context.transaction(); 37 | TraversalDescription base = transaction.traversalDescription().uniqueness( uniqueness() ); 38 | 39 | if(depthFirst){ 40 | base = base.depthFirst(); 41 | }else{ 42 | base = base.breadthFirst(); 43 | } 44 | 45 | return transaction.bidirectionalTraversalDescription() 46 | .startSide( base.expand( expander, sourceState ).evaluator( toDepth( maxDepth / 2 ) ) ) 47 | .endSide( base.expand( expander.reverse(), sinkState ).evaluator( toDepth( maxDepth - maxDepth / 2 ) ) ) 48 | .traverse( start, end ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/tabby/path/TabbyTraversalPathFinder.java: -------------------------------------------------------------------------------- 1 | package tabby.path; 2 | 3 | import org.neo4j.graphalgo.EvaluationContext; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.graphdb.PathExpander; 6 | import org.neo4j.graphdb.Transaction; 7 | import org.neo4j.graphdb.traversal.InitialBranchState; 8 | import org.neo4j.graphdb.traversal.TraversalDescription; 9 | import org.neo4j.graphdb.traversal.Traverser; 10 | import tabby.data.TabbyState; 11 | import tabby.evaluator.TabbyEvaluator; 12 | 13 | /** 14 | * @author wh1t3p1g 15 | * @since 2023/8/23 16 | */ 17 | public class TabbyTraversalPathFinder extends BasePathFinder { 18 | 19 | private InitialBranchState.State state; 20 | private boolean checkAuth = false; 21 | private boolean isBackward = false; 22 | private TabbyState endState = null; 23 | 24 | public TabbyTraversalPathFinder(EvaluationContext context, 25 | PathExpander expander, 26 | TabbyState initialState, 27 | TabbyState endState, int maxDepth, 28 | boolean depthFirst, boolean checkAuth, boolean isBackward) { 29 | super(context, expander, maxDepth, depthFirst); 30 | this.state = new InitialBranchState.State<>(initialState, TabbyState.of()); 31 | this.checkAuth = checkAuth; 32 | this.isBackward = isBackward; 33 | this.endState = endState; 34 | } 35 | 36 | @Override 37 | protected Traverser instantiateTraverser(Node start, Node end) { 38 | Transaction transaction = context.transaction(); 39 | 40 | TraversalDescription base = getBaseDescription(transaction); 41 | 42 | return base.expand(expander, state) 43 | .evaluator(TabbyEvaluator.of(end, endState, maxDepth, checkAuth, isBackward)) 44 | .traverse(start); 45 | } 46 | 47 | public TraversalDescription getBaseDescription(Transaction transaction){ 48 | TraversalDescription base = transaction.traversalDescription(); 49 | 50 | if(depthFirst){ 51 | base = base.depthFirst(); 52 | }else{ 53 | base = base.breadthFirst(); 54 | } 55 | 56 | return base.uniqueness(uniqueness()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/tabby/util/Types.java: -------------------------------------------------------------------------------- 1 | package tabby.util; 2 | 3 | import org.neo4j.graphdb.Direction; 4 | import org.neo4j.graphdb.Relationship; 5 | import org.neo4j.graphdb.RelationshipType; 6 | import org.neo4j.kernel.impl.core.RelationshipEntity; 7 | 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.lang.reflect.Method; 10 | 11 | import static org.neo4j.graphdb.Direction.*; 12 | 13 | /** 14 | * @author wh1t3p1g 15 | * @since 2022/1/6 16 | */ 17 | public class Types { 18 | 19 | private final static int NO_ID = -1; 20 | private static int ALIAS_TYPE_ID = NO_ID; 21 | private static int CALL_TYPE_ID = NO_ID; 22 | 23 | public static Direction directionFor(String type) { 24 | if (type.contains("<")) return INCOMING; 25 | if (type.contains(">")) return OUTGOING; 26 | return BOTH; 27 | } 28 | 29 | public static RelationshipType relationshipTypeFor(String name) { 30 | name = name.replace("<", "").replace(">", ""); 31 | return name.trim().isEmpty() ? null : RelationshipType.withName(name); 32 | } 33 | 34 | public static boolean isAlias(Relationship relationship){ 35 | boolean isAlias = false; 36 | 37 | if(relationship != null){ 38 | int typeId = NO_ID; 39 | 40 | if(relationship instanceof RelationshipEntity){ 41 | // reflect type id 42 | try { 43 | Method method = RelationshipEntity.class.getDeclaredMethod("typeId"); 44 | method.setAccessible(true); 45 | typeId = (int) method.invoke(relationship); 46 | } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { 47 | // ignore 48 | e.printStackTrace(); 49 | } 50 | } 51 | 52 | if(typeId == NO_ID || ALIAS_TYPE_ID == NO_ID || CALL_TYPE_ID == NO_ID){ 53 | RelationshipType type = relationship.getType(); 54 | isAlias = "ALIAS".equals(type.name()); 55 | if(typeId != NO_ID){ 56 | if(isAlias){ 57 | ALIAS_TYPE_ID = typeId; 58 | }else{ 59 | CALL_TYPE_ID = typeId; 60 | } 61 | } 62 | }else{ 63 | isAlias = typeId == ALIAS_TYPE_ID; 64 | } 65 | 66 | } 67 | return isAlias; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/tabby/expander/processor/BaseProcessor.java: -------------------------------------------------------------------------------- 1 | package tabby.expander.processor; 2 | 3 | import lombok.Getter; 4 | import org.neo4j.graphdb.GraphDatabaseService; 5 | import org.neo4j.graphdb.Node; 6 | import org.neo4j.graphdb.Relationship; 7 | import org.neo4j.graphdb.Transaction; 8 | import tabby.calculator.Calculator; 9 | import tabby.data.State; 10 | import tabby.util.Types; 11 | 12 | /** 13 | * @author wh1t3p1g 14 | * @since 2022/5/10 15 | */ 16 | @Getter 17 | public abstract class BaseProcessor implements Processor{ 18 | 19 | public Node node = null; 20 | public State preState = null; 21 | public State nextState = null; 22 | public Relationship lastRelationship = null; 23 | public Calculator calculator = null; 24 | public int[][] polluted = null; 25 | public boolean isLastRelationshipTypeAlias = false; 26 | public boolean isFirstNode = false; 27 | 28 | @Override 29 | public void init(Node node, State preState, Relationship lastRelationship) { 30 | this.node = node; 31 | this.preState = preState; 32 | this.nextState = State.newInstance(); 33 | this.lastRelationship = lastRelationship; 34 | 35 | int[][] polluted = null; 36 | if(lastRelationship == null){ 37 | polluted = preState.getInitialPositions(node.getId()); 38 | isFirstNode = true; 39 | }else{ 40 | long id = lastRelationship.getId(); 41 | polluted = preState.getPositions(id + ""); 42 | isLastRelationshipTypeAlias = Types.isAlias(lastRelationship); 43 | isFirstNode = false; 44 | } 45 | 46 | this.polluted = polluted; 47 | // this.polluted.add(PositionHelper.SOURCE); // 添加source 48 | } 49 | 50 | public void setCalculator(Calculator calculator){ 51 | this.calculator = calculator; 52 | } 53 | 54 | @Override 55 | public boolean isLastRelationshipTypeAlias() { 56 | return isLastRelationshipTypeAlias; 57 | } 58 | 59 | @Override 60 | public boolean isNeedProcess() { 61 | return true; 62 | } 63 | 64 | @Override 65 | public State getNextState() { 66 | return nextState; 67 | } 68 | 69 | @Override 70 | public Processor reverse() { 71 | return null; 72 | } 73 | 74 | @Override 75 | public void setDBSource(GraphDatabaseService db) { 76 | 77 | } 78 | 79 | @Override 80 | public void setTransaction(Transaction tx) { 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tabby-path-finder 2 | 3 | [English Version](https://github.com/wh1t3p1g/tabby-path-finder/blob/master/README_EN.md) 4 | 5 | ## #0 简介 6 | A neo4j procedure for [tabby](https://github.com/wh1t3p1g/tabby) 7 | 8 | tabby污点分析扩展,用于根据tabby生成的代码属性图做动态剪枝+深度搜索符合条件的利用链/漏洞链路。 9 | 10 | ## #1 用法 11 | 12 | 生成jar文件 13 | ```bash 14 | mvn clean package -DskipTests 15 | ``` 16 | 17 | 在neo4j的plugin目录添加jar文件 18 | 19 | neo4j server需要在配置中添加上以下内容 20 | ``` 21 | dbms.security.procedures.unrestricted=apoc.*,tabby.* 22 | ``` 23 | 24 | ## #2 语法 25 | 26 | #### help 27 | 查看所有 procedure 28 | 29 | ``` 30 | call tabby.help("tabby") 31 | ``` 32 | 33 | #### released procedures 34 | 35 | ```cypher 36 | tabby.algo.findPath(source, direct, sink, maxNodeLength, isDepthFirst) YIELD path, weight 37 | tabby.algo.findPathWithState(source, direct, sink, sinkState, maxNodeLength, isDepthFirst) YIELD path, weight 38 | tabby.algo.findJavaGadget(source, direct, sink, maxNodeLength, isDepthFirst) YIELD path, weight 39 | tabby.algo.findJavaGadgetWithState(source, direct, sink, sinkState, maxNodeLength, isDepthFirst) YIELD path, weight 40 | ``` 41 | 42 | findPath 系列用于应用 tabby 生成的带污点的代码属性图,在遍历过程中不断剪枝,最终输出 n 条符合污点传播的路径。 43 | 44 | findJavaGadget 系列用于查找 Java 原生反序列化利用链,在污点剪枝的基础上,同时判断当前函数所属的 class 是否实现了 Serializable 接口。 45 | 46 | 另外,findPath 系列 direct 有3种: 47 | - ">": 前向算法,从 source 开始查找至 sink 48 | - "<": 后向算法,从 sink 开始查找至 source 49 | - "-": 双向算法,分别从 source 和 sink 开始查找,找到聚合点后输出 50 | 51 | findJavaGadget 系列 direct 只支持前向和后向算法 52 | 53 | #### 通用语法 54 | 55 | 通用的语法,更多的用法参考neo4j cypher语法 56 | ``` 57 | match (source:Method {NAME:"readObject"}) // 限定source 58 | match (sink:Method {IS_SINK:true, NAME:"invoke"}) // 限定sink 59 | call tabby.algo.findJavaGadget(source, ">", sink, 8, false) yield path 60 | where none(n in nodes(path) where 61 | n.CLASSNAME in [ 62 | "java.io.ObjectInputStream", 63 | "org.apache.commons.beanutils.BeanMap", 64 | "org.apache.commons.collections4.functors.PrototypeFactory$PrototypeCloneFactory"]) 65 | return path limit 1 66 | ``` 67 | 68 | Note: 由于neo4j底层并不支持多线程,当前所有接口都剔除了多线程的参数配置 69 | 70 | Note: 关于效果的说明: 71 | 72 | 1. 速度上:相比较直接查询路径连通性的算法,加了污点分析的算法会增加几次数据库查询,但污点分析增加了动态剪枝,减少了路径遍历的次数。目前,暂时没有进行速度上的分析,无法确定是增加耗时还是减少查询时间。 73 | 2. 检出率:由于加了污点分析,出来的结果都是tabby污点分析算法所认为可连续数据传递的链路。简单测试了一个小项目,从2k+链路能减少到10+链路。误报极大的降低,但同时,由于算法分析的不准确性,使得相应的增加了漏报率 74 | 75 | Note: tricks: 76 | 77 | 1. 如果需要大而全的链路输出,选择apoc.algo.allSimplePaths。但相应存在大量的误报链路 78 | 2. 如果使用了污点分析扩展,建议先看污点分析出来的链路,后看对应sink的调用节点是否都准确 79 | 3. 类似tabby、codeql等静态分析工具,不是万金油,能用工具直接检测出来的漏洞是非常少的(ps:捡漏还是可以的 XD),但这些工具能给你带来审计上效率的提升,明确思路等 80 | 81 | ## #3 案例 82 | 83 | ~~见cyphers目录~~ 84 | 85 | 目前,查询结果基于tabby 2.0,暂未测试tabby 1.x -------------------------------------------------------------------------------- /src/main/java/tabby/path/TabbyBidirectionalTraversalPathFinder.java: -------------------------------------------------------------------------------- 1 | package tabby.path; 2 | 3 | import org.neo4j.graphalgo.EvaluationContext; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.graphdb.PathExpander; 6 | import org.neo4j.graphdb.Transaction; 7 | import org.neo4j.graphdb.traversal.Evaluators; 8 | import org.neo4j.graphdb.traversal.InitialBranchState; 9 | import org.neo4j.graphdb.traversal.TraversalDescription; 10 | import org.neo4j.graphdb.traversal.Traverser; 11 | import tabby.data.TabbyState; 12 | import tabby.evaluator.CollisionDetector; 13 | 14 | import static org.neo4j.graphdb.traversal.Evaluators.toDepth; 15 | 16 | /** 17 | * @author wh1t3p1g 18 | * @since 2023/8/23 19 | */ 20 | public class TabbyBidirectionalTraversalPathFinder extends BasePathFinder { 21 | 22 | private InitialBranchState.State sourceState; 23 | private InitialBranchState.State sinkState; 24 | 25 | public TabbyBidirectionalTraversalPathFinder(EvaluationContext context, 26 | PathExpander expander, 27 | TabbyState sourceState, 28 | TabbyState sinkState, int maxDepth, 29 | boolean depthFirst) { 30 | super(context, expander, maxDepth, depthFirst); 31 | this.sourceState = new InitialBranchState.State<>(sourceState, TabbyState.of()); 32 | this.sinkState = new InitialBranchState.State<>(sinkState, TabbyState.of()); 33 | } 34 | 35 | @Override 36 | protected Traverser instantiateTraverser(Node start, Node end) { 37 | Transaction transaction = context.transaction(); 38 | TraversalDescription base = getBaseDescription(transaction); 39 | 40 | return transaction.bidirectionalTraversalDescription() 41 | .startSide( base.expand( expander, sourceState ).evaluator( toDepth( maxDepth / 2 ) ) ) 42 | .endSide( base.expand( expander.reverse(), sinkState ).evaluator( toDepth( maxDepth - maxDepth / 2 ) ) ) 43 | .collisionEvaluator(Evaluators.all()) 44 | .collisionPolicy(CollisionDetector::new) 45 | .traverse( start, end ); 46 | } 47 | 48 | public TraversalDescription getBaseDescription(Transaction transaction){ 49 | TraversalDescription base = transaction.traversalDescription(); 50 | 51 | if(depthFirst){ 52 | base = base.depthFirst(); 53 | }else{ 54 | base = base.breadthFirst(); 55 | } 56 | 57 | return base.uniqueness(uniqueness()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/tabby/algo/release/PathFinding.java: -------------------------------------------------------------------------------- 1 | package tabby.algo.release; 2 | 3 | import org.neo4j.graphdb.GraphDatabaseService; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.graphdb.Transaction; 6 | import org.neo4j.procedure.Context; 7 | import org.neo4j.procedure.Description; 8 | import org.neo4j.procedure.Name; 9 | import org.neo4j.procedure.Procedure; 10 | import tabby.algo.BasePathFinding; 11 | import tabby.result.PathResult; 12 | 13 | import java.util.stream.Stream; 14 | 15 | /** 16 | * @author wh1t3p1g 17 | * @since 2022/1/6 18 | */ 19 | public class PathFinding extends BasePathFinding { 20 | 21 | @Context 22 | public GraphDatabaseService db; 23 | 24 | @Context 25 | public Transaction tx; 26 | 27 | @Procedure("tabby.algo.findPath") 28 | @Description("tabby.algo.findPath(source, direct, sink, maxNodeLength, isDepthFirst) YIELD path, weight" + 29 | " - using findPath to get source-sink path with maxNodeLength") 30 | public Stream findPath(@Name("source") Node sourceNode, 31 | @Name("direct") String direct, 32 | @Name("sink") Node sinkNode, 33 | @Name("maxNodeLength") Long maxNodeLength, 34 | @Name("isDepthFirst") boolean isDepthFirst){ 35 | return findPathWithState(sourceNode, direct, sinkNode, null, maxNodeLength.intValue(), isDepthFirst, false, false, db, tx); 36 | } 37 | 38 | /** 39 | * 后向分析,需提供 sink 函数的 污点数据 40 | * 不提供前向分析,即source to sink 41 | * @param sourceNode 42 | * @param sinkNode 43 | * @param state 44 | * @param maxNodeLength 45 | * @param isDepthFirst 46 | * @return 47 | */ 48 | @Procedure("tabby.algo.findPathWithState") 49 | @Description("tabby.algo.findPathWithState(source, direct, sink, sinkState, maxNodeLength, isDepthFirst) YIELD path, weight" + 50 | " - using findPath to get source-sink path with maxNodeLength and state") 51 | public Stream findPathWithState(@Name("source") Node sourceNode, 52 | @Name("direct") String direct, 53 | @Name("sink") Node sinkNode, 54 | @Name("state") String state, 55 | @Name("maxNodeLength") Long maxNodeLength, 56 | @Name("isDepthFirst") boolean isDepthFirst){ 57 | return findPathWithState(sourceNode, direct, sinkNode, state, maxNodeLength.intValue(), isDepthFirst, false, false, db, tx); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/tabby/path/MonoDirectionalTraversalPathFinder.java: -------------------------------------------------------------------------------- 1 | package tabby.path; 2 | 3 | import org.neo4j.graphalgo.EvaluationContext; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.graphdb.PathExpander; 6 | import org.neo4j.graphdb.Transaction; 7 | import org.neo4j.graphdb.traversal.InitialBranchState; 8 | import org.neo4j.graphdb.traversal.TraversalDescription; 9 | import org.neo4j.graphdb.traversal.Traverser; 10 | import tabby.data.State; 11 | import tabby.evaluator.MonoPathEvaluator; 12 | import tabby.util.JsonHelper; 13 | 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | /** 18 | * @author wh1t3p1g 19 | * @since 2022/4/26 20 | */ 21 | public class MonoDirectionalTraversalPathFinder extends BasePathFinder{ 22 | 23 | private InitialBranchState.State stack; 24 | 25 | public MonoDirectionalTraversalPathFinder(EvaluationContext context, 26 | PathExpander expander, 27 | int maxDepth, 28 | State state, 29 | boolean depthFirst 30 | ) { 31 | super(context, expander, maxDepth, depthFirst); 32 | this.stack = new InitialBranchState.State<>(state, state.copy()); 33 | } 34 | 35 | @Override 36 | protected Traverser instantiateTraverser(Node start, Node end) { 37 | Transaction transaction = context.transaction(); 38 | 39 | TraversalDescription base = getBaseDescription(transaction, Collections.singletonList(start)); 40 | 41 | return base.expand(expander, stack).evaluator(MonoPathEvaluator.of(end, maxDepth)).traverse(start); 42 | } 43 | 44 | public TraversalDescription getBaseDescription(Transaction transaction, List starts){ 45 | initialStack(starts); 46 | 47 | TraversalDescription base = transaction.traversalDescription(); 48 | if(depthFirst){ 49 | base = base.depthFirst(); 50 | }else{ 51 | base = base.breadthFirst(); 52 | } 53 | 54 | return base.uniqueness(uniqueness()); 55 | } 56 | 57 | public void initialStack(List starts){ 58 | if(stack == null){ 59 | State state = State.newInstance(); 60 | for(Node start:starts){ 61 | String position = (String) start.getProperty("POLLUTED_POSITION", "[]"); 62 | int[][] initialPositions = JsonHelper.parse(position); 63 | state.addInitialPositions(start.getId(), initialPositions); 64 | } 65 | stack = new InitialBranchState.State<>(state, state.copy()); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/tabby/calculator/BackwardCalculator.java: -------------------------------------------------------------------------------- 1 | package tabby.calculator; 2 | 3 | import tabby.util.PositionHelper; 4 | import tabby.util.Transformer; 5 | 6 | import java.util.Arrays; 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | import java.util.stream.Collectors; 10 | 11 | /** 12 | * @author wh1t3p1g 13 | * @since 2022/5/10 14 | */ 15 | public class BackwardCalculator implements Calculator { 16 | 17 | /** 18 | * 返回下一个节点污点状态需求 19 | * 如果返回的是[-2] 说明下一个节点可以强制保留路径 20 | * @param callSite 21 | * @param polluted 22 | * @return 23 | */ 24 | @Override 25 | public int[] v2(int[][] callSite, Set polluted) { 26 | Set newPolluted = new HashSet<>(); 27 | 28 | for(int p : polluted){ 29 | int pos = p + 1; 30 | if(pos < callSite.length && pos >= 0){ 31 | int[] call = callSite[pos]; 32 | if(PositionHelper.isNotPollutedPosition(call)) return null; 33 | newPolluted.addAll(Arrays.stream(call).boxed().collect(Collectors.toSet())); 34 | }else if(p == PositionHelper.SOURCE){ 35 | // 如果仅剩下-2,则后续的节点只需判断是否是source节点即可 36 | newPolluted.add(PositionHelper.SOURCE); 37 | } else{ 38 | return null; 39 | } 40 | } 41 | 42 | newPolluted.remove(PositionHelper.NOT_POLLUTED_POSITION); 43 | return newPolluted.stream().mapToInt(Integer::intValue).toArray(); 44 | } 45 | 46 | /** 47 | * polluted 48 | * [[1],[2,3]] 49 | * 代表 第2个参数 + 第3或4个参数 是可控的,子集合里面为或关系,不同子集为并关系 50 | * @param callSite 51 | * @param polluted 52 | * @return 53 | */ 54 | @Override 55 | public int[][] v3(int[][] callSite, int[][] polluted) { 56 | Set ret = new HashSet<>(); 57 | int len = polluted.length; 58 | for(int i=0;i newPolluted = new HashSet<>(); 60 | int[] pos = polluted[i]; 61 | for(int p:pos){ 62 | int index = p + 1; 63 | if(index < callSite.length && index >= 0){ 64 | // 多个值取或 65 | int[] call = callSite[index]; 66 | if(PositionHelper.isNotPollutedPosition(call)) continue; 67 | newPolluted.addAll(Transformer.intArrayToSet(call)); 68 | }else if(p == PositionHelper.SOURCE){ 69 | newPolluted.add(PositionHelper.SOURCE); 70 | } 71 | } 72 | if(newPolluted.isEmpty()) return null; 73 | newPolluted.remove(PositionHelper.NOT_POLLUTED_POSITION); 74 | ret.add(Transformer.setToIntArray(newPolluted)); 75 | } 76 | 77 | return ret.toArray(new int[0][]); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/tabby/expander/processor/BackwardedProcessor.java: -------------------------------------------------------------------------------- 1 | package tabby.expander.processor; 2 | 3 | import org.neo4j.graphdb.GraphDatabaseService; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.graphdb.Relationship; 6 | import org.neo4j.graphdb.Transaction; 7 | import tabby.calculator.Calculator; 8 | import tabby.data.Cache; 9 | import tabby.data.Pollution; 10 | import tabby.data.TabbyState; 11 | import tabby.util.Types; 12 | 13 | /** 14 | * @author wh1t3p1g 15 | * @since 2022/5/7 16 | */ 17 | public class BackwardedProcessor implements Processor { 18 | 19 | private TabbyState nextState; 20 | private Pollution pollution; 21 | private boolean isCheckType = false; 22 | 23 | public BackwardedProcessor(boolean isCheckType) { 24 | this.isCheckType = isCheckType; 25 | } 26 | 27 | @Override 28 | public void init(Node node, TabbyState preState, Relationship lastRelationship) { 29 | this.nextState = TabbyState.of(); 30 | 31 | if(lastRelationship == null){ 32 | this.pollution = preState.get("node_"+node.getId()); 33 | }else{ 34 | long id = lastRelationship.getId(); 35 | this.pollution = preState.get(String.valueOf(id)); 36 | } 37 | } 38 | 39 | @Override 40 | public Relationship process(Relationship next) { 41 | Relationship ret = null; 42 | String nextId = String.valueOf(next.getId()); 43 | if(Types.isAlias(next)){ 44 | nextState.put(nextId, pollution); 45 | ret = next; 46 | }else{ 47 | Pollution callSite = Cache.rel.get(next, false); 48 | 49 | Pollution nextPollution = Pollution.getNextPollutionReverse(pollution, callSite); 50 | if(nextPollution != null){ 51 | nextState.put(nextId, nextPollution); 52 | ret = next; 53 | } 54 | } 55 | return ret; 56 | } 57 | 58 | @Override 59 | public boolean isNeedProcess() { 60 | return true; 61 | } 62 | 63 | @Override 64 | public TabbyState getNextState() { 65 | return nextState; 66 | } 67 | 68 | @Override 69 | public void setCalculator(Calculator calculator) { 70 | } 71 | 72 | @Override 73 | public boolean isLastRelationshipTypeAlias() { 74 | return false; 75 | } 76 | 77 | @Override 78 | public Processor copy() { 79 | return new BackwardedProcessor(isCheckType); 80 | } 81 | 82 | @Override 83 | public Processor reverse() { 84 | return new ForwardedProcessor(isCheckType); 85 | } 86 | 87 | @Override 88 | public void setDBSource(GraphDatabaseService db) { 89 | 90 | } 91 | 92 | @Override 93 | public void setTransaction(Transaction tx) { 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/tabby/expander/processor/JavaGadgetProcessor.java: -------------------------------------------------------------------------------- 1 | package tabby.expander.processor; 2 | 3 | import lombok.Getter; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.graphdb.Relationship; 6 | import tabby.data.EdgeCache; 7 | import tabby.data.State; 8 | import tabby.util.Types; 9 | 10 | import java.util.Map; 11 | 12 | /** 13 | * @author wh1t3p1g 14 | * @since 2022/5/7 15 | */ 16 | @Getter 17 | public class JavaGadgetProcessor extends BaseProcessor{ 18 | 19 | private boolean isSerializable = false; 20 | private boolean isAbstract = false; 21 | private boolean isStatic = false; 22 | private boolean isFromAbstractClass = false; 23 | private String classname = null; 24 | 25 | @Override 26 | public void init(Node node, State preState, Relationship lastRelationship){ 27 | super.init(node, preState, lastRelationship); 28 | 29 | Map properties = node.getProperties("IS_SERIALIZABLE", "IS_ABSTRACT", "IS_STATIC", "IS_FROM_ABSTRACT_CLASS","CLASSNAME"); 30 | this.isSerializable = (boolean) properties.getOrDefault("IS_SERIALIZABLE", false); 31 | this.isAbstract = (boolean) properties.getOrDefault("IS_ABSTRACT", false); 32 | this.isStatic = (boolean) properties.getOrDefault("IS_STATIC", false); 33 | this.isFromAbstractClass = (boolean) properties.getOrDefault("IS_FROM_ABSTRACT_CLASS", false); 34 | this.classname = (String) properties.getOrDefault("CLASSNAME", "java.lang.Object"); 35 | } 36 | 37 | @Override 38 | public Relationship process(Relationship next){ 39 | Relationship ret = null; 40 | long nextEdgeId = next.getId(); 41 | String key = nextEdgeId + ""; 42 | if(Types.isAlias(next)){ 43 | nextState.put(key, polluted); 44 | nextState.addAliasEdge(nextEdgeId); 45 | if(lastRelationship != null && preState.isStaticCall(lastRelationship.getId())){ 46 | nextState.addStaticCallEdge(nextEdgeId); 47 | } 48 | ret = next; 49 | }else{ 50 | int[][] callSite = EdgeCache.rel.get(next); 51 | int[][] nextPos = calculator.calculate(callSite, polluted); 52 | 53 | if(nextPos != null && nextPos.length > 0){ 54 | nextState.put(key, nextPos); 55 | if(isStatic){ 56 | nextState.addStaticCallEdge(nextEdgeId); 57 | } 58 | ret = next; 59 | } 60 | } 61 | return ret; 62 | } 63 | 64 | @Override 65 | public boolean isNeedProcess() { 66 | if(isSerializable || isStatic || isAbstract || isFromAbstractClass || "java.lang.Object".equals(classname)) return true; 67 | 68 | return lastRelationship != null && preState.isStaticCall(lastRelationship.getId()); // 如果上层是静态调用,则默认允许下一层可以不符合上面的条件 69 | } 70 | 71 | @Override 72 | public Processor copy() { 73 | return new JavaGadgetProcessor(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/tabby/expander/SimplePathExpander.java: -------------------------------------------------------------------------------- 1 | package tabby.expander; 2 | 3 | import org.neo4j.graphdb.*; 4 | import org.neo4j.graphdb.traversal.BranchState; 5 | import org.neo4j.internal.helpers.collection.Iterables; 6 | import tabby.calculator.BackwardCalculator; 7 | import tabby.calculator.ForwardedCalculator; 8 | import tabby.data.State; 9 | import tabby.expander.processor.Processor; 10 | import tabby.util.Types; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Objects; 15 | import java.util.stream.Collectors; 16 | import java.util.stream.StreamSupport; 17 | 18 | /** 19 | * from source to sink 20 | * @author wh1t3p1g 21 | * @since 2022/4/26 22 | */ 23 | public class SimplePathExpander implements PathExpander { 24 | 25 | private final Direction direction; 26 | private final RelationshipType[] relationshipTypes; 27 | private boolean parallel = false; 28 | private boolean isBackward = false; 29 | private Processor processor; 30 | 31 | public SimplePathExpander(Processor processor, boolean parallel, boolean isBackward) { 32 | String[] types; 33 | 34 | this.processor = processor; 35 | this.parallel = parallel; 36 | this.isBackward = isBackward; 37 | 38 | if(isBackward){ 39 | types = new String[]{"", "ALIAS>"}; 43 | this.processor.setCalculator(new ForwardedCalculator()); 44 | } 45 | direction = Types.directionFor(types[0]); 46 | relationshipTypes = new RelationshipType[]{ 47 | Types.relationshipTypeFor(types[0]), 48 | Types.relationshipTypeFor(types[1]) 49 | }; 50 | } 51 | 52 | @Override 53 | public ResourceIterable expand(Path path, BranchState state) { 54 | final Node node = path.endNode(); 55 | final Relationship lastRelationship = path.lastRelationship(); 56 | processor.init(node, state.getState(), lastRelationship); 57 | 58 | if(processor.isNeedProcess()){ 59 | Iterable relationships = node.getRelationships(direction, relationshipTypes); 60 | List nextRelationships = StreamSupport.stream(relationships.spliterator(), parallel) 61 | .map((next) -> processor.process(next)) 62 | .filter(Objects::nonNull) 63 | .collect(Collectors.toList()); 64 | state.setState(processor.getNextState()); 65 | return Iterables.asResourceIterable(nextRelationships); 66 | } 67 | 68 | return Iterables.asResourceIterable(new ArrayList<>()); 69 | } 70 | 71 | @Override 72 | public PathExpander reverse() { 73 | return new SimplePathExpander(processor.copy(), parallel, !isBackward); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/tabby/data/TabbyState.java: -------------------------------------------------------------------------------- 1 | package tabby.data; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.neo4j.graphdb.Node; 6 | 7 | import java.util.*; 8 | 9 | /** 10 | * 继承 上节点对应的状态 和 类型 11 | * Table> 12 | * @author wh1t3p1g 13 | * @since 2023/8/22 14 | */ 15 | @Getter 16 | @Setter 17 | public class TabbyState { 18 | 19 | private Map state = new HashMap<>(); 20 | 21 | public void put(String id, Pollution pollution){ 22 | state.put(id, pollution); 23 | } 24 | 25 | public Pollution get(String id){ 26 | return state.get(id); 27 | } 28 | 29 | public static TabbyState of(){ 30 | return new TabbyState(); 31 | } 32 | 33 | public static TabbyState initialState(Node node, String polluteJson){ 34 | try{ 35 | TabbyState tabbyState = new TabbyState(); 36 | Pollution pollution = new Pollution(); 37 | 38 | if(polluteJson != null){ 39 | pollution.setPollutedFromJson(polluteJson); 40 | }else{ 41 | String json = (String) node.getProperty("POLLUTED_POSITION", "[]"); 42 | pollution.setPollutedFromJson(json); 43 | } 44 | String id = "node_" + node.getId(); 45 | tabbyState.put(id, pollution); 46 | return tabbyState; 47 | }catch (Exception ig){} 48 | return null; 49 | } 50 | 51 | public static TabbyState initialState(Node node){ 52 | try{ 53 | TabbyState tabbyState = new TabbyState(); 54 | String subSignature = (String) node.getProperty("SUB_SIGNATURE", ""); 55 | String classname = (String) node.getProperty("CLASSNAME", ""); 56 | String id = "node_"+node.getId(); 57 | 58 | List> polluted = new LinkedList<>(); 59 | List> types = new LinkedList<>(); 60 | 61 | polluted.add(Set.of(-1)); 62 | types.add(Set.of(classname)); 63 | 64 | String[] parameters = getParameterTypes(subSignature); 65 | int index = 0; 66 | for(String type:parameters){ 67 | polluted.add(Set.of(index++)); 68 | types.add(Set.of(type)); 69 | } 70 | 71 | tabbyState.put(id, Pollution.of(polluted, types, null)); 72 | return tabbyState; 73 | }catch (Exception ig){} 74 | 75 | return null; 76 | } 77 | 78 | public static String[] getParameterTypes(String subSignature){ 79 | int index = subSignature.indexOf("("); 80 | String sub = subSignature.substring(index+1); 81 | sub = sub.substring(0, sub.length()-1); 82 | if(sub.contains(",")){ 83 | return sub.split(","); 84 | }else if(!sub.isEmpty()){ 85 | return new String[]{sub}; 86 | }else{ 87 | return new String[0]; 88 | } 89 | } 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/tabby/algo/BasePathFinding.java: -------------------------------------------------------------------------------- 1 | package tabby.algo; 2 | 3 | import org.neo4j.graphalgo.BasicEvaluationContext; 4 | import org.neo4j.graphalgo.impl.path.TraversalPathFinder; 5 | import org.neo4j.graphdb.*; 6 | import tabby.data.TabbyState; 7 | import tabby.expander.TabbyPathExpander; 8 | import tabby.path.TabbyBidirectionalTraversalPathFinder; 9 | import tabby.path.TabbyTraversalPathFinder; 10 | import tabby.result.PathResult; 11 | 12 | import java.util.stream.Stream; 13 | import java.util.stream.StreamSupport; 14 | 15 | /** 16 | * @author wh1t3p1g 17 | * @project tabby-path-finder 18 | * @since 2023/12/7 19 | */ 20 | public class BasePathFinding { 21 | 22 | public static Stream findPathWithState(Node sourceNode, String direct, Node sinkNode, String state, 23 | int maxNodeLength, boolean isDepthFirst, boolean checkAuth, boolean isCheckType, 24 | GraphDatabaseService db, Transaction tx){ 25 | 26 | PathExpander expander; 27 | TraversalPathFinder algo; 28 | Iterable allPaths; 29 | 30 | if(">".equals(direct)){ 31 | expander = new TabbyPathExpander(false, false, isCheckType, db, tx); 32 | TabbyState initialState = TabbyState.initialState(sourceNode); 33 | TabbyState sinkState = TabbyState.initialState(sinkNode, state); 34 | algo = new TabbyTraversalPathFinder( 35 | new BasicEvaluationContext(tx, db), 36 | expander, initialState, sinkState, 37 | maxNodeLength, isDepthFirst, checkAuth, false); 38 | 39 | allPaths = algo.findAllPaths(sourceNode, sinkNode); 40 | }else if("<".equals(direct)){ 41 | expander = new TabbyPathExpander(false, true, isCheckType, db, tx); 42 | TabbyState initialState = TabbyState.initialState(sinkNode, state); 43 | if(initialState == null){ 44 | maxNodeLength = 0; 45 | } 46 | algo = new TabbyTraversalPathFinder( 47 | new BasicEvaluationContext(tx, db), 48 | expander, initialState, null, 49 | maxNodeLength, isDepthFirst, false, true); 50 | 51 | allPaths = algo.findAllPaths(sinkNode, sourceNode); 52 | }else{ 53 | expander = new TabbyPathExpander(false, false, isCheckType, db, tx); 54 | 55 | TabbyState sourceState = TabbyState.initialState(sourceNode); 56 | TabbyState sinkState = TabbyState.initialState(sinkNode, state); 57 | 58 | algo = new TabbyBidirectionalTraversalPathFinder(new BasicEvaluationContext(tx, db), 59 | expander, sourceState, sinkState, maxNodeLength, isDepthFirst); 60 | 61 | allPaths = algo.findAllPaths(sourceNode, sinkNode); 62 | } 63 | 64 | return StreamSupport.stream(allPaths.spliterator(), true) 65 | .map(PathResult::new); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/tabby/help/Help.java: -------------------------------------------------------------------------------- 1 | package tabby.help; 2 | 3 | import org.neo4j.graphdb.Transaction; 4 | import org.neo4j.procedure.Context; 5 | import org.neo4j.procedure.Description; 6 | import org.neo4j.procedure.Name; 7 | import org.neo4j.procedure.Procedure; 8 | import tabby.data.EdgeCache; 9 | import tabby.result.HelpResult; 10 | import tabby.result.StringResult; 11 | 12 | import java.util.HashSet; 13 | import java.util.Map; 14 | import java.util.Set; 15 | import java.util.stream.Stream; 16 | 17 | import static org.neo4j.internal.helpers.collection.MapUtil.map; 18 | 19 | /** 20 | * copy from apoc.help 21 | * @author wh1t3p1g 22 | * @since 2022/1/6 23 | */ 24 | public class Help { 25 | 26 | @Context 27 | public Transaction tx; 28 | 29 | private static final Set extended = new HashSet<>(); 30 | 31 | @Procedure("tabby.help") 32 | @Description("Provides descriptions of available procedures. To narrow the results, supply a search string. To also search in the description text, append + to the end of the search string.") 33 | public Stream info(@Name("proc") String name) throws Exception { 34 | boolean searchText = false; 35 | if (name != null) { 36 | name = name.trim(); 37 | if (name.endsWith("+")) { 38 | name = name.substring(0, name.lastIndexOf('+')).trim(); 39 | searchText = true; 40 | } 41 | } 42 | String filter = " WHERE name starts with 'tabby.' " + 43 | " AND ($name IS NULL OR toLower(name) CONTAINS toLower($name) " + 44 | " OR ($desc IS NOT NULL AND toLower(description) CONTAINS toLower($desc))) "; 45 | 46 | String proceduresQuery = "SHOW PROCEDURES yield name, description, signature " + filter + 47 | "RETURN 'procedure' as type, name, description, signature "; 48 | 49 | String functionsQuery = "SHOW FUNCTIONS yield name, description, signature " + filter + 50 | "RETURN 'function' as type, name, description, signature "; 51 | Map params = map( "name", name, "desc", searchText ? name : null ); 52 | Stream> proceduresResults = tx.execute( proceduresQuery, params ).stream(); 53 | Stream> functionsResults = tx.execute( functionsQuery, params ).stream(); 54 | 55 | return Stream.of( proceduresResults, functionsResults ).flatMap( results -> results.map( 56 | row -> new HelpResult( row, !extended.contains( (String) row.get( "name" ) ) ) ) ); 57 | } 58 | 59 | @Procedure("tabby.version") 60 | @Description("tabby path finder version") 61 | public Stream version() throws Exception { 62 | StringResult result = new StringResult("version 1.0, 20230215"); 63 | return Stream.of(result); 64 | } 65 | 66 | @Procedure("tabby.cache.count") 67 | @Description("tabby path finder version") 68 | public Stream count() throws Exception { 69 | StringResult result = new StringResult("cache count: "+ EdgeCache.rel.size()); 70 | return Stream.of(result); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/tabby/evaluator/TabbyEvaluator.java: -------------------------------------------------------------------------------- 1 | package tabby.evaluator; 2 | 3 | import org.neo4j.graphdb.Node; 4 | import org.neo4j.graphdb.Path; 5 | import org.neo4j.graphdb.Relationship; 6 | import org.neo4j.graphdb.traversal.BranchState; 7 | import org.neo4j.graphdb.traversal.Evaluation; 8 | import org.neo4j.graphdb.traversal.PathEvaluator; 9 | import tabby.data.Pollution; 10 | import tabby.data.TabbyState; 11 | 12 | /** 13 | * @author wh1t3p1g 14 | * @since 2023/8/24 15 | */ 16 | public class TabbyEvaluator extends PathEvaluator.Adapter{ 17 | 18 | private Node endNode; 19 | private int maxDepth; 20 | private boolean checkAuth; 21 | private boolean isBackward; 22 | private Pollution endPol = null; 23 | 24 | public TabbyEvaluator(Node endNode, TabbyState endState, int maxDepth, boolean checkAuth, boolean isBackward) { 25 | this.endNode = endNode; 26 | this.maxDepth = maxDepth; 27 | this.checkAuth = checkAuth; 28 | this.isBackward = isBackward; 29 | if(endState != null){ 30 | this.endPol = endState.get("node_"+endNode.getId()); 31 | } 32 | } 33 | 34 | public static TabbyEvaluator of(Node endNode, TabbyState endState, int maxDepth, boolean checkAuth, boolean isBackward){ 35 | return new TabbyEvaluator(endNode, endState, maxDepth, checkAuth, isBackward); 36 | } 37 | @Override 38 | public Evaluation evaluate(Path path, BranchState branchState) { 39 | boolean includes = true; 40 | boolean continues = true; 41 | int length = path.length(); 42 | Node node = path.endNode(); 43 | 44 | if(length >= maxDepth){ 45 | continues = false; // 超出长度 不继续进行 46 | if(endNode != null && !endNode.equals(node)){ 47 | includes = false; // 最后的节点不是endNode,不保存当前结果 48 | } 49 | }else if(length == 0){ // 开始节点 50 | includes = false; 51 | } else if(endNode != null && endNode.equals(node)){ 52 | // 长度没到,但已经找到了endNode,停止进行 53 | continues = false; 54 | if(checkAuth){ 55 | TabbyState state = branchState.getState(); 56 | Relationship edge = path.lastRelationship(); 57 | if(edge != null && state != null){ 58 | String id = String.valueOf(edge.getId()); 59 | Pollution pollution = state.get(id); 60 | includes = pollution.isContainsAuth(); 61 | } 62 | } 63 | 64 | if(includes && !isBackward && endPol != null){ 65 | TabbyState state = branchState.getState(); 66 | Relationship edge = path.lastRelationship(); 67 | if(edge != null && state != null){ 68 | String id = String.valueOf(edge.getId()); 69 | Pollution pollution = state.get(id); 70 | includes = Pollution.compare(pollution, endPol); 71 | } 72 | } 73 | } else { 74 | includes = false; 75 | } 76 | 77 | return Evaluation.of(includes, continues); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/tabby/expander/TabbyPathExpander.java: -------------------------------------------------------------------------------- 1 | package tabby.expander; 2 | 3 | import org.neo4j.graphdb.*; 4 | import org.neo4j.graphdb.traversal.BranchState; 5 | import org.neo4j.internal.helpers.collection.Iterables; 6 | import tabby.data.TabbyState; 7 | import tabby.expander.processor.BackwardedProcessor; 8 | import tabby.expander.processor.ForwardedProcessor; 9 | import tabby.expander.processor.Processor; 10 | import tabby.util.Types; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Objects; 15 | import java.util.stream.Collectors; 16 | import java.util.stream.StreamSupport; 17 | 18 | /** 19 | * from source to sink 20 | * @author wh1t3p1g 21 | * @since 2022/4/26 22 | */ 23 | public class TabbyPathExpander implements PathExpander { 24 | 25 | private final Direction direction; 26 | private final RelationshipType[] relationshipTypes; 27 | private boolean parallel = false; 28 | private boolean isBackward = false; 29 | private boolean isCheckType = false; 30 | private Processor processor; 31 | private GraphDatabaseService db; 32 | private Transaction tx; 33 | 34 | public TabbyPathExpander(boolean parallel, boolean isBackward, boolean isCheckType, GraphDatabaseService db, Transaction tx) { 35 | String[] types; 36 | 37 | this.parallel = parallel; 38 | this.isBackward = isBackward; 39 | this.isCheckType = isCheckType; 40 | this.db = db; 41 | this.tx = tx; 42 | 43 | if(isBackward){ 44 | this.processor = new BackwardedProcessor(isCheckType); 45 | types = new String[]{"", "ALIAS>"}; 51 | } 52 | 53 | direction = Types.directionFor(types[0]); 54 | relationshipTypes = new RelationshipType[]{ 55 | Types.relationshipTypeFor(types[0]), 56 | Types.relationshipTypeFor(types[1]) 57 | }; 58 | } 59 | 60 | @Override 61 | public ResourceIterable expand(Path path, BranchState state) { 62 | final Node node = path.endNode(); 63 | final Relationship lastRelationship = path.lastRelationship(); 64 | processor.init(node, state.getState(), lastRelationship); 65 | 66 | if(processor.isNeedProcess()){ 67 | Iterable relationships = node.getRelationships(direction, relationshipTypes); 68 | List nextRelationships = StreamSupport.stream(relationships.spliterator(), parallel) 69 | .map((next) -> processor.process(next)) 70 | .filter(Objects::nonNull) 71 | .collect(Collectors.toList()); 72 | state.setState(processor.getNextState()); 73 | return Iterables.asResourceIterable(nextRelationships); 74 | } 75 | 76 | return Iterables.asResourceIterable(new ArrayList<>()); 77 | } 78 | 79 | @Override 80 | public PathExpander reverse() { 81 | return new TabbyPathExpander(parallel, !isBackward, isCheckType, db, tx); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/tabby/algo/beta/PathFinding.java: -------------------------------------------------------------------------------- 1 | package tabby.algo.beta; 2 | 3 | import org.neo4j.graphdb.GraphDatabaseService; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.graphdb.Transaction; 6 | import org.neo4j.procedure.Context; 7 | import org.neo4j.procedure.Description; 8 | import org.neo4j.procedure.Name; 9 | import org.neo4j.procedure.Procedure; 10 | import tabby.algo.BasePathFinding; 11 | import tabby.result.PathResult; 12 | 13 | import java.util.stream.Stream; 14 | 15 | /** 16 | * @author wh1t3p1g 17 | * @since 2022/1/6 18 | */ 19 | public class PathFinding extends BasePathFinding { 20 | 21 | @Context 22 | public GraphDatabaseService db; 23 | 24 | @Context 25 | public Transaction tx; 26 | 27 | @Procedure("tabby.beta.findPath") 28 | @Description("tabby.beta.findPath(source, direct, sink, maxNodeLength, isDepthFirst) YIELD path, weight" + 29 | " - using findPath to get source-sink path with maxNodeLength") 30 | public Stream findPath(@Name("source") Node sourceNode, 31 | @Name("direct") String direct, 32 | @Name("sink") Node sinkNode, 33 | @Name("maxNodeLength") Long maxNodeLength, 34 | @Name("isDepthFirst") boolean isDepthFirst){ 35 | return findPathWithState(sourceNode, direct, sinkNode, null, maxNodeLength.intValue(), isDepthFirst, false, true, db, tx); 36 | } 37 | 38 | /** 39 | * 后向分析,需提供 sink 函数的 污点数据 40 | * 不提供前向分析,即source to sink 41 | * @param sourceNode 42 | * @param sinkNode 43 | * @param state 44 | * @param maxNodeLength 45 | * @param isDepthFirst 46 | * @return 47 | */ 48 | @Procedure("tabby.beta.findPathWithState") 49 | @Description("tabby.beta.findPathWithState(source, direct, sink, sinkState, maxNodeLength, isDepthFirst) YIELD path, weight" + 50 | " - using findPath to get source-sink path with maxNodeLength and state") 51 | public Stream findPathWithState(@Name("source") Node sourceNode, 52 | @Name("direct") String direct, 53 | @Name("sink") Node sinkNode, 54 | @Name("state") String state, 55 | @Name("maxNodeLength") Long maxNodeLength, 56 | @Name("isDepthFirst") boolean isDepthFirst){ 57 | return findPathWithState(sourceNode, direct, sinkNode, state, maxNodeLength.intValue(), isDepthFirst, false, true, db, tx); 58 | } 59 | 60 | @Procedure("tabby.beta.findPathWithAuth") 61 | @Description("tabby.beta.findPathWithAuth(source, sink, maxNodeLength, isDepthFirst) YIELD path, weight" + 62 | " - using findPath to get source-sink path with maxNodeLength") 63 | public Stream findPathWithAuth(@Name("source") Node sourceNode, 64 | @Name("sink") Node sinkNode, 65 | @Name("maxNodeLength") Long maxNodeLength, 66 | @Name("isDepthFirst") boolean isDepthFirst){ 67 | 68 | return findPathWithState(sourceNode, ">", sinkNode, null, maxNodeLength.intValue(), isDepthFirst, true, true, db, tx); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/tabby/expander/processor/ForwardedProcessor.java: -------------------------------------------------------------------------------- 1 | package tabby.expander.processor; 2 | 3 | import org.neo4j.graphdb.GraphDatabaseService; 4 | import org.neo4j.graphdb.Node; 5 | import org.neo4j.graphdb.Relationship; 6 | import org.neo4j.graphdb.Transaction; 7 | import tabby.calculator.Calculator; 8 | import tabby.data.Cache; 9 | import tabby.data.Pollution; 10 | import tabby.data.TabbyState; 11 | import tabby.util.Types; 12 | 13 | /** 14 | * @author wh1t3p1g 15 | * @since 2022/5/7 16 | */ 17 | public class ForwardedProcessor implements Processor { 18 | 19 | private TabbyState nextState; 20 | private Pollution pollution; 21 | private boolean isCheckType = false; 22 | private GraphDatabaseService db; 23 | private Transaction tx; 24 | 25 | public ForwardedProcessor(boolean isCheckType) { 26 | this.isCheckType = isCheckType; 27 | } 28 | 29 | @Override 30 | public void init(Node node, TabbyState preState, Relationship lastRelationship) { 31 | this.nextState = TabbyState.of(); 32 | 33 | if(lastRelationship == null){ 34 | this.pollution = preState.get("node_"+node.getId()); 35 | }else{ 36 | long id = lastRelationship.getId(); 37 | this.pollution = preState.get(String.valueOf(id)); 38 | } 39 | } 40 | 41 | @Override 42 | public Relationship process(Relationship next) { 43 | Relationship ret = null; 44 | String nextId = String.valueOf(next.getId()); 45 | if(Types.isAlias(next)){ 46 | if(isCheckType){ 47 | Node start = next.getStartNode(); 48 | Node end = next.getEndNode(); 49 | Pollution nextPollution = Pollution.pure(start, end, pollution, db, tx); 50 | if(nextPollution != null){ 51 | nextState.put(nextId, nextPollution); 52 | ret = next; 53 | } 54 | }else{ 55 | nextState.put(nextId, pollution); 56 | ret = next; 57 | } 58 | }else{ 59 | Pollution callSite = Cache.rel.get(next, isCheckType); 60 | 61 | Pollution nextPollution = Pollution.getNextPollution(pollution, callSite, isCheckType); 62 | if(nextPollution != null){ 63 | nextState.put(nextId, nextPollution); 64 | ret = next; 65 | } 66 | } 67 | return ret; 68 | } 69 | 70 | @Override 71 | public boolean isNeedProcess() { 72 | return true; 73 | } 74 | 75 | @Override 76 | public TabbyState getNextState() { 77 | return nextState; 78 | } 79 | 80 | @Override 81 | public void setCalculator(Calculator calculator) { 82 | } 83 | 84 | @Override 85 | public boolean isLastRelationshipTypeAlias() { 86 | return false; 87 | } 88 | 89 | @Override 90 | public Processor copy() { 91 | return new ForwardedProcessor(isCheckType); 92 | } 93 | 94 | @Override 95 | public Processor reverse() { 96 | return new BackwardedProcessor(isCheckType); 97 | } 98 | 99 | @Override 100 | public void setDBSource(GraphDatabaseService db) { 101 | this.db = db; 102 | } 103 | 104 | @Override 105 | public void setTransaction(Transaction tx) { 106 | this.tx = tx; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | tabby 8 | tabby-path-finder 9 | 1.1 10 | 11 | 12 | 5.9.0 13 | 5.7.0 14 | 15 | 16 | 17 | 18 | 19 | com.google.code.gson 20 | gson 21 | 2.9.0 22 | 23 | 24 | org.projectlombok 25 | lombok 26 | 1.18.24 27 | provided 28 | 29 | 30 | 35 | org.neo4j 36 | neo4j 37 | ${neo4j.version} 38 | provided 39 | 40 | 41 | org.neo4j.procedure 42 | apoc-core 43 | 5.4.1 44 | provided 45 | 46 | 47 | org.neo4j.procedure 48 | apoc-common 49 | 5.4.1 50 | provided 51 | 52 | 53 | 54 | 55 | com.google.guava 56 | guava 57 | 32.1.2-jre 58 | 59 | 60 | 61 | 62 | 64 | org.neo4j.test 65 | neo4j-harness 66 | ${neo4j.version} 67 | test 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.junit.jupiter 80 | junit-jupiter-engine 81 | ${junit-jupiter.version} 82 | test 83 | 84 | 85 | 86 | 87 | 88 | 89 | maven-compiler-plugin 90 | 3.10.1 91 | 92 | 17 93 | 17 94 | 95 | 96 | 97 | org.apache.maven.plugins 98 | maven-shade-plugin 99 | 3.2.4 100 | 101 | false 102 | 103 | 104 | 105 | package 106 | 107 | shade 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /src/main/java/tabby/algo/release/JavaGadgetPathFinding.java: -------------------------------------------------------------------------------- 1 | package tabby.algo.release; 2 | 3 | import org.neo4j.graphalgo.BasicEvaluationContext; 4 | import org.neo4j.graphdb.*; 5 | import org.neo4j.procedure.Context; 6 | import org.neo4j.procedure.Description; 7 | import org.neo4j.procedure.Name; 8 | import org.neo4j.procedure.Procedure; 9 | import tabby.data.State; 10 | import tabby.expander.SimplePathExpander; 11 | import tabby.expander.processor.ProcessorFactory; 12 | import tabby.path.MonoDirectionalTraversalPathFinder; 13 | import tabby.result.PathResult; 14 | import tabby.util.JsonHelper; 15 | import tabby.util.PositionHelper; 16 | 17 | import java.util.Map; 18 | import java.util.stream.Stream; 19 | import java.util.stream.StreamSupport; 20 | 21 | /** 22 | * @author wh1t3p1g 23 | * @since 2022/1/6 24 | */ 25 | public class JavaGadgetPathFinding { 26 | 27 | @Context 28 | public GraphDatabaseService db; 29 | 30 | @Context 31 | public Transaction tx; 32 | 33 | @Procedure("tabby.algo.findJavaGadget") 34 | @Description("tabby.algo.findJavaGadget(source, direct, sink, maxNodeLength, isDepthFirst) YIELD path, weight" + 35 | " - using findJavaGadget to get source-sink-gadget path") 36 | public Stream findJavaGadget( 37 | @Name("source") Node sourceNode, 38 | @Name("direct") String direct, 39 | @Name("sink") Node sinkNode, 40 | @Name("maxNodeLength") Long maxNodeLength, 41 | @Name("isDepthFirst") boolean isDepthFirst) { 42 | 43 | return findJavaGadgetWithState(sourceNode, direct, sinkNode, null, maxNodeLength, isDepthFirst); 44 | } 45 | 46 | @Procedure("tabby.algo.findJavaGadgetWithState") 47 | @Description("tabby.algo.findJavaGadgetWithState(source, direct, sink, sinkState, maxNodeLength, isDepthFirst) YIELD path, weight" + 48 | " - using findJavaGadget to get source-sink-gadget path with sink state") 49 | public Stream findJavaGadgetWithState( 50 | @Name("source") Node sourceNode, 51 | @Name("direct") String direct, 52 | @Name("sink") Node sinkNode, 53 | @Name("sinkState") String state, 54 | @Name("maxNodeLength") Long maxNodeLength, 55 | @Name("isDepthFirst") boolean isDepthFirst) { 56 | boolean isBackward = "<".equals(direct); 57 | String processor = isBackward ? "JavaGadgetBackward":"JavaGadget"; 58 | 59 | PathExpander expander = new SimplePathExpander( 60 | ProcessorFactory.newInstance(processor), false, isBackward); 61 | 62 | State initialState; 63 | 64 | if(isBackward){ 65 | initialState = getSinkInitialState(sinkNode, state); 66 | }else{ 67 | initialState = getSourceInitialState(sourceNode); 68 | } 69 | 70 | MonoDirectionalTraversalPathFinder algo = new MonoDirectionalTraversalPathFinder( 71 | new BasicEvaluationContext(tx, db), 72 | expander, maxNodeLength.intValue(), initialState, isDepthFirst 73 | ); 74 | 75 | Iterable allPaths; 76 | if(isBackward){ 77 | allPaths = algo.findAllPaths(sinkNode, sourceNode); 78 | }else{ 79 | allPaths = algo.findAllPaths(sourceNode, sinkNode); 80 | } 81 | return StreamSupport.stream(allPaths.spliterator(), true) 82 | .map(PathResult::new); 83 | } 84 | 85 | /** 86 | * 为source节点生成初始状态 87 | * @param node 88 | * @return 89 | */ 90 | public State getSourceInitialState(Node node){ 91 | State state = State.newInstance(); 92 | long parameterSize = 0; 93 | try{ 94 | parameterSize = (long) node.getProperty("PARAMETER_SIZE", 0); 95 | }catch (Exception ignore){} 96 | 97 | int initSize = (int) parameterSize + 1; 98 | int[][] initialPositions = new int[initSize][]; 99 | initialPositions[0] = new int[]{PositionHelper.THIS}; // check this 100 | for(int i=0; i properties = node.getProperties("IS_SINK", "POLLUTED_POSITION"); 124 | boolean isSink = (boolean) properties.getOrDefault("IS_SINK", false); 125 | if(isSink){ 126 | polluted = (String) properties.getOrDefault("POLLUTED_POSITION", "[]"); 127 | state.addInitialPositions(node.getId(), JsonHelper.parse(polluted)); 128 | } 129 | }else{ 130 | state.addInitialPositions(node.getId(), initialPositions); 131 | } 132 | 133 | return state; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/tabby/data/Pollution.java: -------------------------------------------------------------------------------- 1 | package tabby.data; 2 | 3 | import com.google.common.collect.MultimapBuilder; 4 | import com.google.common.collect.SetMultimap; 5 | import com.google.gson.reflect.TypeToken; 6 | import lombok.Data; 7 | import org.neo4j.graphdb.*; 8 | import tabby.util.JsonHelper; 9 | import tabby.util.PositionHelper; 10 | 11 | import java.lang.reflect.Type; 12 | import java.util.*; 13 | 14 | /** 15 | * @author wh1t3p1g 16 | * @since 2023/8/23 17 | */ 18 | @Data 19 | public class Pollution { 20 | 21 | private List> polluted = new LinkedList<>(); 22 | 23 | private List> types = new LinkedList<>(); 24 | 25 | // private Map> specialPolluted = new HashMap<>(); 26 | private SetMultimap specialPolluted = MultimapBuilder.hashKeys().hashSetValues().build(); 27 | private boolean isCallerInstanceObj = false; 28 | 29 | public void setPollutedFromJson(String json){ 30 | Type objectType = new TypeToken>>(){}.getType(); 31 | List> p = JsonHelper.parseObject(json, objectType); 32 | if(p == null){ 33 | // 适配一维数组 34 | objectType = new TypeToken>(){}.getType(); 35 | Set pp = JsonHelper.parseObject(json, objectType); 36 | if(pp != null){ 37 | p = new ArrayList<>(); 38 | p.add(pp); 39 | } 40 | } 41 | 42 | setPolluted(p); 43 | } 44 | 45 | public void setTypesFromJson(String json){ 46 | Type objectType = new TypeToken>>(){}.getType(); 47 | List> p = JsonHelper.parseObject(json, objectType); 48 | if(p != null){ 49 | setTypes(p); 50 | } 51 | } 52 | 53 | public Set getFlatPolluted(){ 54 | Set ret = new HashSet<>(); 55 | for(Set p:polluted){ 56 | ret.addAll(p); 57 | } 58 | return ret; 59 | } 60 | 61 | public Set getTypes(int index){ 62 | if(types.size() <= index) return new HashSet<>(); 63 | return types.get(index); 64 | } 65 | 66 | public boolean isContainsAuth(){ 67 | Set flatSet = getFlatPolluted(); 68 | 69 | if(flatSet.contains(PositionHelper.AUTH)){ 70 | return true; 71 | }else{ 72 | for(int index:flatSet){ 73 | if(specialPolluted.containsKey(index)){ 74 | return true; 75 | } 76 | } 77 | } 78 | return false; 79 | } 80 | 81 | // 正向算法 forward,生成下一个 pollution 82 | public static Pollution getNextPollution(Pollution pre, Pollution cur, boolean isCheckType){ 83 | List> next = new LinkedList<>(); 84 | List> nextTypes = new LinkedList<>(); 85 | SetMultimap specialPolluted = MultimapBuilder.hashKeys().hashSetValues().build(); 86 | SetMultimap preSpecialPolluted = pre.getSpecialPolluted(); 87 | 88 | Set pol = pre.getFlatPolluted(); 89 | pol.add(PositionHelper.SOURCE); 90 | pol.add(PositionHelper.AUTH); 91 | pol.remove(PositionHelper.NOT_POLLUTED_POSITION); 92 | 93 | List> callSite = cur.getPolluted(); 94 | int length = callSite.size(); 95 | for(int index = 0; index < length; index++){ 96 | Set sub = callSite.get(index); 97 | if(sub.contains(PositionHelper.AUTH)){ // 创建新的指向 98 | specialPolluted.put(index-1, PositionHelper.AUTH); 99 | } 100 | boolean flag = true; 101 | Set newTypes = new HashSet<>(); 102 | newTypes.addAll(cur.getTypes(index)); 103 | for(int p:sub){ 104 | if(pol.contains(p)){ 105 | if(flag){ 106 | next.add(Set.of(index-1)); 107 | flag = false; 108 | } 109 | } 110 | 111 | if(isCheckType){ 112 | int pos = p+1; 113 | if(pos < pre.getTypes().size() && pos >= 0){ 114 | if(index != 0 || !cur.isCallerInstanceObj()){ 115 | newTypes.addAll(pre.getTypes(pos)); 116 | } 117 | } 118 | } 119 | 120 | if(preSpecialPolluted.containsKey(p)){ 121 | specialPolluted.putAll(index-1, preSpecialPolluted.get(p)); 122 | } 123 | } 124 | if(isCheckType && newTypes.size() > 0){ 125 | nextTypes.add(newTypes); 126 | } 127 | } 128 | 129 | if(next.isEmpty()){ 130 | return null; 131 | } 132 | 133 | return Pollution.of(next, nextTypes, specialPolluted); 134 | } 135 | 136 | // backward 137 | public static Pollution getNextPollutionReverse(Pollution pre, Pollution cur){ 138 | if(pre.isEmpty()) return null; 139 | List> nextPolluted = new LinkedList<>(); 140 | List> callSite = cur.getPolluted(); 141 | List> prePolluted = pre.getPolluted(); 142 | int length = callSite.size(); 143 | boolean flag = true; 144 | for(Set prePol: prePolluted){ 145 | Set temp = new HashSet<>(); 146 | for(int p : prePol){ 147 | int pos = p + 1; 148 | if(length > pos && pos >= 0){ 149 | Set curSite = callSite.get(pos); 150 | if(curSite != null){ 151 | temp.addAll(curSite); 152 | } 153 | }else if(p == PositionHelper.AUTH || p == PositionHelper.SOURCE){ 154 | temp.add(p); 155 | } 156 | } 157 | if(temp.isEmpty() || (temp.size() == 1 && temp.contains(PositionHelper.NOT_POLLUTED_POSITION))){ 158 | flag = false; 159 | break; 160 | }else{ 161 | temp.remove(PositionHelper.NOT_POLLUTED_POSITION); 162 | nextPolluted.add(temp); 163 | } 164 | } 165 | if(flag){ 166 | return Pollution.of(nextPolluted, null, null); 167 | } 168 | 169 | return null; 170 | } 171 | 172 | public boolean isEmpty(){ 173 | return polluted.isEmpty(); 174 | } 175 | 176 | public static Pollution of(Relationship edge, boolean isCheckType){ 177 | try{ 178 | Pollution pollution = new Pollution(); 179 | if(isCheckType){ 180 | Map properties = edge.getProperties("POLLUTED_POSITION", "IS_CALLER_THIS_FIELD_OBJ", "TYPES"); 181 | pollution.setPollutedFromJson((String) properties.getOrDefault("POLLUTED_POSITION", "[]")); 182 | pollution.setCallerInstanceObj((boolean) properties.getOrDefault("IS_CALLER_THIS_FIELD_OBJ", false)); 183 | pollution.setTypesFromJson((String) properties.getOrDefault("TYPES", "[]")); 184 | }else{ 185 | pollution.setPollutedFromJson((String) edge.getProperty("POLLUTED_POSITION", "[]")); 186 | } 187 | return pollution; 188 | }catch (Exception ig){} 189 | 190 | return null; 191 | } 192 | 193 | public static Pollution of(List> polluted, List> types, SetMultimap specialPolluted){ 194 | Pollution pollution = new Pollution(); 195 | pollution.setPolluted(polluted); 196 | pollution.setTypes(types); 197 | if(specialPolluted != null){ 198 | pollution.setSpecialPolluted(specialPolluted); 199 | } 200 | return pollution.copy(); 201 | } 202 | 203 | public Pollution copy(){ 204 | Pollution copied = new Pollution(); 205 | Type objectType = new TypeToken>>(){}.getType(); 206 | copied.setPolluted(JsonHelper.deepCopy(polluted, objectType)); 207 | objectType = new TypeToken>>(){}.getType(); 208 | copied.setTypes(JsonHelper.deepCopy(types, objectType)); 209 | Map> map = specialPolluted.asMap(); 210 | 211 | for(Map.Entry> entry: map.entrySet()){ 212 | copied.specialPolluted.putAll(entry.getKey(), entry.getValue()); 213 | } 214 | return copied; 215 | } 216 | 217 | public static boolean compare(Pollution pre, Pollution cur){ 218 | List> sinkPol = cur.getPolluted(); 219 | Set callPol = pre.getFlatPolluted(); 220 | callPol.add(PositionHelper.SOURCE); 221 | callPol.add(PositionHelper.AUTH); 222 | callPol.remove(PositionHelper.NOT_POLLUTED_POSITION); 223 | 224 | for(Set sink:sinkPol){ 225 | boolean flag = true; 226 | for(int p:sink){ 227 | if(callPol.contains(p)){ 228 | flag = false; 229 | break; 230 | } 231 | } 232 | if(flag){ 233 | return false; 234 | } 235 | } 236 | return true; 237 | } 238 | 239 | public static Pollution pure(Node start, Node end, Pollution cur, GraphDatabaseService db, Transaction tx){ 240 | Pollution nextPollution = cur.copy(); 241 | 242 | // pure types 243 | List> types = nextPollution.getTypes(); 244 | 245 | if(!types.isEmpty()){ 246 | Set baseObjTypes = types.get(0); 247 | if(baseObjTypes.size() > 0 && !baseObjTypes.contains("java.lang.Object")){ 248 | try{ 249 | String startNodeClazz = (String) start.getProperty("CLASSNAME"); 250 | if(startNodeClazz != null && !startNodeClazz.isBlank()){ 251 | baseObjTypes.remove(startNodeClazz); 252 | } 253 | 254 | if(baseObjTypes.size() > 0){ 255 | Map data = end.getProperties("IS_ABSTRACT", "CLASSNAME"); 256 | boolean isAbstract = (boolean) data.getOrDefault("IS_ABSTRACT", false); 257 | String endNodeClazz = (String) data.getOrDefault("CLASSNAME", null); 258 | 259 | if(!isAbstract && endNodeClazz != null && !endNodeClazz.isBlank() && !baseObjTypes.contains(endNodeClazz)){ 260 | // 如果下一个节点还是abstract,则可能下一条边还是alias,继续往下传 261 | // 如果下一个节点不是abstract,并且当前types里面没有当前的endNode的类型,则不继续往下传 262 | try{ 263 | Label label = Label.label("Class"); 264 | Node classNode = tx.findNode(label, "NAME", endNodeClazz); 265 | if(classNode != null){ 266 | String childClassnames = (String) classNode.getProperty("CHILD_CLASSNAMES", ""); 267 | boolean flag = true; 268 | for(String objType:baseObjTypes){ 269 | if(childClassnames.contains(objType)){ 270 | flag = false; 271 | break; 272 | } 273 | } 274 | if(flag) return null; 275 | } 276 | }catch (Exception ignore){ 277 | return null; 278 | } 279 | } 280 | } 281 | }catch (Exception ig){} 282 | } 283 | nextPollution.setTypes(types); 284 | } 285 | 286 | return nextPollution; 287 | } 288 | 289 | } 290 | --------------------------------------------------------------------------------