├── settings.gradle ├── webservice-skip-sources.demo ├── .gitignore ├── fastjson-slinks.demo ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ └── java │ └── gadgetinspector │ ├── data │ ├── DataFactory.java │ ├── CustomSlink.java │ ├── SlinkReference.java │ ├── Source.java │ ├── SlinkFactory.java │ ├── GraphCall.java │ ├── DataLoader.java │ ├── InheritanceMap.java │ ├── MethodReference.java │ ├── ClassReference.java │ └── InheritanceDeriver.java │ ├── SlinkDiscovery.java │ ├── ImplementationFinder.java │ ├── sqlinject │ ├── mybatis │ │ └── MapperXMLDiscovery.java │ ├── SQLInjectSerializableDecider.java │ ├── SQLInjectImplementationFinder.java │ └── SQLInjectSourceDiscovery.java │ ├── SerializableDecider.java │ ├── fastjson │ ├── FastjsonSerializableDecider.java │ ├── FastjsonImplementationFinder.java │ └── FastjsonSourceDiscovery.java │ ├── newxstream │ ├── NewXstremImplementationFinder.java │ ├── NewXstremSerializableDecider.java │ └── NewXstreamSourceDiscovery.java │ ├── config │ ├── ConfigRepository.java │ ├── GIConfig.java │ ├── NewXstreamDeserializationConfig.java │ ├── XstreamDeserializationConfig.java │ ├── JacksonDeserializationConfig.java │ ├── HessianDeserializationConfig.java │ ├── JavaDeserializationConfig.java │ ├── FastjsonDeserializationConfig.java │ ├── SQLInjectDeserializationConfig.java │ └── WebserviceDeserializationConfig.java │ ├── ConfigHelper.java │ ├── jackson │ ├── JacksonImplementationFinder.java │ ├── JacksonSerializableDecider.java │ └── JacksonSourceDiscovery.java │ ├── hessian │ ├── HessianImplementationFinder.java │ ├── HessianSerializableDecider.java │ └── HessianSourceDiscovery.java │ ├── javaserial │ ├── SimpleImplementationFinder.java │ ├── SimpleSerializableDecider.java │ └── SimpleSourceDiscovery.java │ ├── webservice │ ├── WebserviceSerializableDecider.java │ ├── WebserviceImplementationFinder.java │ └── WebserviceSourceDiscovery.java │ ├── SourceDiscovery.java │ ├── xstream │ ├── XstreamSerializableDecider.java │ └── CustomXstreamSerializableDecider.java │ ├── ClassResourceEnumerator.java │ ├── Util.java │ ├── MethodDiscovery.java │ ├── MavenCrawerPopular.java │ ├── MavenCrawer.java │ ├── CallGraphDiscovery.java │ └── GadgetInspector.java ├── LICENSE ├── jackson-skip-sources.demo ├── fastjson-skip-sources.demo ├── gradlew └── README.md /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='gadget-inspector' 2 | -------------------------------------------------------------------------------- /webservice-skip-sources.demo: -------------------------------------------------------------------------------- 1 | org/springframework/ 2 | javax/xml/ws/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /.gradle/ 3 | /.idea/ 4 | /*.dat 5 | /*.bak 6 | /history 7 | /gadget-result 8 | /conf 9 | -------------------------------------------------------------------------------- /fastjson-slinks.demo: -------------------------------------------------------------------------------- 1 | java/rmi/registry/Registry lookup (Ljava/lang/String;)Ljava/rmi/Remote; 2 | javax/naming/Context lookup -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/product/gadgetinspector/main/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/data/DataFactory.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.data; 2 | 3 | public interface DataFactory { 4 | T parse(String[] fields); 5 | String[] serialize(T obj); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/SlinkDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | /** 4 | * @author xuanyh 5 | */ 6 | public interface SlinkDiscovery { 7 | 8 | void discover(); 9 | 10 | void save(); 11 | } 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStorePath=wrapper/dists 5 | zipStoreBase=GRADLE_USER_HOME 6 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/ImplementationFinder.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | import gadgetinspector.data.MethodReference; 4 | 5 | import java.util.Set; 6 | 7 | public interface ImplementationFinder { 8 | Set getImplementations(MethodReference.Handle target); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/sqlinject/mybatis/MapperXMLDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.sqlinject.mybatis; 2 | 3 | import gadgetinspector.SlinkDiscovery; 4 | 5 | /** 6 | * 扫描MyBatis XML和mapper接口,确定slink 7 | * 8 | * @author xuanyh 9 | */ 10 | public class MapperXMLDiscovery implements SlinkDiscovery { 11 | 12 | @Override 13 | public void discover() { 14 | } 15 | 16 | @Override 17 | public void save() { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/SerializableDecider.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | import gadgetinspector.data.ClassReference; 4 | import gadgetinspector.javaserial.SimpleSerializableDecider; 5 | 6 | import java.util.function.Function; 7 | 8 | /** 9 | * Represents logic to decide if a class is serializable. The simple case (implemented by 10 | * {@link SimpleSerializableDecider}) just checks if the class implements serializable. Other use-cases may have more 11 | * complicated logic. 12 | */ 13 | public interface SerializableDecider extends Function { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/sqlinject/SQLInjectSerializableDecider.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.sqlinject; 2 | 3 | import gadgetinspector.SerializableDecider; 4 | import gadgetinspector.data.ClassReference; 5 | import gadgetinspector.data.MethodReference; 6 | import java.util.Map; 7 | 8 | public class SQLInjectSerializableDecider implements SerializableDecider { 9 | public SQLInjectSerializableDecider(Map methodMap) { 10 | } 11 | 12 | @Override 13 | public Boolean apply(ClassReference.Handle handle) { 14 | return Boolean.FALSE; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/data/CustomSlink.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.data; 2 | 3 | /** 4 | * @author threedr3am 5 | */ 6 | public class CustomSlink { 7 | private String className; 8 | private String method; 9 | private String desc; 10 | 11 | public CustomSlink() { 12 | } 13 | 14 | public CustomSlink(String className, String method, String desc) { 15 | this.className = className; 16 | this.method = method; 17 | this.desc = desc; 18 | } 19 | 20 | public String getClassName() { 21 | return className; 22 | } 23 | 24 | public void setClassName(String className) { 25 | this.className = className; 26 | } 27 | 28 | public String getMethod() { 29 | return method; 30 | } 31 | 32 | public void setMethod(String method) { 33 | this.method = method; 34 | } 35 | 36 | public String getDesc() { 37 | return desc; 38 | } 39 | 40 | public void setDesc(String desc) { 41 | this.desc = desc; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/fastjson/FastjsonSerializableDecider.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.fastjson; 2 | 3 | import gadgetinspector.SerializableDecider; 4 | import gadgetinspector.data.ClassReference; 5 | import gadgetinspector.data.MethodReference; 6 | import gadgetinspector.hessian.HessianSourceDiscovery; 7 | import java.util.Map; 8 | 9 | public class FastjsonSerializableDecider implements SerializableDecider { 10 | public FastjsonSerializableDecider(Map methodMap) { 11 | } 12 | 13 | @Override 14 | public Boolean apply(ClassReference.Handle handle) { 15 | if (isNoGadgetClass(handle)) { 16 | return false; 17 | } 18 | return Boolean.TRUE; 19 | } 20 | 21 | private boolean isNoGadgetClass(ClassReference.Handle clazz) { 22 | if (FastjsonSourceDiscovery.skipList.contains(clazz.getName())) { 23 | return true; 24 | } 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/newxstream/NewXstremImplementationFinder.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.newxstream; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.data.MethodReference; 6 | 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | 10 | public class NewXstremImplementationFinder implements ImplementationFinder{ 11 | private final SerializableDecider serializableDecider; 12 | public NewXstremImplementationFinder(SerializableDecider serializableDecider){ 13 | this.serializableDecider = serializableDecider; 14 | } 15 | @Override 16 | public Set getImplementations(MethodReference.Handle target) { 17 | Set allImpls = new HashSet<>(); 18 | 19 | if (Boolean.TRUE.equals(serializableDecider.apply(target.getClassReference()))) { 20 | allImpls.add(target); 21 | } 22 | 23 | return allImpls; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/config/ConfigRepository.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.config; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | public class ConfigRepository { 8 | private static final List ALL_CONFIGS = Collections.unmodifiableList(Arrays.asList( 9 | new JavaDeserializationConfig(), 10 | new JacksonDeserializationConfig(), 11 | new XstreamDeserializationConfig(), 12 | new FastjsonDeserializationConfig(), 13 | new SQLInjectDeserializationConfig(), 14 | new HessianDeserializationConfig(), 15 | new WebserviceDeserializationConfig(), 16 | new NewXstreamDeserializationConfig() 17 | )); 18 | 19 | public static GIConfig getConfig(String name) { 20 | for (GIConfig config : ALL_CONFIGS) { 21 | if (config.getName().equals(name)) { 22 | return config; 23 | } 24 | } 25 | return null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/data/SlinkReference.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.data; 2 | 3 | import gadgetinspector.data.ClassReference.Handle; 4 | import java.util.Set; 5 | 6 | /** 7 | * @author xuanyh 8 | */ 9 | public class SlinkReference { 10 | 11 | private ClassReference.Handle classReference; 12 | private Set methodReferences; 13 | 14 | public SlinkReference(Handle classReference, 15 | Set methodReferences) { 16 | this.classReference = classReference; 17 | this.methodReferences = methodReferences; 18 | } 19 | 20 | public Handle getClassReference() { 21 | return classReference; 22 | } 23 | 24 | public void setClassReference(Handle classReference) { 25 | this.classReference = classReference; 26 | } 27 | 28 | public Set getMethodReferences() { 29 | return methodReferences; 30 | } 31 | 32 | public void setMethodReferences(Set methodReferences) { 33 | this.methodReferences = methodReferences; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/config/GIConfig.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.config; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.SlinkDiscovery; 6 | import gadgetinspector.SourceDiscovery; 7 | import gadgetinspector.data.ClassReference; 8 | import gadgetinspector.data.InheritanceMap; 9 | import gadgetinspector.data.MethodReference; 10 | 11 | import gadgetinspector.data.MethodReference.Handle; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | public interface GIConfig { 16 | 17 | String getName(); 18 | SerializableDecider getSerializableDecider(Map methodMap, InheritanceMap inheritanceMap); 19 | ImplementationFinder getImplementationFinder( 20 | Map methodMap, 21 | Map> methodImplMap, 22 | InheritanceMap inheritanceMap, 23 | Map> methodsByClass); 24 | SourceDiscovery getSourceDiscovery(); 25 | SlinkDiscovery getSlinkDiscovery(); 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018 Ian Haken 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/ConfigHelper.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | import gadgetinspector.config.GIConfig; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | /** 8 | * @author xuanyh 9 | */ 10 | public class ConfigHelper { 11 | 12 | public static GIConfig giConfig; 13 | 14 | public static String mybatisMapperXMLPath; 15 | 16 | public static boolean taintTrack = true; 17 | 18 | public static int opLevel = 0; 19 | 20 | public static boolean history = false; 21 | public static String historyRecordFile = "history/"; 22 | 23 | public static int maxJarCount = Integer.MAX_VALUE; 24 | 25 | public static boolean onlyJDK = false; 26 | 27 | public static int maxChainLength = Integer.MAX_VALUE; 28 | public static int maxRepeatBranchesTimes = 20; 29 | public static int similarLevel = 0; 30 | 31 | public static boolean craw = false; 32 | public static int crawMin = 0; 33 | public static boolean crawMaven = false; 34 | public static boolean onlyCrawMavenPopular = false; 35 | public static String crawMavenJarPath = "/tmp"; 36 | public static boolean onlyCrawMaven = false; 37 | public static boolean onlyCrawNexus = false; 38 | 39 | public static Set slinks = new HashSet<>(); 40 | 41 | public static String skipSourcesFile = ""; 42 | public static String slinksFile = ""; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/data/Source.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.data; 2 | 3 | public class Source { 4 | private final MethodReference.Handle sourceMethod; 5 | private final int taintedArgIndex; 6 | 7 | public Source(MethodReference.Handle sourceMethod,int taintedArgIndex) { 8 | this.sourceMethod = sourceMethod; 9 | this.taintedArgIndex = taintedArgIndex; 10 | } 11 | 12 | public MethodReference.Handle getSourceMethod() { 13 | return sourceMethod; 14 | } 15 | 16 | public int getTaintedArgIndex() { 17 | return taintedArgIndex; 18 | } 19 | 20 | public static class Factory implements DataFactory { 21 | 22 | @Override 23 | public Source parse(String[] fields) { 24 | return new Source( 25 | new MethodReference.Handle(new ClassReference.Handle(fields[0]), fields[1], fields[2]), 26 | Integer.parseInt(fields[3]) 27 | ); 28 | } 29 | 30 | @Override 31 | public String[] serialize(Source obj) { 32 | return new String[]{ 33 | obj.sourceMethod.getClassReference().getName(), obj.sourceMethod.getName(), obj.sourceMethod.getDesc(), 34 | Integer.toString(obj.taintedArgIndex), 35 | }; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/sqlinject/SQLInjectImplementationFinder.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.sqlinject; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.data.MethodReference; 6 | import gadgetinspector.data.MethodReference.Handle; 7 | import java.util.HashSet; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | public class SQLInjectImplementationFinder implements ImplementationFinder { 12 | 13 | private final Map> methodImplMap; 14 | 15 | public SQLInjectImplementationFinder(Map> methodImplMap) { 16 | this.methodImplMap = methodImplMap; 17 | } 18 | 19 | @Override 20 | public Set getImplementations(MethodReference.Handle target) { 21 | Set allImpls = new HashSet<>(); 22 | 23 | // Assume that the target method is always available, even if not serializable; the target may just be a local 24 | // instance rather than something an attacker can control. 25 | allImpls.add(target); 26 | 27 | Set subClassImpls = methodImplMap.get(target); 28 | if (subClassImpls != null) { 29 | for (MethodReference.Handle subClassImpl : subClassImpls) { 30 | allImpls.add(subClassImpl); 31 | } 32 | } 33 | 34 | return allImpls; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/data/SlinkFactory.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.data; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import java.util.stream.Collectors; 6 | 7 | /** 8 | * @author xuanyh 9 | */ 10 | public class SlinkFactory implements DataFactory { 11 | 12 | @Override 13 | public SlinkReference parse(String[] fields) { 14 | Set methodReferences = new HashSet<>(); 15 | String[] tmp = fields[1].split("&"); 16 | for (int i = 0; i < tmp.length; i++) { 17 | String[] methods = tmp[i].split("!"); 18 | methodReferences.add(new MethodReference( 19 | new ClassReference.Handle(methods[0]), 20 | methods[1], 21 | methods[2], 22 | Boolean.parseBoolean(methods[3]))); 23 | } 24 | ClassReference.Handle handle = new ClassReference.Handle(fields[0]); 25 | return new SlinkReference(handle, methodReferences); 26 | } 27 | 28 | @Override 29 | public String[] serialize(SlinkReference obj) { 30 | String methodReferenceString = obj.getMethodReferences().stream().map( 31 | methodReference -> methodReference.getClassReference().getName() + "!" + methodReference 32 | .getName() + "!" + methodReference.getDesc() + "!" + methodReference.isStatic()) 33 | .collect( 34 | Collectors.joining("&")); 35 | return new String[]{ 36 | obj.getClassReference().getName(), 37 | methodReferenceString, 38 | }; 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/jackson/JacksonImplementationFinder.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.jackson; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.data.MethodReference; 6 | 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | 10 | public class JacksonImplementationFinder implements ImplementationFinder { 11 | 12 | private final SerializableDecider serializableDecider; 13 | 14 | public JacksonImplementationFinder(SerializableDecider serializableDecider) { 15 | this.serializableDecider = serializableDecider; 16 | } 17 | 18 | @Override 19 | public Set getImplementations(MethodReference.Handle target) { 20 | Set allImpls = new HashSet<>(); 21 | 22 | // For jackson search, we don't get to specify the class; it uses reflection to instantiate the 23 | // class itself. So just add the target method if the target class is serializable. 24 | if (Boolean.TRUE.equals(serializableDecider.apply(target.getClassReference()))) { 25 | allImpls.add(target); 26 | } 27 | //补充lookup情况 28 | if(target.getName().equals("lookup") && 29 | (target.getClassReference().getName().equals("javax/naming/Context") || target.getClassReference().getName().equals("java/rmi/registry/Registry"))){ 30 | allImpls.add(target); 31 | } 32 | 33 | return allImpls; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/config/NewXstreamDeserializationConfig.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.config; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.SlinkDiscovery; 6 | import gadgetinspector.SourceDiscovery; 7 | import gadgetinspector.data.ClassReference; 8 | import gadgetinspector.data.InheritanceMap; 9 | import gadgetinspector.data.MethodReference; 10 | import gadgetinspector.newxstream.*; 11 | 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | public class NewXstreamDeserializationConfig implements GIConfig { 16 | @Override 17 | public String getName() { 18 | return "newXstream"; 19 | } 20 | 21 | @Override 22 | public SerializableDecider getSerializableDecider(Map methodMap, InheritanceMap inheritanceMap) { 23 | return new NewXstremSerializableDecider(methodMap); 24 | } 25 | 26 | @Override 27 | public ImplementationFinder getImplementationFinder(Map methodMap, Map> methodImplMap, InheritanceMap inheritanceMap, Map> methodsByClass) { 28 | return new NewXstremImplementationFinder(getSerializableDecider(methodMap, inheritanceMap)); 29 | 30 | } 31 | 32 | @Override 33 | public SourceDiscovery getSourceDiscovery() { 34 | return new NewXstreamSourceDiscovery(); 35 | } 36 | 37 | @Override 38 | public SlinkDiscovery getSlinkDiscovery() { 39 | return null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/hessian/HessianImplementationFinder.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.hessian; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.data.MethodReference; 6 | import java.util.HashSet; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | public class HessianImplementationFinder implements ImplementationFinder { 11 | 12 | private final SerializableDecider serializableDecider; 13 | private final Map> methodImplMap; 14 | 15 | public HessianImplementationFinder(SerializableDecider serializableDecider, Map> methodImplMap) { 16 | this.serializableDecider = serializableDecider; 17 | this.methodImplMap = methodImplMap; 18 | } 19 | 20 | @Override 21 | public Set getImplementations(MethodReference.Handle target) { 22 | Set allImpls = new HashSet<>(); 23 | 24 | // Assume that the target method is always available, even if not serializable; the target may just be a local 25 | // instance rather than something an attacker can control. 26 | allImpls.add(target); 27 | 28 | Set subClassImpls = methodImplMap.get(target); 29 | if (subClassImpls != null) { 30 | for (MethodReference.Handle subClassImpl : subClassImpls) { 31 | if (Boolean.TRUE.equals(serializableDecider.apply(subClassImpl.getClassReference()))) { 32 | allImpls.add(subClassImpl); 33 | } 34 | } 35 | } 36 | return allImpls; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/javaserial/SimpleImplementationFinder.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.javaserial; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.data.MethodReference; 6 | 7 | import java.util.HashSet; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | public class SimpleImplementationFinder implements ImplementationFinder { 12 | 13 | private final SerializableDecider serializableDecider; 14 | private final Map> methodImplMap; 15 | 16 | public SimpleImplementationFinder(SerializableDecider serializableDecider, Map> methodImplMap) { 17 | this.serializableDecider = serializableDecider; 18 | this.methodImplMap = methodImplMap; 19 | } 20 | 21 | @Override 22 | public Set getImplementations(MethodReference.Handle target) { 23 | Set allImpls = new HashSet<>(); 24 | 25 | // Assume that the target method is always available, even if not serializable; the target may just be a local 26 | // instance rather than something an attacker can control. 27 | allImpls.add(target); 28 | 29 | Set subClassImpls = methodImplMap.get(target); 30 | if (subClassImpls != null) { 31 | for (MethodReference.Handle subClassImpl : subClassImpls) { 32 | if (Boolean.TRUE.equals(serializableDecider.apply(subClassImpl.getClassReference()))) { 33 | allImpls.add(subClassImpl); 34 | } 35 | } 36 | } 37 | 38 | return allImpls; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/config/XstreamDeserializationConfig.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.config; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.SlinkDiscovery; 6 | import gadgetinspector.SourceDiscovery; 7 | import gadgetinspector.data.ClassReference; 8 | import gadgetinspector.data.InheritanceMap; 9 | import gadgetinspector.data.MethodReference; 10 | import gadgetinspector.data.MethodReference.Handle; 11 | import gadgetinspector.javaserial.SimpleImplementationFinder; 12 | import gadgetinspector.javaserial.SimpleSourceDiscovery; 13 | import gadgetinspector.xstream.XstreamSerializableDecider; 14 | 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | public class XstreamDeserializationConfig implements GIConfig { 19 | @Override 20 | public String getName() { 21 | return "xstream"; 22 | } 23 | 24 | public SerializableDecider getSerializableDecider(Map methodMap, InheritanceMap inheritanceMap) { 25 | return new XstreamSerializableDecider(); 26 | } 27 | 28 | @Override 29 | public ImplementationFinder getImplementationFinder( 30 | Map methodMap, 31 | Map> methodImplMap, 32 | InheritanceMap inheritanceMap, 33 | Map> methodsByClass) { 34 | return new SimpleImplementationFinder(getSerializableDecider(methodMap, inheritanceMap), methodImplMap); 35 | } 36 | 37 | @Override 38 | public SourceDiscovery getSourceDiscovery() { 39 | return new SimpleSourceDiscovery(); 40 | } 41 | 42 | @Override 43 | public SlinkDiscovery getSlinkDiscovery() { 44 | return null; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/webservice/WebserviceSerializableDecider.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.webservice; 2 | 3 | import gadgetinspector.SerializableDecider; 4 | import gadgetinspector.data.ClassReference; 5 | import gadgetinspector.data.MethodReference; 6 | import gadgetinspector.jackson.JacksonSourceDiscovery; 7 | 8 | import java.util.HashMap; 9 | import java.util.HashSet; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | public class WebserviceSerializableDecider implements SerializableDecider { 14 | //类是否通过决策的缓存集合 15 | private final Map cache = new HashMap<>(); 16 | //类名-方法集合 17 | 18 | private final Map> methodsByClassMap; 19 | 20 | public WebserviceSerializableDecider(Map methodMap){ 21 | this.methodsByClassMap = new HashMap<>(); 22 | for (MethodReference.Handle method : methodMap.keySet()) { 23 | Set classMethods = methodsByClassMap.get(method.getClassReference()); 24 | if (classMethods == null) { 25 | classMethods = new HashSet<>(); 26 | methodsByClassMap.put(method.getClassReference(), classMethods); 27 | } 28 | classMethods.add(method); 29 | } 30 | } 31 | 32 | @Override 33 | public Boolean apply(ClassReference.Handle handle) { 34 | if (isNoGadgetClass(handle)) { 35 | return false; 36 | } 37 | return Boolean.TRUE; 38 | } 39 | private boolean isNoGadgetClass(ClassReference.Handle clazz) { 40 | if (WebserviceSourceDiscovery.skipList.contains(clazz.getName())) { 41 | return true; 42 | } 43 | return false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/config/JacksonDeserializationConfig.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.config; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.SlinkDiscovery; 6 | import gadgetinspector.SourceDiscovery; 7 | import gadgetinspector.data.ClassReference; 8 | import gadgetinspector.data.InheritanceMap; 9 | import gadgetinspector.data.MethodReference; 10 | import gadgetinspector.data.MethodReference.Handle; 11 | import gadgetinspector.jackson.JacksonImplementationFinder; 12 | import gadgetinspector.jackson.JacksonSerializableDecider; 13 | import gadgetinspector.jackson.JacksonSourceDiscovery; 14 | 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | public class JacksonDeserializationConfig implements GIConfig { 19 | 20 | @Override 21 | public String getName() { 22 | return "jackson"; 23 | } 24 | 25 | @Override 26 | public SerializableDecider getSerializableDecider(Map methodMap, InheritanceMap inheritanceMap) { 27 | return new JacksonSerializableDecider(methodMap); 28 | } 29 | 30 | @Override 31 | public ImplementationFinder getImplementationFinder( 32 | Map methodMap, 33 | Map> methodImplMap, 34 | InheritanceMap inheritanceMap, 35 | Map> methodsByClass) { 36 | return new JacksonImplementationFinder(getSerializableDecider(methodMap, inheritanceMap)); 37 | } 38 | 39 | @Override 40 | public SourceDiscovery getSourceDiscovery() { 41 | return new JacksonSourceDiscovery(); 42 | } 43 | 44 | @Override 45 | public SlinkDiscovery getSlinkDiscovery() { 46 | return null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/config/HessianDeserializationConfig.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.config; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.SlinkDiscovery; 6 | import gadgetinspector.SourceDiscovery; 7 | import gadgetinspector.data.ClassReference; 8 | import gadgetinspector.data.InheritanceMap; 9 | import gadgetinspector.data.MethodReference; 10 | import gadgetinspector.data.MethodReference.Handle; 11 | import gadgetinspector.hessian.HessianImplementationFinder; 12 | import gadgetinspector.hessian.HessianSerializableDecider; 13 | import gadgetinspector.hessian.HessianSourceDiscovery; 14 | import java.util.Map; 15 | import java.util.Set; 16 | 17 | public class HessianDeserializationConfig implements GIConfig { 18 | 19 | @Override 20 | public String getName() { 21 | return "hessian"; 22 | } 23 | 24 | @Override 25 | public SerializableDecider getSerializableDecider(Map methodMap, InheritanceMap inheritanceMap) { 26 | return new HessianSerializableDecider(inheritanceMap); 27 | } 28 | 29 | @Override 30 | public ImplementationFinder getImplementationFinder( 31 | Map methodMap, 32 | Map> methodImplMap, 33 | InheritanceMap inheritanceMap, 34 | Map> methodsByClass) { 35 | return new HessianImplementationFinder(getSerializableDecider(methodMap, inheritanceMap), methodImplMap); 36 | } 37 | 38 | @Override 39 | public SourceDiscovery getSourceDiscovery() { 40 | return new HessianSourceDiscovery(); 41 | } 42 | 43 | @Override 44 | public SlinkDiscovery getSlinkDiscovery() { 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/config/JavaDeserializationConfig.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.config; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.SlinkDiscovery; 6 | import gadgetinspector.SourceDiscovery; 7 | import gadgetinspector.data.ClassReference; 8 | import gadgetinspector.data.InheritanceMap; 9 | import gadgetinspector.data.MethodReference; 10 | import gadgetinspector.data.MethodReference.Handle; 11 | import gadgetinspector.javaserial.SimpleImplementationFinder; 12 | import gadgetinspector.javaserial.SimpleSerializableDecider; 13 | import gadgetinspector.javaserial.SimpleSourceDiscovery; 14 | 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | public class JavaDeserializationConfig implements GIConfig { 19 | 20 | @Override 21 | public String getName() { 22 | return "jserial"; 23 | } 24 | 25 | @Override 26 | public SerializableDecider getSerializableDecider(Map methodMap, InheritanceMap inheritanceMap) { 27 | return new SimpleSerializableDecider(inheritanceMap); 28 | } 29 | 30 | @Override 31 | public ImplementationFinder getImplementationFinder( 32 | Map methodMap, 33 | Map> methodImplMap, 34 | InheritanceMap inheritanceMap, 35 | Map> methodsByClass) { 36 | return new SimpleImplementationFinder(getSerializableDecider(methodMap, inheritanceMap), methodImplMap); 37 | } 38 | 39 | @Override 40 | public SourceDiscovery getSourceDiscovery() { 41 | return new SimpleSourceDiscovery(); 42 | } 43 | 44 | @Override 45 | public SlinkDiscovery getSlinkDiscovery() { 46 | return null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/config/FastjsonDeserializationConfig.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.config; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.SlinkDiscovery; 6 | import gadgetinspector.SourceDiscovery; 7 | import gadgetinspector.data.ClassReference; 8 | import gadgetinspector.data.InheritanceMap; 9 | import gadgetinspector.data.MethodReference; 10 | import gadgetinspector.data.MethodReference.Handle; 11 | import gadgetinspector.fastjson.FastjsonImplementationFinder; 12 | import gadgetinspector.fastjson.FastjsonSerializableDecider; 13 | import gadgetinspector.fastjson.FastjsonSourceDiscovery; 14 | import java.util.Map; 15 | import java.util.Set; 16 | 17 | public class FastjsonDeserializationConfig implements GIConfig { 18 | 19 | @Override 20 | public String getName() { 21 | return "fastjson"; 22 | } 23 | 24 | @Override 25 | public SerializableDecider getSerializableDecider(Map methodMap, InheritanceMap inheritanceMap) { 26 | return new FastjsonSerializableDecider(methodMap); 27 | } 28 | 29 | @Override 30 | public ImplementationFinder getImplementationFinder( 31 | Map methodMap, 32 | Map> methodImplMap, 33 | InheritanceMap inheritanceMap, 34 | Map> methodsByClass) { 35 | return new FastjsonImplementationFinder(getSerializableDecider(methodMap, inheritanceMap), methodImplMap, methodsByClass); 36 | } 37 | 38 | @Override 39 | public SourceDiscovery getSourceDiscovery() { 40 | return new FastjsonSourceDiscovery(); 41 | } 42 | 43 | @Override 44 | public SlinkDiscovery getSlinkDiscovery() { 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/newxstream/NewXstremSerializableDecider.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.newxstream; 2 | 3 | import gadgetinspector.SerializableDecider; 4 | import gadgetinspector.data.ClassReference; 5 | import gadgetinspector.data.MethodReference; 6 | import gadgetinspector.jackson.JacksonSourceDiscovery; 7 | import gadgetinspector.webservice.WebserviceSourceDiscovery; 8 | 9 | import java.util.HashMap; 10 | import java.util.HashSet; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | public class NewXstremSerializableDecider implements SerializableDecider { 15 | //类是否通过决策的缓存集合 16 | private final Map cache = new HashMap<>(); 17 | //类名-方法集合 18 | 19 | private final Map> methodsByClassMap; 20 | 21 | public NewXstremSerializableDecider(Map methodMap){ 22 | this.methodsByClassMap = new HashMap<>(); 23 | for (MethodReference.Handle method : methodMap.keySet()) { 24 | Set classMethods = methodsByClassMap.get(method.getClassReference()); 25 | if (classMethods == null) { 26 | classMethods = new HashSet<>(); 27 | methodsByClassMap.put(method.getClassReference(), classMethods); 28 | } 29 | classMethods.add(method); 30 | } 31 | } 32 | @Override 33 | public Boolean apply(ClassReference.Handle handle) { 34 | if (isNoGadgetClass(handle)) { 35 | return false; 36 | } 37 | return Boolean.TRUE; 38 | } 39 | private boolean isNoGadgetClass(ClassReference.Handle clazz) { 40 | if (WebserviceSourceDiscovery.skipList.contains(clazz.getName())) { 41 | return true; 42 | } 43 | return false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/config/SQLInjectDeserializationConfig.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.config; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.SlinkDiscovery; 6 | import gadgetinspector.SourceDiscovery; 7 | import gadgetinspector.data.ClassReference; 8 | import gadgetinspector.data.InheritanceMap; 9 | import gadgetinspector.data.MethodReference; 10 | import gadgetinspector.data.MethodReference.Handle; 11 | import gadgetinspector.sqlinject.SQLInjectImplementationFinder; 12 | import gadgetinspector.sqlinject.SQLInjectSerializableDecider; 13 | import gadgetinspector.sqlinject.SQLInjectSourceDiscovery; 14 | import gadgetinspector.sqlinject.mybatis.MapperXMLDiscovery; 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | public class SQLInjectDeserializationConfig implements GIConfig { 19 | 20 | @Override 21 | public String getName() { 22 | return "sqlinject"; 23 | } 24 | 25 | @Override 26 | public SerializableDecider getSerializableDecider(Map methodMap, InheritanceMap inheritanceMap) { 27 | return new SQLInjectSerializableDecider(methodMap); 28 | } 29 | 30 | @Override 31 | public ImplementationFinder getImplementationFinder( 32 | Map methodMap, 33 | Map> methodImplMap, 34 | InheritanceMap inheritanceMap, 35 | Map> methodsByClass) { 36 | return new SQLInjectImplementationFinder(methodImplMap); 37 | } 38 | 39 | @Override 40 | public SourceDiscovery getSourceDiscovery() { 41 | return new SQLInjectSourceDiscovery(); 42 | } 43 | 44 | @Override 45 | public SlinkDiscovery getSlinkDiscovery() { 46 | return new MapperXMLDiscovery(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/config/WebserviceDeserializationConfig.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.config; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.SlinkDiscovery; 6 | import gadgetinspector.SourceDiscovery; 7 | import gadgetinspector.data.ClassReference; 8 | import gadgetinspector.data.InheritanceMap; 9 | import gadgetinspector.data.MethodReference; 10 | import gadgetinspector.fastjson.FastjsonImplementationFinder; 11 | import gadgetinspector.webservice.WebserviceImplementationFinder; 12 | import gadgetinspector.webservice.WebserviceSerializableDecider; 13 | import gadgetinspector.webservice.WebserviceSourceDiscovery; 14 | 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | public class WebserviceDeserializationConfig implements GIConfig { 19 | @Override 20 | public String getName() { 21 | return "webservice"; 22 | } 23 | 24 | @Override 25 | public SerializableDecider getSerializableDecider(Map methodMap, InheritanceMap inheritanceMap) { 26 | return new WebserviceSerializableDecider(methodMap); 27 | } 28 | 29 | @Override 30 | public ImplementationFinder getImplementationFinder(Map methodMap, 31 | Map> methodImplMap, 32 | InheritanceMap inheritanceMap, Map> methodsByClass) { 33 | return new WebserviceImplementationFinder(getSerializableDecider(methodMap, inheritanceMap), methodImplMap, methodsByClass); 34 | } 35 | 36 | @Override 37 | public SourceDiscovery getSourceDiscovery() { 38 | return new WebserviceSourceDiscovery(); 39 | } 40 | 41 | @Override 42 | public SlinkDiscovery getSlinkDiscovery() { 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/hessian/HessianSerializableDecider.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.hessian; 2 | 3 | import gadgetinspector.SerializableDecider; 4 | import gadgetinspector.data.ClassReference; 5 | import gadgetinspector.data.ClassReference.Handle; 6 | import gadgetinspector.data.InheritanceMap; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class HessianSerializableDecider implements SerializableDecider { 11 | private final Map cache = new HashMap<>(); 12 | private final InheritanceMap inheritanceMap; 13 | 14 | public HessianSerializableDecider(InheritanceMap inheritanceMap) { 15 | this.inheritanceMap = inheritanceMap; 16 | } 17 | 18 | /** 19 | * 用于判断class是否可以被序列化 20 | * 21 | * @param handle 22 | * @return 23 | */ 24 | @Override 25 | public Boolean apply(ClassReference.Handle handle) { 26 | Boolean cached = cache.get(handle); 27 | if (cached != null) { 28 | return cached; 29 | } 30 | 31 | Boolean result = applyNoCache(handle); 32 | 33 | cache.put(handle, result); 34 | return result; 35 | } 36 | 37 | private Boolean applyNoCache(ClassReference.Handle handle) { 38 | 39 | if (isBlacklistedClass(handle)) { 40 | return false; 41 | } 42 | 43 | if (isNoGadgetClass(handle)) { 44 | return false; 45 | } 46 | 47 | return true; 48 | } 49 | 50 | /** 51 | * 判断class是否在黑名单内 52 | * 53 | * @param clazz 54 | * @return 55 | */ 56 | private boolean isBlacklistedClass(ClassReference.Handle clazz) { 57 | // if (inheritanceMap.isSubclassOf(clazz, new Handle("java/io/InputStream"))) { 58 | // return true; 59 | // } 60 | return false; 61 | } 62 | 63 | private boolean isNoGadgetClass(ClassReference.Handle clazz) { 64 | if (HessianSourceDiscovery.skipList.contains(clazz.getName())) { 65 | return true; 66 | } 67 | return false; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/fastjson/FastjsonImplementationFinder.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.fastjson; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.data.ClassReference; 6 | import gadgetinspector.data.MethodReference; 7 | import gadgetinspector.data.MethodReference.Handle; 8 | import java.util.HashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | public class FastjsonImplementationFinder implements ImplementationFinder { 13 | 14 | private final SerializableDecider serializableDecider; 15 | private final Map> methodImplMap; 16 | private final Map> methodsByClass; 17 | 18 | public FastjsonImplementationFinder(SerializableDecider serializableDecider, 19 | Map> methodImplMap, 20 | Map> methodsByClass) { 21 | this.serializableDecider = serializableDecider; 22 | this.methodImplMap = methodImplMap; 23 | this.methodsByClass = methodsByClass; 24 | } 25 | 26 | @Override 27 | public Set getImplementations(MethodReference.Handle target) { 28 | Set allImpls = new HashSet<>(); 29 | 30 | // Fastjson可以指定接口实现类 31 | if (Boolean.TRUE.equals(serializableDecider.apply(target.getClassReference()))) { 32 | Set methods = methodsByClass.get(target.getClassReference()); 33 | if (methods == null) 34 | return allImpls; 35 | if (methods.contains(target)) { 36 | allImpls.add(target); 37 | } 38 | } 39 | 40 | Set subClassImpls = methodImplMap.get(target); 41 | if (subClassImpls != null) { 42 | for (MethodReference.Handle subClassImpl : subClassImpls) { 43 | if (Boolean.TRUE.equals(serializableDecider.apply(subClassImpl.getClassReference()))) { 44 | allImpls.add(subClassImpl); 45 | } 46 | } 47 | } 48 | 49 | return allImpls; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/SourceDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | import gadgetinspector.data.*; 4 | 5 | import java.io.IOException; 6 | import java.nio.file.Paths; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | /* FIXME: This source discovery is limited to standard serializable objects; doesn't do proper source discovery for 15 | * non-standard Xstream cases. */ 16 | public abstract class SourceDiscovery { 17 | 18 | private final List discoveredSources = new ArrayList<>(); 19 | 20 | protected final void addDiscoveredSource(Source source) { 21 | discoveredSources.add(source); 22 | } 23 | 24 | public void discover() throws IOException { 25 | Map classMap = DataLoader.loadClasses(); 26 | Map methodMap = DataLoader.loadMethods(); 27 | InheritanceMap inheritanceMap = InheritanceMap.load(); 28 | 29 | Map> graphCallMap = new HashMap<>(); 30 | for (GraphCall graphCall : DataLoader.loadData(Paths.get("callgraph.dat"), new GraphCall.Factory())) { 31 | MethodReference.Handle caller = graphCall.getCallerMethod(); 32 | if (!graphCallMap.containsKey(caller)) { 33 | Set graphCalls = new HashSet<>(); 34 | graphCalls.add(graphCall); 35 | graphCallMap.put(caller, graphCalls); 36 | } else { 37 | graphCallMap.get(caller).add(graphCall); 38 | } 39 | } 40 | 41 | discover(classMap, methodMap, inheritanceMap, graphCallMap); 42 | } 43 | 44 | public abstract void discover(Map classMap, 45 | Map methodMap, 46 | InheritanceMap inheritanceMap, Map> graphCallMap); 47 | 48 | public void save() throws IOException { 49 | DataLoader.saveData(Paths.get("sources.dat"), new Source.Factory(), discoveredSources); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/sqlinject/SQLInjectSourceDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.sqlinject; 2 | 3 | import gadgetinspector.SourceDiscovery; 4 | import gadgetinspector.data.ClassReference; 5 | import gadgetinspector.data.GraphCall; 6 | import gadgetinspector.data.InheritanceMap; 7 | import gadgetinspector.data.MethodReference; 8 | import gadgetinspector.data.Source; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | public class SQLInjectSourceDiscovery extends SourceDiscovery { 13 | 14 | @Override 15 | public void discover(Map classMap, 16 | Map methodMap, 17 | InheritanceMap inheritanceMap, Map> graphCallMap) { 18 | 19 | for (MethodReference.Handle method : methodMap.keySet()) { 20 | ClassReference classReference = classMap.get(method.getClassReference()); 21 | Set graphCalls = graphCallMap.get(method); 22 | if (graphCalls == null) { 23 | continue; 24 | } 25 | for (GraphCall graphCall : graphCalls) { 26 | //servlet 27 | if ((graphCall.getTargetMethod().getName().equals("getQueryString") 28 | || graphCall.getTargetMethod().getName().equals("getParameter") 29 | || graphCall.getTargetMethod().getName().equals("getParameterNames") 30 | || graphCall.getTargetMethod().getName().equals("getParameterValues") 31 | || graphCall.getTargetMethod().getName().equals("getParameterMap")) 32 | && (inheritanceMap.isSubclassOf(graphCall.getTargetMethod().getClassReference(), 33 | new ClassReference.Handle("javax/servlet/ServletRequest"))) 34 | ) { 35 | addDiscoveredSource(new Source(method, graphCall.getCallerArgIndex())); 36 | continue; 37 | } 38 | if (classReference != null && (classReference.getAnnotations() 39 | .contains("Lorg/springframework/web/bind/annotation/RestController;") || classReference 40 | .getAnnotations().contains("Lorg/springframework/stereotype/Controller;"))) { 41 | addDiscoveredSource(new Source(method, graphCall.getCallerArgIndex())); 42 | continue; 43 | } 44 | } 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/webservice/WebserviceImplementationFinder.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.webservice; 2 | 3 | import gadgetinspector.ImplementationFinder; 4 | import gadgetinspector.SerializableDecider; 5 | import gadgetinspector.data.ClassReference; 6 | import gadgetinspector.data.ClassReference; 7 | import gadgetinspector.data.MethodReference; 8 | 9 | import java.util.HashSet; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | public class WebserviceImplementationFinder implements ImplementationFinder { 14 | private final SerializableDecider serializableDecider; 15 | private final Map> methodImplMap; 16 | private final Map> methodsByClass; 17 | public WebserviceImplementationFinder(SerializableDecider serializableDecider, 18 | Map> methodImplMap, 19 | Map> methodsByClass){ 20 | this.serializableDecider = serializableDecider; 21 | this.methodImplMap = methodImplMap; 22 | this.methodsByClass = methodsByClass; 23 | } 24 | @Override 25 | public Set getImplementations(MethodReference.Handle target) { 26 | Set allImpls = new HashSet<>(); 27 | 28 | if (Boolean.TRUE.equals(serializableDecider.apply(target.getClassReference()))) { 29 | Set methods = methodsByClass.get(target.getClassReference()); 30 | if (methods == null) 31 | return allImpls; 32 | if (methods.contains(target)) { 33 | allImpls.add(target); 34 | } 35 | } 36 | 37 | Set subClassImpls = methodImplMap.get(target); 38 | if (subClassImpls != null) { 39 | for (MethodReference.Handle subClassImpl : subClassImpls) { 40 | if (Boolean.TRUE.equals(serializableDecider.apply(subClassImpl.getClassReference()))) { 41 | allImpls.add(subClassImpl); 42 | } 43 | } 44 | } 45 | 46 | 47 | return allImpls; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/javaserial/SimpleSerializableDecider.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.javaserial; 2 | 3 | import gadgetinspector.SerializableDecider; 4 | import gadgetinspector.data.ClassReference; 5 | import gadgetinspector.data.InheritanceMap; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class SimpleSerializableDecider implements SerializableDecider { 11 | private final Map cache = new HashMap<>(); 12 | private final InheritanceMap inheritanceMap; 13 | 14 | public SimpleSerializableDecider(InheritanceMap inheritanceMap) { 15 | this.inheritanceMap = inheritanceMap; 16 | } 17 | 18 | /** 19 | * 用于判断class是否可以被序列化 20 | * 21 | * @param handle 22 | * @return 23 | */ 24 | @Override 25 | public Boolean apply(ClassReference.Handle handle) { 26 | Boolean cached = cache.get(handle); 27 | if (cached != null) { 28 | return cached; 29 | } 30 | 31 | Boolean result = applyNoCache(handle); 32 | 33 | cache.put(handle, result); 34 | return result; 35 | } 36 | 37 | private Boolean applyNoCache(ClassReference.Handle handle) { 38 | 39 | if (isBlacklistedClass(handle)) { 40 | return false; 41 | } 42 | 43 | //判断是否有直接或间接实现java/io/Serializable序列化接口 44 | if (inheritanceMap.isSubclassOf(handle, new ClassReference.Handle("java/io/Serializable"))) { 45 | return true; 46 | } 47 | 48 | return false; 49 | } 50 | 51 | /** 52 | * 判断class是否在黑名单内 53 | * 54 | * @param clazz 55 | * @return 56 | */ 57 | private static boolean isBlacklistedClass(ClassReference.Handle clazz) { 58 | if (clazz.getName().startsWith("com/google/common/collect/")) { 59 | return true; 60 | } 61 | 62 | // Serialization of these classes has been disabled since clojure 1.9.0 63 | // https://github.com/clojure/clojure/commit/271674c9b484d798484d134a5ac40a6df15d3ac3 64 | if (clazz.getName().equals("clojure/core/proxy$clojure/lang/APersistentMap$ff19274a") 65 | || clazz.getName().equals("clojure/inspector/proxy$javax/swing/table/AbstractTableModel$ff19274a")) { 66 | return true; 67 | } 68 | 69 | return false; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/jackson/JacksonSerializableDecider.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.jackson; 2 | 3 | import gadgetinspector.SerializableDecider; 4 | import gadgetinspector.data.ClassReference; 5 | import gadgetinspector.data.MethodReference; 6 | import java.util.HashMap; 7 | import java.util.HashSet; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | public class JacksonSerializableDecider implements SerializableDecider { 12 | //类是否通过决策的缓存集合 13 | private final Map cache = new HashMap<>(); 14 | //类名-方法集合 映射集合 15 | private final Map> methodsByClassMap; 16 | 17 | public JacksonSerializableDecider(Map methodMap) { 18 | this.methodsByClassMap = new HashMap<>(); 19 | for (MethodReference.Handle method : methodMap.keySet()) { 20 | Set classMethods = methodsByClassMap.get(method.getClassReference()); 21 | if (classMethods == null) { 22 | classMethods = new HashSet<>(); 23 | methodsByClassMap.put(method.getClassReference(), classMethods); 24 | } 25 | classMethods.add(method); 26 | } 27 | } 28 | 29 | @Override 30 | public Boolean apply(ClassReference.Handle handle) { 31 | if (isNoGadgetClass(handle)) { 32 | return false; 33 | } 34 | Boolean cached = cache.get(handle); 35 | if (cached != null) { 36 | return cached; 37 | } 38 | 39 | Set classMethods = methodsByClassMap.get(handle); 40 | if (classMethods != null) { 41 | for (MethodReference.Handle method : classMethods) { 42 | //该类,只要有无参构造方法,就通过决策 43 | if (method.getName().equals("") && method.getDesc().equals("()V")) { 44 | cache.put(handle, Boolean.TRUE); 45 | return Boolean.TRUE; 46 | } 47 | } 48 | } 49 | 50 | cache.put(handle, Boolean.FALSE); 51 | return Boolean.FALSE; 52 | } 53 | 54 | private boolean isNoGadgetClass(ClassReference.Handle clazz) { 55 | if (JacksonSourceDiscovery.skipList.contains(clazz.getName())) { 56 | return true; 57 | } 58 | return false; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/xstream/XstreamSerializableDecider.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.xstream; 2 | 3 | import gadgetinspector.SerializableDecider; 4 | import gadgetinspector.data.ClassReference; 5 | 6 | /** 7 | * The default behavior of xstream is to support xml tags as specifying arbitrary class names. So a class is serializable 8 | * if the class name is a valid XML tag. 9 | */ 10 | public class XstreamSerializableDecider implements SerializableDecider { 11 | 12 | @Override 13 | public Boolean apply(ClassReference.Handle handle) { 14 | return isValidXmlTag(handle.getName().replace('/', '.')); 15 | } 16 | 17 | private static boolean isValidXmlTag(String name) { 18 | // Name ::= NameStartChar (NameChar)* 19 | if (name == null || name.length() == 0) { 20 | return false; 21 | } 22 | if (!isNameStartChar(name.charAt(0))) { 23 | return false; 24 | } 25 | for (int i = 1; i < name.length(); i++) { 26 | if (!isNameChar(name.charAt(i))) { 27 | return false; 28 | } 29 | } 30 | return true; 31 | } 32 | 33 | private static boolean isNameStartChar(char c) { 34 | // NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] 35 | if (c == ':' || ('A' <= c && c <= 'Z') || c == '_' || ('a' <= c && c <= 'z') 36 | || (0xC0 <= c && c <= 0xD6) || (0xD8 <= c && c <= 0xF6) || (0xF8 <= c && c <= 0x2FF) 37 | || (0x370 <= c && c <= 0x37D) || (0x37F <= c && c <= 0x1FFF) || (0x200C <= c && c <= 0x200D) 38 | || (0x2070 <= c && c <= 0x218F) || (0x2C00 <= c && c <= 0x2FEF) || (0x3001 <= c && c <= 0xD7FF) 39 | || (0xF900 <= c && c <= 0xFDCF) || (0xFDF0 <= c && c <= 0xFFFD)) { 40 | return true; 41 | } 42 | return false; 43 | } 44 | 45 | private static boolean isNameChar(char c) { 46 | // NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] 47 | if (isNameStartChar(c) || c == '-' || c == '.' || ('0' <= c && c <= '9') || c == 0xB7 48 | || (0x300 <= c && c <= 0x036F) || (0x203F <= c && c <= 0x2040)) { 49 | return true; 50 | } 51 | return false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/newxstream/NewXstreamSourceDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.newxstream; 2 | 3 | import gadgetinspector.ConfigHelper; 4 | import gadgetinspector.SourceDiscovery; 5 | import gadgetinspector.data.*; 6 | import gadgetinspector.webservice.WebserviceSerializableDecider; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Paths; 12 | import java.util.HashSet; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | public class NewXstreamSourceDiscovery extends SourceDiscovery { 17 | public static final Set skipList = new HashSet<>(); 18 | 19 | static { 20 | if (!ConfigHelper.skipSourcesFile.isEmpty()) { 21 | try(BufferedReader bufferedReader = Files.newBufferedReader(Paths.get(ConfigHelper.skipSourcesFile))) { 22 | String line; 23 | while ((line = bufferedReader.readLine()) != null) { 24 | String c; 25 | if (!(c = line.split("#")[0].trim()).isEmpty()) { 26 | skipList.add(line.trim()); 27 | } 28 | } 29 | } catch (IOException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | } 34 | 35 | @Override 36 | public void discover(Map classMap, Map methodMap, InheritanceMap inheritanceMap, Map> graphCallMap) { 37 | final NewXstremSerializableDecider newXstreamDecider = new NewXstremSerializableDecider(methodMap); 38 | 39 | for (MethodReference.Handle method : methodMap.keySet()) { 40 | MethodReference methodValue = methodMap.get(method); 41 | boolean skipFlag = false; 42 | for (String skipClass:skipList){ 43 | if (method.getClassReference().getName().contains(skipClass)){ 44 | skipFlag = true; 45 | break; 46 | } 47 | } 48 | if (skipFlag){ 49 | continue; 50 | } 51 | if (newXstreamDecider.apply(method.getClassReference())) { 52 | if (method.getName().equals("hashCode") && method.getDesc().contains("()")) 53 | { 54 | addDiscoveredSource(new Source(method, 0)); 55 | } 56 | } 57 | 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/hessian/HessianSourceDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.hessian; 2 | 3 | import gadgetinspector.SerializableDecider; 4 | import gadgetinspector.SourceDiscovery; 5 | import gadgetinspector.data.ClassReference; 6 | import gadgetinspector.data.GraphCall; 7 | import gadgetinspector.data.InheritanceMap; 8 | import gadgetinspector.data.MethodReference; 9 | import gadgetinspector.data.Source; 10 | import java.util.HashSet; 11 | import java.util.Map; 12 | import java.util.Set; 13 | import org.objectweb.asm.Type; 14 | 15 | public class HessianSourceDiscovery extends SourceDiscovery { 16 | 17 | public static final Set skipList = new HashSet<>(); 18 | 19 | static { 20 | //一些在非污点模式下发现无法利用的类 21 | skipList.add("javax/naming/directory/BasicAttributes"); 22 | skipList.add("net/bytebuddy/description/annotation/AnnotationDescription$AbstractBase"); 23 | skipList.add("javax/naming/Reference"); 24 | skipList.add(""); 25 | skipList.add(""); 26 | skipList.add(""); 27 | skipList.add(""); 28 | } 29 | 30 | @Override 31 | public void discover(Map classMap, 32 | Map methodMap, 33 | InheritanceMap inheritanceMap, Map> graphCallMap) { 34 | 35 | final SerializableDecider serializableDecider = new HessianSerializableDecider(inheritanceMap); 36 | 37 | // hashCode() or equals() are accessible entry points using standard tricks of putting those objects 38 | // into a HashMap. 39 | for (MethodReference.Handle method : methodMap.keySet()) { 40 | if (skipList.contains(method.getClassReference().getName())) { 41 | continue; 42 | } 43 | if (method.getName().equals("hashCode") && method.getDesc().equals("()I")) { 44 | addDiscoveredSource(new Source(method, 0)); 45 | } 46 | if (method.getName().equals("equals") && method.getDesc().equals("(Ljava/lang/Object;)Z")) { 47 | addDiscoveredSource(new Source(method, 0)); 48 | addDiscoveredSource(new Source(method, 1)); 49 | } 50 | if (method.getName().equals("toString") && method.getDesc().equals("()Ljava/lang/String;")) { 51 | addDiscoveredSource(new Source(method, 0)); 52 | } 53 | } 54 | } 55 | 56 | public static void main(String[] args) throws Exception { 57 | SourceDiscovery sourceDiscovery = new HessianSourceDiscovery(); 58 | sourceDiscovery.discover(); 59 | sourceDiscovery.save(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/jackson/JacksonSourceDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.jackson; 2 | 3 | import gadgetinspector.ConfigHelper; 4 | import gadgetinspector.SourceDiscovery; 5 | import gadgetinspector.data.ClassReference; 6 | import gadgetinspector.data.GraphCall; 7 | import gadgetinspector.data.InheritanceMap; 8 | import gadgetinspector.data.MethodReference; 9 | import gadgetinspector.data.Source; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.IOException; 13 | import java.nio.file.Files; 14 | import java.nio.file.Paths; 15 | import java.util.HashSet; 16 | import java.util.Map; 17 | import java.util.Set; 18 | 19 | public class JacksonSourceDiscovery extends SourceDiscovery { 20 | 21 | public static final Set skipList = new HashSet<>(); 22 | 23 | static { 24 | if (!ConfigHelper.skipSourcesFile.isEmpty()) { 25 | try(BufferedReader bufferedReader = Files.newBufferedReader(Paths.get(ConfigHelper.skipSourcesFile))) { 26 | String line; 27 | while ((line = bufferedReader.readLine()) != null) { 28 | String c; 29 | if (!(c = line.split("#")[0].trim()).isEmpty()) { 30 | skipList.add(line.trim()); 31 | } 32 | } 33 | } catch (IOException e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | } 38 | 39 | @Override 40 | public void discover(Map classMap, 41 | Map methodMap, 42 | InheritanceMap inheritanceMap, Map> graphCallMap) { 43 | 44 | final JacksonSerializableDecider serializableDecider = new JacksonSerializableDecider(methodMap); 45 | 46 | for (MethodReference.Handle method : methodMap.keySet()) { 47 | if (skipList.contains(method.getClassReference().getName())) { 48 | continue; 49 | } 50 | if (serializableDecider.apply(method.getClassReference())) { 51 | if (method.getName().equals("") && method.getDesc().equals("()V")) { 52 | addDiscoveredSource(new Source(method, 0)); 53 | } 54 | if (method.getName().startsWith("get") && method.getDesc().startsWith("()")) { 55 | addDiscoveredSource(new Source(method, 0)); 56 | } 57 | if (method.getName().startsWith("set") && method.getDesc().matches("\\(L[^;]*;\\)V")) { 58 | addDiscoveredSource(new Source(method, 0)); 59 | } 60 | } 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /jackson-skip-sources.demo: -------------------------------------------------------------------------------- 1 | #####一些已被找到的gadget 2 | org/apache/commons/collections/functors/InvokerTransformer 3 | org/apache/commons/collections/functors/InstantiateTransformer 4 | org/apache/commons/collections4/functors/InvokerTransformer 5 | org/apache/commons/collections4/functors/InstantiateTransformer 6 | org/codehaus/groovy/runtime/ConvertedClosure 7 | org/codehaus/groovy/runtime/MethodClosure 8 | org/springframework/beans/factory/ObjectFactory 9 | com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl 10 | org/apache/xalan/xsltc/trax/TemplatesImpl 11 | com/sun/rowset/JdbcRowSetImpl 12 | java/util/logging/FileHandler 13 | java/rmi/server/UnicastRemoteObject 14 | org/springframework/beans/factory/config/PropertyPathFactoryBean 15 | org/apache/tomcat/dbcp/dbcp2/BasicDataSource 16 | com/sun/org/apache/bcel/internal/util/ClassLoader 17 | org/hibernate/jmx/StatisticsService 18 | org/apache/ibatis/datasource/jndi/JndiDataSourceFactory 19 | org/apache/ibatis/parsing/XPathParser 20 | jodd/db/connection/DataSourceConnectionProvider 21 | oracle/jdbc/connector/OracleManagedConnectionFactory 22 | oracle/jdbc/rowset/OracleJDBCRowSet 23 | org/slf4j/ext/EventData 24 | flex/messaging/util/concurrent/AsynchBeansWorkManagerExecutor 25 | com/sun/deploy/security/ruleset/DRSHelper 26 | org/apache/axis2/jaxws/spi/handler/HandlerResolverImpl 27 | org/jboss/util/propertyeditor/DocumentEditor 28 | org/apache/openjpa/ee/RegistryManagedRuntime 29 | org/apache/openjpa/ee/JNDIManagedRuntime 30 | org/apache/axis2/transport/jms/JMSOutTransportInfo 31 | com/mysql/cj/jdbc/admin/MiniAdmin 32 | ch/qos/logback/core/db/DriverManagerConnectionSource 33 | org/jdom/transform/XSLTransformer 34 | org/jdom2/transform/XSLTransformer 35 | net/sf/ehcache/transaction/manager/DefaultTransactionManagerLookup 36 | net/sf/ehcache/hibernate/EhcacheJtaTransactionManagerLookup 37 | ch/qos/logback/core/db/JNDIConnectionSource 38 | com/zaxxer/hikari/HikariConfig 39 | com/zaxxer/hikari/HikariDataSource 40 | org/apache/cxf/jaxrs/provider/XSLTJaxbProvider 41 | org/apache/commons/configuration/JNDIConfiguration 42 | org/apache/commons/configuration2/JNDIConfiguration 43 | org/apache/xalan/lib/sql/JNDIConnectionPool 44 | org/apache/commons/dbcp/datasources/PerUserPoolDataSource 45 | org/apache/commons/dbcp/datasources/SharedPoolDataSource 46 | com/p6spy/engine/spy/P6DataSource 47 | org/apache/log4j/receivers/db/DriverManagerConnectionSource 48 | org/apache/log4j/receivers/db/JNDIConnectionSource 49 | net/sf/ehcache/transaction/manager/selector/GenericJndiSelector 50 | net/sf/ehcache/transaction/manager/selector/GlassfishSelector 51 | org/apache/xbean/propertyeditor/JndiConverter 52 | org/apache/hadoop/shaded/com/zaxxer/hikari/HikariConfig 53 | com/ibatis/sqlmap/engine/transaction/jta/JtaTransactionConfig 54 | br/com/anteros/dbcp/AnterosDBCPConfig 55 | org/apache/shiro/realm/jndi/JndiRealmFactory 56 | org/apache/ignite/cache/jta/jndi/CacheJndiTmLookup 57 | org/apache/ignite/cache/jta/jndi/CacheJndiTmFactory 58 | org/apache/aries/transaction/jms/RecoverablePooledConnectionFactory 59 | org/apache/aries/transaction/jms/internal/XaPooledConnectionFactory 60 | org/quartz/utils/JNDIConnectionProvider 61 | org/apache/shiro/jndi/JndiObjectFactory 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/xstream/CustomXstreamSerializableDecider.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.xstream; 2 | 3 | import gadgetinspector.SerializableDecider; 4 | import gadgetinspector.data.ClassReference; 5 | 6 | import java.util.*; 7 | 8 | /** 9 | * Represents a custom serializability decider that implements some complex constraints in one application that was 10 | * evaluated. This helps illustrate the sort of mitigations which look constraining enough to prevent attacks but are 11 | * still insufficient. 12 | */ 13 | public class CustomXstreamSerializableDecider implements SerializableDecider { 14 | private final Map serializableMap = new HashMap<>(); 15 | private final Map classMap; 16 | private final Map> inheritanceMap; 17 | 18 | public CustomXstreamSerializableDecider(Map classMap, 19 | Map> inheritanceMap) { 20 | this.classMap = classMap; 21 | this.inheritanceMap = inheritanceMap; 22 | } 23 | 24 | @Override 25 | public Boolean apply(ClassReference.Handle handle) { 26 | List circularRefList = new ArrayList<>(); 27 | Boolean result = isSerializable(handle, circularRefList); 28 | if (circularRefList.size() != 0) { 29 | throw new IllegalStateException(); 30 | } 31 | return result; 32 | } 33 | 34 | private boolean isSerializable(ClassReference.Handle clazz, List circularRefList) { 35 | Boolean serializable = serializableMap.get(clazz); 36 | if (serializable != null) { 37 | return serializable.booleanValue(); 38 | } 39 | 40 | if (circularRefList.contains(clazz)) { 41 | serializableMap.put(clazz, Boolean.FALSE); 42 | return false; 43 | } 44 | 45 | if (clazz.getName().equals("java/lang/String")) { 46 | serializableMap.put(clazz, Boolean.TRUE); 47 | return true; 48 | } 49 | 50 | if (clazz.getName().charAt(0) == '[' || clazz.getName().equals("java/lang/Class")) { 51 | serializableMap.put(clazz, Boolean.FALSE); 52 | return false; 53 | } 54 | 55 | ClassReference classReference = classMap.get(clazz); 56 | if (classReference == null) { 57 | serializableMap.put(clazz, Boolean.TRUE); 58 | return true; 59 | } 60 | 61 | circularRefList.add(clazz); 62 | try { 63 | for (ClassReference.Member member : classReference.getMembers()) { 64 | if (member.getName().contains("$")) { 65 | serializableMap.put(clazz, Boolean.FALSE); 66 | return false; 67 | } 68 | 69 | if (!isSerializable(member.getType(), circularRefList)) { 70 | serializableMap.put(clazz, Boolean.FALSE); 71 | return false; 72 | } 73 | } 74 | for (ClassReference.Handle superClass : inheritanceMap.get(clazz)) { 75 | if (!isSerializable(superClass, circularRefList)) { 76 | serializableMap.put(clazz, Boolean.FALSE); 77 | return false; 78 | } 79 | } 80 | } finally { 81 | circularRefList.remove(circularRefList.size()-1); 82 | } 83 | 84 | serializableMap.put(clazz, Boolean.TRUE); 85 | return true; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/data/GraphCall.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.data; 2 | 3 | public class GraphCall { 4 | private final MethodReference.Handle callerMethod; 5 | private final MethodReference.Handle targetMethod; 6 | private final int callerArgIndex; 7 | private final String callerArgPath; 8 | private final int targetArgIndex; 9 | 10 | public GraphCall(MethodReference.Handle callerMethod, MethodReference.Handle targetMethod, int callerArgIndex, String callerArgPath, int targetArgIndex) { 11 | this.callerMethod = callerMethod; 12 | this.targetMethod = targetMethod; 13 | this.callerArgIndex = callerArgIndex; 14 | this.callerArgPath = callerArgPath; 15 | this.targetArgIndex = targetArgIndex; 16 | } 17 | 18 | public MethodReference.Handle getCallerMethod() { 19 | return callerMethod; 20 | } 21 | 22 | public MethodReference.Handle getTargetMethod() { 23 | return targetMethod; 24 | } 25 | 26 | public int getCallerArgIndex() { 27 | return callerArgIndex; 28 | } 29 | 30 | public String getCallerArgPath() { 31 | return callerArgPath; 32 | } 33 | 34 | public int getTargetArgIndex() { 35 | return targetArgIndex; 36 | } 37 | 38 | @Override 39 | public boolean equals(Object o) { 40 | if (this == o) return true; 41 | if (o == null || getClass() != o.getClass()) return false; 42 | 43 | GraphCall graphCall = (GraphCall) o; 44 | 45 | if (callerArgIndex != graphCall.callerArgIndex) return false; 46 | if (targetArgIndex != graphCall.targetArgIndex) return false; 47 | if (callerMethod != null ? !callerMethod.equals(graphCall.callerMethod) : graphCall.callerMethod != null) 48 | return false; 49 | if (targetMethod != null ? !targetMethod.equals(graphCall.targetMethod) : graphCall.targetMethod != null) 50 | return false; 51 | return callerArgPath != null ? callerArgPath.equals(graphCall.callerArgPath) : graphCall.callerArgPath == null; 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | int result = callerMethod != null ? callerMethod.hashCode() : 0; 57 | result = 31 * result + (targetMethod != null ? targetMethod.hashCode() : 0); 58 | result = 31 * result + callerArgIndex; 59 | result = 31 * result + (callerArgPath != null ? callerArgPath.hashCode() : 0); 60 | result = 31 * result + targetArgIndex; 61 | return result; 62 | } 63 | 64 | public static class Factory implements DataFactory { 65 | 66 | @Override 67 | public GraphCall parse(String[] fields) { 68 | return new GraphCall( 69 | new MethodReference.Handle(new ClassReference.Handle(fields[0]), fields[1], fields[2]), 70 | new MethodReference.Handle(new ClassReference.Handle(fields[3]), fields[4], fields[5]), 71 | Integer.parseInt(fields[6]), 72 | fields[7], 73 | Integer.parseInt(fields[8])); 74 | } 75 | 76 | @Override 77 | public String[] serialize(GraphCall obj) { 78 | return new String[]{ 79 | obj.callerMethod.getClassReference().getName(), obj.callerMethod.getName(), obj.callerMethod.getDesc(), 80 | obj.targetMethod.getClassReference().getName(), obj.targetMethod.getName(), obj.targetMethod.getDesc(), 81 | Integer.toString(obj.callerArgIndex), 82 | obj.callerArgPath, 83 | Integer.toString(obj.targetArgIndex), 84 | }; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/data/DataLoader.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.data; 2 | 3 | import com.google.common.io.Files; 4 | 5 | import java.io.BufferedWriter; 6 | import java.io.IOException; 7 | import java.nio.charset.StandardCharsets; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.*; 11 | 12 | public class DataLoader { 13 | public static List loadData(Path filePath, DataFactory factory) throws IOException { 14 | final List lines = Files.readLines(filePath.toFile(), StandardCharsets.UTF_8); 15 | final List values = new ArrayList(lines.size()); 16 | for (String line : lines) { 17 | values.add(factory.parse(line.split("\t", -1))); 18 | } 19 | return values; 20 | } 21 | 22 | public static void saveData(Path filePath, DataFactory factory, Collection values) throws IOException { 23 | try (BufferedWriter writer = Files.newWriter(filePath.toFile(), StandardCharsets.UTF_8)) { 24 | for (T value : values) { 25 | final String[] fields = factory.serialize(value); 26 | if (fields == null) { 27 | continue; 28 | } 29 | 30 | StringBuilder sb = new StringBuilder(); 31 | for (String field : fields) { 32 | if (field == null) { 33 | sb.append("\t"); 34 | } else { 35 | sb.append("\t").append(field); 36 | } 37 | } 38 | writer.write(sb.substring(1)); 39 | writer.write("\n"); 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * 从classes.dat加载类信息 46 | * 47 | * @return 48 | */ 49 | public static Map loadClasses() { 50 | try { 51 | Map classMap = new HashMap<>(); 52 | for (ClassReference classReference : loadData(Paths.get("classes.dat"), new ClassReference.Factory())) { 53 | classMap.put(classReference.getHandle(), classReference); 54 | } 55 | return classMap; 56 | } catch (IOException e) { 57 | throw new RuntimeException(e); 58 | } 59 | } 60 | 61 | /** 62 | * 从methods.dat加载所有方法信息 63 | * 64 | * @return 65 | */ 66 | public static Map loadMethods() { 67 | try { 68 | Map methodMap = new HashMap<>(); 69 | for (MethodReference methodReference : loadData(Paths.get("methods.dat"), new MethodReference.Factory())) { 70 | methodMap.put(methodReference.getHandle(), methodReference); 71 | } 72 | return methodMap; 73 | } catch (IOException e) { 74 | throw new RuntimeException(e); 75 | } 76 | } 77 | 78 | /** 79 | * 从slinks.dat加载特殊的slink信息 80 | * 81 | * @return 82 | */ 83 | public static Map> loadSlinks() { 84 | try { 85 | Map> methodMap = new HashMap<>(); 86 | for (SlinkReference slinkReference : loadData(Paths.get("slinks.dat"), new SlinkFactory())) { 87 | methodMap.put(slinkReference.getClassReference(), slinkReference.getMethodReferences()); 88 | } 89 | return methodMap; 90 | } catch (IOException e) { 91 | e.printStackTrace(); 92 | } 93 | return Collections.EMPTY_MAP; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/data/InheritanceMap.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.data; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Paths; 5 | import java.util.*; 6 | 7 | public class InheritanceMap { 8 | //子-父关系集合 9 | private final Map> inheritanceMap; 10 | //父-子关系集合 11 | private final Map> subClassMap; 12 | 13 | public InheritanceMap(Map> inheritanceMap) { 14 | this.inheritanceMap = inheritanceMap; 15 | subClassMap = new HashMap<>(); 16 | for (Map.Entry> entry : inheritanceMap.entrySet()) { 17 | ClassReference.Handle child = entry.getKey(); 18 | for (ClassReference.Handle parent : entry.getValue()) { 19 | subClassMap.computeIfAbsent(parent, k -> new HashSet<>()).add(child); 20 | } 21 | } 22 | } 23 | 24 | public Set>> entrySet() { 25 | return inheritanceMap.entrySet(); 26 | } 27 | 28 | public Set getSuperClasses(ClassReference.Handle clazz) { 29 | Set parents = inheritanceMap.get(clazz); 30 | if (parents == null) { 31 | return null; 32 | } 33 | return Collections.unmodifiableSet(parents); 34 | } 35 | 36 | public boolean isSubclassOf(ClassReference.Handle clazz, ClassReference.Handle superClass) { 37 | if(clazz.getName().equals(superClass.getName())){ 38 | return true; 39 | } 40 | Set parents = inheritanceMap.get(clazz); 41 | if (parents == null) { 42 | return false; 43 | } 44 | return parents.contains(superClass); 45 | } 46 | 47 | public Set getSubClasses(ClassReference.Handle clazz) { 48 | Set subClasses = subClassMap.get(clazz); 49 | if (subClasses == null) { 50 | return null; 51 | } 52 | return Collections.unmodifiableSet(subClasses); 53 | } 54 | 55 | public void save() throws IOException { 56 | //inheritanceMap.dat数据格式: 57 | //类名 父类或超类或接口类1 父类或超类或接口类2 父类或超类或接口类3 ... 58 | DataLoader.saveData(Paths.get("inheritanceMap.dat"), new InheritanceMapFactory(), inheritanceMap.entrySet()); 59 | } 60 | 61 | public static InheritanceMap load() throws IOException { 62 | Map> inheritanceMap = new HashMap<>(); 63 | for (Map.Entry> entry : DataLoader.loadData( 64 | Paths.get("inheritanceMap.dat"), new InheritanceMapFactory())) { 65 | inheritanceMap.put(entry.getKey(), entry.getValue()); 66 | } 67 | return new InheritanceMap(inheritanceMap); 68 | } 69 | 70 | private static class InheritanceMapFactory implements DataFactory>> { 71 | @Override 72 | public Map.Entry> parse(String[] fields) { 73 | ClassReference.Handle clazz = new ClassReference.Handle(fields[0]); 74 | Set superClasses = new HashSet<>(); 75 | for (int i = 1; i < fields.length; i++) { 76 | superClasses.add(new ClassReference.Handle(fields[i])); 77 | } 78 | return new AbstractMap.SimpleEntry<>(clazz, superClasses); 79 | } 80 | 81 | @Override 82 | public String[] serialize(Map.Entry> obj) { 83 | final String[] fields = new String[obj.getValue().size()+1]; 84 | fields[0] = obj.getKey().getName(); 85 | int i = 1; 86 | for (ClassReference.Handle handle : obj.getValue()) { 87 | fields[i++] = handle.getName(); 88 | } 89 | return fields; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /fastjson-skip-sources.demo: -------------------------------------------------------------------------------- 1 | ####一些在非污点模式下发现无法利用的类 2 | ####JNDI 3 | com/sun/org/apache/xerces/internal/parsers/XML11Configuration 4 | com/caucho/vfs/JndiPath 5 | com/alibaba/druid/filter/logging/Log4j2Filter 6 | org/python/apache/xerces/jaxp/SAXParserImpl 7 | com/caucho/quercus/servlet/QuercusServlet 8 | org/apache/commons/mail/Email 9 | com/netflix/config/ConfigurationManager 10 | com/intellij/openapi/editor/impl/DocumentImpl 11 | javax/swing/JComboBox 12 | org/eclipse/persistence/internal/sessions/DirectCollectionChangeRecord 13 | org/apache/curator/framework/recipes/cache/NodeCache 14 | org/apache/catalina/core/ContainerBase 15 | org/codehaus/groovy/ast/expr/MethodCallExpression 16 | io/sentry/Sentry 17 | freemarker/template/Configuration 18 | org/apache/camel/processor/interceptor/BacklogTracer 19 | org/apache/xerces/jaxp/DocumentBuilderImpl 20 | javafx/embed/swing/SwingNode 21 | java/security/Signer 22 | org/glassfish/jersey/message/internal/AbstractJaxbProvider 23 | org/apache/commons/discovery/log/DiscoveryLogFactory 24 | dk/itst/oiosaml/sp/service/SPFilter 25 | com/ibm/as400/access/AS400JDBCManagedDataSource 26 | org/apache/crimson/jaxp/SAXParserImpl 27 | org/kapott/hbci/passport/AbstractPinTanPassport 28 | fr/inria/corese/tinkerpop/TinkerpopGraph 29 | org/elasticsearch/common/netty/bootstrap/Bootstrap 30 | org/eclipse/jdt/internal/core/JavaModelManager 31 | javax/swing/JTextField 32 | java/security/Signer 33 | jdk/nashorn/api/scripting/ScriptObjectMirror 34 | sun/security/provider/certpath/ldap/LDAPCertStore$LDAPRequest 35 | javafx/embed/swing/SwingNode 36 | javax/management/relation/RelationSupport 37 | java/awt/dnd/DragGestureRecognizer 38 | javafx/embed/swing/SwingFXUtils 39 | 40 | ####XXE & SSRF 41 | javafx/embed/swing/SwingNode$SwingNodeContent 42 | javafx/scene/control/Spinner 43 | org/apache/beam/vendor/grpc/v1p13p1/io/grpc/internal/DelayedStream 44 | org/exolab/castor/builder/SourceGenerator 45 | javax/accessibility/AccessibleContext 46 | java/awt/image/ImageFilter 47 | org/apache/cxf/jaxws/handler/logical/LogicalMessageImpl 48 | jdk/nashorn/api/scripting/ScriptObjectMirror 49 | javax/swing/JPopupMenu 50 | javax/management/modelmbean/RequiredModelMBean 51 | java/awt/SplashScreen 52 | org/apache/http/protocol/UriPatternMatcher 53 | org/eclipse/jetty/servlet/ErrorPageErrorHandler 54 | org/apache/hadoop/mapreduce/jobhistory/JobFinishedEvent 55 | org/apache/cxf/jaxrs/AbstractJAXRSFactoryBean 56 | javax/swing/AbstractButton 57 | javax/swing/DefaultCellEditor$1 58 | abbot/editor/recorder/Recorder 59 | org/apache/beam/vendor/grpc/v1_13_1/io/grpc/internal/DelayedStream 60 | sun/swing/JLightweightFrame 61 | org/apache/myfaces/view/facelets/component/UIRepeat 62 | org/apache/cxf/transports/http/configuration/HTTPServerPolicy 63 | org/apache/camel/impl/ComponentConfigurationSupport 64 | javax/swing/JProgressBar 65 | java/awt/Toolkit$DesktopPropertyChangeSupport 66 | java/awt/Component 67 | com/sun/xml/internal/ws/api/addressing/WSEndpointReference 68 | org/apache/myfaces/taglib/core/ViewTag 69 | org/apache/cxf/transports/http/configuration/HTTPClientPolicy 70 | javax/swing/table/DefaultTableCellRenderer 71 | javax/swing/plaf/basic/BasicComboBoxEditor 72 | org/apache/storm/shade/org/eclipse/jetty/http/MimeTypes 73 | java/beans/PropertyChangeSupport 74 | sun/util/locale/InternalLocaleBuilder 75 | sun/rmi/transport/tcp/TCPEndpoint 76 | org/apache/beam/vendor/grpc/v1/io/grpc/internal/DelayedStream 77 | org/apache/jackrabbit/oak/plugins/document/util/TimingDocumentStoreWrapper 78 | io/grpc/internal/DelayedStream 79 | org/apache/camel/com/googlecode/concurrentlinkedhashmap/ConcurrentLinkedHashMap 80 | org/apache/catalina/valves/rewrite/RewriteValve 81 | com/actelion/research/orbit/gui/RdfSearchBox 82 | org/apache/batik/swing/JSVGCanvas 83 | org/apache/calcite/avatica/tck/shaded/org/hsqldb/jdbc/JDBCSQLXML 84 | nu/xom/DocType 85 | 86 | 87 | 88 | #####一些已被找到的gadget 89 | #####JNDI 90 | org/apache/xbean/propertyeditor/JndiConverter 91 | org/apache/xbean/propertyeditor/AbstractConverter 92 | 93 | #####SSRF & XXE 94 | org/apache/cxf/jaxrs/model/wadl/WadlGenerator 95 | org/apache/cxf/jaxrs/utils/schemas/SchemaHandler 96 | org/apache/commons/jelly/impl/Embedded 97 | javax/swing/JEditorPane 98 | org.exolab.castor.dtx.DTXEngine 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/data/MethodReference.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.data; 2 | 3 | public class MethodReference { 4 | private final ClassReference.Handle classReference; 5 | private final String name; 6 | private final String desc; 7 | private final boolean isStatic; 8 | private String methodAnnotationDesc = "none"; 9 | private String parameterAnnotationDesc = "none"; 10 | 11 | 12 | public MethodReference(ClassReference.Handle classReference, String name, String desc, boolean isStatic) { 13 | this.classReference = classReference; 14 | this.name = name; 15 | this.desc = desc; 16 | this.isStatic = isStatic; 17 | 18 | } 19 | public MethodReference(ClassReference.Handle classReference, String name, String desc, boolean isStatic, String methodAnnotationDesc, String parameterAnnotationDesc) { 20 | this.classReference = classReference; 21 | this.name = name; 22 | this.desc = desc; 23 | this.isStatic = isStatic; 24 | this.methodAnnotationDesc = methodAnnotationDesc; 25 | this.parameterAnnotationDesc = parameterAnnotationDesc; 26 | 27 | } 28 | 29 | public ClassReference.Handle getClassReference() { 30 | return classReference; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public String getDesc() { 38 | return desc; 39 | } 40 | 41 | public boolean isStatic() { 42 | return isStatic; 43 | } 44 | public String getMethodAnnotationDesc(){ 45 | return this.methodAnnotationDesc; 46 | } 47 | public String getParameterAnnotationDesc(){ 48 | return this.parameterAnnotationDesc; 49 | } 50 | 51 | public Handle getHandle() { 52 | return new Handle(classReference, name, desc); 53 | } 54 | 55 | public static class Handle { 56 | private final ClassReference.Handle classReference; 57 | private final String name; 58 | private final String desc; 59 | 60 | public Handle(ClassReference.Handle classReference, String name, String desc) { 61 | this.classReference = classReference; 62 | this.name = name; 63 | this.desc = desc; 64 | } 65 | 66 | public ClassReference.Handle getClassReference() { 67 | return classReference; 68 | } 69 | 70 | public String getName() { 71 | return name; 72 | } 73 | 74 | public String getDesc() { 75 | return desc; 76 | } 77 | 78 | @Override 79 | public boolean equals(Object o) { 80 | if (this == o) return true; 81 | if (o == null || getClass() != o.getClass()) return false; 82 | 83 | Handle handle = (Handle) o; 84 | 85 | if (classReference != null ? !classReference.equals(handle.classReference) : handle.classReference != null) 86 | return false; 87 | if (name != null ? !name.equals(handle.name) : handle.name != null) return false; 88 | return desc != null ? desc.equals(handle.desc) : handle.desc == null; 89 | } 90 | 91 | @Override 92 | public int hashCode() { 93 | int result = classReference != null ? classReference.hashCode() : 0; 94 | result = 31 * result + (name != null ? name.hashCode() : 0); 95 | result = 31 * result + (desc != null ? desc.hashCode() : 0); 96 | return result; 97 | } 98 | } 99 | 100 | public static class Factory implements DataFactory { 101 | 102 | @Override 103 | public MethodReference parse(String[] fields) { 104 | return new MethodReference( 105 | new ClassReference.Handle(fields[0]), 106 | fields[1], 107 | fields[2], 108 | Boolean.parseBoolean(fields[3]), 109 | fields[4], 110 | fields[5]); 111 | } 112 | 113 | @Override 114 | public String[] serialize(MethodReference obj) { 115 | return new String[] { 116 | obj.classReference.getName(), 117 | obj.name, 118 | obj.desc, 119 | Boolean.toString(obj.isStatic), 120 | obj.methodAnnotationDesc, 121 | obj.parameterAnnotationDesc 122 | }; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/javaserial/SimpleSourceDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.javaserial; 2 | 3 | import gadgetinspector.SerializableDecider; 4 | import gadgetinspector.SourceDiscovery; 5 | import gadgetinspector.data.ClassReference; 6 | import gadgetinspector.data.GraphCall; 7 | import gadgetinspector.data.InheritanceMap; 8 | import gadgetinspector.data.MethodReference; 9 | import gadgetinspector.data.Source; 10 | import java.util.Set; 11 | import org.objectweb.asm.Type; 12 | 13 | import java.util.Map; 14 | 15 | public class SimpleSourceDiscovery extends SourceDiscovery { 16 | 17 | @Override 18 | public void discover(Map classMap, 19 | Map methodMap, 20 | InheritanceMap inheritanceMap, Map> graphCallMap) { 21 | 22 | final SerializableDecider serializableDecider = new SimpleSerializableDecider(inheritanceMap); 23 | 24 | for (MethodReference.Handle method : methodMap.keySet()) { 25 | if (Boolean.TRUE.equals(serializableDecider.apply(method.getClassReference()))) { 26 | if (method.getName().equals("finalize") && method.getDesc().equals("()V")) { 27 | addDiscoveredSource(new Source(method, 0)); 28 | } 29 | } 30 | } 31 | 32 | // If a class implements readObject, the ObjectInputStream passed in is considered tainted 33 | for (MethodReference.Handle method : methodMap.keySet()) { 34 | if (Boolean.TRUE.equals(serializableDecider.apply(method.getClassReference()))) { 35 | if (method.getName().equals("readObject") && method.getDesc().equals("(Ljava/io/ObjectInputStream;)V")) { 36 | addDiscoveredSource(new Source(method, 1)); 37 | } 38 | } 39 | } 40 | 41 | // Using the proxy trick, anything extending serializable and invocation handler is tainted. 42 | for (ClassReference.Handle clazz : classMap.keySet()) { 43 | if (Boolean.TRUE.equals(serializableDecider.apply(clazz)) 44 | && inheritanceMap.isSubclassOf(clazz, new ClassReference.Handle("java/lang/reflect/InvocationHandler"))) { 45 | MethodReference.Handle method = new MethodReference.Handle( 46 | clazz, "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;"); 47 | 48 | addDiscoveredSource(new Source(method, 0)); 49 | } 50 | } 51 | 52 | // hashCode() or equals() are accessible entry points using standard tricks of putting those objects 53 | // into a HashMap. 54 | for (MethodReference.Handle method : methodMap.keySet()) { 55 | if (Boolean.TRUE.equals(serializableDecider.apply(method.getClassReference()))) { 56 | if (method.getName().equals("hashCode") && method.getDesc().equals("()I")) { 57 | addDiscoveredSource(new Source(method, 0)); 58 | } 59 | if (method.getName().equals("equals") && method.getDesc().equals("(Ljava/lang/Object;)Z")) { 60 | addDiscoveredSource(new Source(method, 0)); 61 | addDiscoveredSource(new Source(method, 1)); 62 | } 63 | } 64 | } 65 | 66 | // Using a comparator proxy, we can jump into the call() / doCall() method of any groovy Closure and all the 67 | // args are tainted. 68 | // https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/Groovy1.java 69 | for (MethodReference.Handle method : methodMap.keySet()) { 70 | if (Boolean.TRUE.equals(serializableDecider.apply(method.getClassReference())) 71 | && inheritanceMap.isSubclassOf(method.getClassReference(), new ClassReference.Handle("groovy/lang/Closure")) 72 | && (method.getName().equals("call") || method.getName().equals("doCall"))) { 73 | 74 | addDiscoveredSource(new Source(method, 0)); 75 | Type[] methodArgs = Type.getArgumentTypes(method.getDesc()); 76 | for (int i = 0; i < methodArgs.length; i++) { 77 | addDiscoveredSource(new Source(method, i + 1)); 78 | } 79 | } 80 | } 81 | } 82 | 83 | public static void main(String[] args) throws Exception { 84 | SourceDiscovery sourceDiscovery = new SimpleSourceDiscovery(); 85 | sourceDiscovery.discover(); 86 | sourceDiscovery.save(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/webservice/WebserviceSourceDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.webservice; 2 | 3 | import gadgetinspector.ConfigHelper; 4 | import gadgetinspector.SourceDiscovery; 5 | import gadgetinspector.data.*; 6 | import gadgetinspector.jackson.JacksonSerializableDecider; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Paths; 12 | import java.util.HashSet; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | public class WebserviceSourceDiscovery extends SourceDiscovery { 17 | public static final Set skipList = new HashSet<>(); 18 | 19 | static { 20 | if (!ConfigHelper.skipSourcesFile.isEmpty()) { 21 | try(BufferedReader bufferedReader = Files.newBufferedReader(Paths.get(ConfigHelper.skipSourcesFile))) { 22 | String line; 23 | while ((line = bufferedReader.readLine()) != null) { 24 | String c; 25 | if (!(c = line.split("#")[0].trim()).isEmpty()) { 26 | skipList.add(line.trim()); 27 | } 28 | } 29 | } catch (IOException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | } 34 | 35 | @Override 36 | public void discover(Map classMap, Map methodMap, InheritanceMap inheritanceMap, Map> graphCallMap) { 37 | final WebserviceSerializableDecider webserviceDecider = new WebserviceSerializableDecider(methodMap); 38 | 39 | for (MethodReference.Handle method : methodMap.keySet()) { 40 | MethodReference methodValue = methodMap.get(method); 41 | boolean skipFlag = false; 42 | for (String skipClass:skipList){ 43 | if (method.getClassReference().getName().contains(skipClass)){ 44 | skipFlag = true; 45 | break; 46 | } 47 | } 48 | if (skipFlag){ 49 | continue; 50 | } 51 | 52 | if (webserviceDecider.apply(method.getClassReference())) { 53 | // if(method.getClassReference().getName().contains("org/joychou/controller/PathTraversal")){ 54 | // System.out.println("test by 5wimming"); 55 | // } 56 | if (!method.getName().contains("") 57 | && (method.getDesc().contains("Ljavax/servlet/http/HttpServletRequest") 58 | || method.getDesc().contains("Ljavax/servlet/ServletRequest") 59 | || method.getDesc().contains("Ljavax/xml/ws/handler/soap/SOAPMessageContext") 60 | || method.getDesc().contains("Ljavax/xml/ws/handler/MessageContext") 61 | || method.getDesc().contains("Ljavax/xml/rpc/handler/soap/SOAPMessageContext") 62 | || method.getDesc().contains("Lorg/apache/cxf/message/Message") 63 | || method.getDesc().contains("Lorg/aopalliance/intercept/MethodInvocation") 64 | || methodValue.getMethodAnnotationDesc().contains("Lorg/springframework/web/bind/annotation/RequestMapping") 65 | || methodValue.getMethodAnnotationDesc().contains("Ljavax/ws/rs/Path") 66 | || methodValue.getMethodAnnotationDesc().contains("Lorg/springframework/web/bind/annotation/GetMapping") 67 | || methodValue.getMethodAnnotationDesc().contains("Lorg/springframework/web/bind/annotation/PostMapping") 68 | || methodValue.getParameterAnnotationDesc().contains("Lorg/springframework/web/bind/annotation/RequestParam") 69 | || methodValue.getMethodAnnotationDesc().contains("Lorg/springframework/web/bind/annotation/PutMapping") 70 | || methodValue.getMethodAnnotationDesc().contains("Lorg/springframework/web/bind/annotation/PatchMapping") 71 | || methodValue.getParameterAnnotationDesc().contains("Lorg/springframework/web/bind/annotation/DeleteMapping") 72 | || methodValue.getParameterAnnotationDesc().contains("Ljavax/ws/rs/QueryParam") 73 | || methodValue.getParameterAnnotationDesc().contains("Ljavax/ws/PathParam"))) 74 | { 75 | addDiscoveredSource(new Source(method, 0)); 76 | } 77 | // if (!method.getName().contains("") 78 | // && (method.getClassReference().getName().contains("org/springframework/web/method/support/InvocableHandlerMethod"))) 79 | // { 80 | // addDiscoveredSource(new Source(method, 0)); 81 | // } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/ClassResourceEnumerator.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | import com.google.common.reflect.ClassPath; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.net.*; 8 | import java.nio.file.*; 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import javax.crypto.Cipher; 12 | 13 | public class ClassResourceEnumerator { 14 | private final ClassLoader classLoader; 15 | 16 | public ClassResourceEnumerator(ClassLoader classLoader) throws IOException { 17 | this.classLoader = classLoader; 18 | } 19 | 20 | /** 21 | * java runtime所有的class(rt.jar)和指定的jar或war中的所有class 22 | * 23 | * @return 24 | * @throws IOException 25 | */ 26 | public Collection getAllClasses() throws IOException { 27 | Collection result = new ArrayList<>(getRuntimeClasses()); 28 | if (ConfigHelper.onlyJDK) 29 | return result; 30 | for (ClassPath.ClassInfo classInfo : ClassPath.from(classLoader).getAllClasses()) { 31 | result.add(new ClassLoaderClassResource(classLoader, classInfo.getResourceName())); 32 | } 33 | return result; 34 | } 35 | 36 | private Collection getRuntimeClasses() throws IOException { 37 | // A hacky way to get the current JRE's rt.jar. Depending on the class loader, rt.jar may be in the 38 | // bootstrap classloader so all the JDK classes will be excluded from classpath scanning with this! 39 | // However, this only works up to Java 8, since after that Java uses some crazy module magic. 40 | //加载rt.jar的所有class 41 | URL stringClassUrl = Object.class.getResource("String.class"); 42 | URLConnection connection = stringClassUrl.openConnection(); 43 | Collection result = new ArrayList<>(); 44 | if (connection instanceof JarURLConnection) { 45 | URL runtimeUrl = ((JarURLConnection) connection).getJarFileURL(); 46 | URLClassLoader classLoader = new URLClassLoader(new URL[]{runtimeUrl}); 47 | 48 | for (ClassPath.ClassInfo classInfo : ClassPath.from(classLoader).getAllClasses()) { 49 | result.add(new ClassLoaderClassResource(classLoader, classInfo.getResourceName())); 50 | } 51 | } 52 | 53 | // URL cipherClassUrl = Cipher.class.getResource("Cipher.class"); 54 | // URLConnection connection2 = cipherClassUrl.openConnection(); 55 | // if (connection2 instanceof JarURLConnection) { 56 | // URL runtimeUrl = ((JarURLConnection) connection2).getJarFileURL(); 57 | // URLClassLoader classLoader = new URLClassLoader(new URL[]{runtimeUrl}); 58 | // 59 | // for (ClassPath.ClassInfo classInfo : ClassPath.from(classLoader).getAllClasses()) { 60 | // result.add(new ClassLoaderClassResource(classLoader, classInfo.getResourceName())); 61 | // } 62 | // } 63 | if (!result.isEmpty()) 64 | return result; 65 | 66 | // Try finding all the JDK classes using the Java9+ modules method: 67 | try { 68 | FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); 69 | Files.walk(fs.getPath("/")).forEach(p -> { 70 | if (p.toString().toLowerCase().endsWith(".class")) { 71 | result.add(new PathClassResource(p)); 72 | } 73 | }); 74 | } catch (ProviderNotFoundException e) { 75 | // Do nothing; this is expected on versions below Java9 76 | } 77 | 78 | return result; 79 | } 80 | 81 | public static interface ClassResource { 82 | public InputStream getInputStream() throws IOException; 83 | public String getName(); 84 | } 85 | 86 | private static class PathClassResource implements ClassResource { 87 | private final Path path; 88 | 89 | private PathClassResource(Path path) { 90 | this.path = path; 91 | } 92 | 93 | @Override 94 | public InputStream getInputStream() throws IOException { 95 | return Files.newInputStream(path); 96 | } 97 | 98 | @Override 99 | public String getName() { 100 | return path.toString(); 101 | } 102 | } 103 | 104 | private static class ClassLoaderClassResource implements ClassResource { 105 | private final ClassLoader classLoader; 106 | private final String resourceName; 107 | 108 | private ClassLoaderClassResource(ClassLoader classLoader, String resourceName) { 109 | this.classLoader = classLoader; 110 | this.resourceName = resourceName; 111 | } 112 | 113 | @Override 114 | public InputStream getInputStream() throws IOException { 115 | return classLoader.getResourceAsStream(resourceName); 116 | } 117 | 118 | @Override 119 | public String getName() { 120 | return resourceName; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/data/ClassReference.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.data; 2 | 3 | import java.util.HashSet; 4 | import java.util.List; 5 | import java.util.Set; 6 | 7 | public class ClassReference { 8 | private final String name; 9 | private final String superClass; 10 | private final String[] interfaces; 11 | private final boolean isInterface; 12 | private final Member[] members; 13 | private final Set annotations; 14 | 15 | public static class Member { 16 | private final String name; 17 | private final int modifiers; 18 | private final ClassReference.Handle type; 19 | 20 | public Member(String name, int modifiers, Handle type) { 21 | this.name = name; 22 | this.modifiers = modifiers; 23 | this.type = type; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public int getModifiers() { 31 | return modifiers; 32 | } 33 | 34 | public Handle getType() { 35 | return type; 36 | } 37 | } 38 | 39 | public ClassReference(String name, String superClass, String[] interfaces, boolean isInterface, Member[] members, Set annotations) { 40 | this.name = name; 41 | this.superClass = superClass; 42 | this.interfaces = interfaces; 43 | this.isInterface = isInterface; 44 | this.members = members; 45 | this.annotations = annotations; 46 | } 47 | 48 | public String getName() { 49 | return name; 50 | } 51 | 52 | public String getSuperClass() { 53 | return superClass; 54 | } 55 | 56 | public String[] getInterfaces() { 57 | return interfaces; 58 | } 59 | 60 | public boolean isInterface() { 61 | return isInterface; 62 | } 63 | 64 | public Member[] getMembers() { 65 | return members; 66 | } 67 | 68 | public Handle getHandle() { 69 | return new Handle(name); 70 | } 71 | 72 | public Set getAnnotations() { 73 | return annotations; 74 | } 75 | 76 | public static class Handle { 77 | private final String name; 78 | 79 | public Handle(String name) { 80 | this.name = name; 81 | } 82 | 83 | public String getName() { 84 | return name; 85 | } 86 | 87 | @Override 88 | public boolean equals(Object o) { 89 | if (this == o) return true; 90 | if (o == null || getClass() != o.getClass()) return false; 91 | 92 | Handle handle = (Handle) o; 93 | 94 | return name != null ? name.equals(handle.name) : handle.name == null; 95 | } 96 | 97 | @Override 98 | public int hashCode() { 99 | return name != null ? name.hashCode() : 0; 100 | } 101 | } 102 | 103 | public static class Factory implements DataFactory { 104 | 105 | @Override 106 | public ClassReference parse(String[] fields) { 107 | String[] interfaces; 108 | if (fields[2].equals("")) { 109 | interfaces = new String[0]; 110 | } else { 111 | interfaces = fields[2].split(","); 112 | } 113 | 114 | String[] memberEntries = fields[4].split("!"); 115 | Member[] members = new Member[memberEntries.length/3]; 116 | for (int i = 0; i < members.length; i++) { 117 | members[i] = new Member(memberEntries[3*i], Integer.parseInt(memberEntries[3*i+1]), 118 | new ClassReference.Handle(memberEntries[3*i+2])); 119 | } 120 | String[] tmpAnnotations = fields[5].split(","); 121 | Set annotations = new HashSet<>(); 122 | for (int i = 0; i < tmpAnnotations.length; i++) { 123 | if (tmpAnnotations.length > 0) { 124 | annotations.add(tmpAnnotations[i]); 125 | } 126 | } 127 | 128 | return new ClassReference( 129 | fields[0], 130 | fields[1].equals("") ? null : fields[1], 131 | interfaces, 132 | Boolean.parseBoolean(fields[3]), 133 | members, 134 | annotations); 135 | } 136 | 137 | @Override 138 | public String[] serialize(ClassReference obj) { 139 | String interfaces; 140 | if (obj.interfaces.length > 0) { 141 | StringBuilder interfacesSb = new StringBuilder(); 142 | for (String iface : obj.interfaces) { 143 | interfacesSb.append(",").append(iface); 144 | } 145 | interfaces = interfacesSb.substring(1); 146 | } else { 147 | interfaces = ""; 148 | } 149 | 150 | StringBuilder members = new StringBuilder(); 151 | for (Member member : obj.members) { 152 | members.append("!").append(member.getName()) 153 | .append("!").append(Integer.toString(member.getModifiers())) 154 | .append("!").append(member.getType().getName()); 155 | } 156 | 157 | StringBuilder annotations = new StringBuilder(); 158 | for (String a : obj.annotations) { 159 | annotations.append(a).append(","); 160 | } 161 | 162 | return new String[]{ 163 | obj.name, 164 | obj.superClass, 165 | interfaces, 166 | Boolean.toString(obj.isInterface), 167 | members.length() == 0 ? null : members.substring(1), 168 | annotations.length() > 0 ? annotations.substring(0, annotations.length() - 1) : "" 169 | }; 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/data/InheritanceDeriver.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.data; 2 | 3 | import gadgetinspector.data.ClassReference.Handle; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | public class InheritanceDeriver { 13 | private static final Logger LOGGER = LoggerFactory.getLogger(InheritanceDeriver.class); 14 | 15 | public static InheritanceMap derive(Map classMap) { 16 | LOGGER.debug("Calculating inheritance for " + (classMap.size()) + " classes..."); 17 | Map> implicitInheritance = new HashMap<>(); 18 | //遍历所有类 19 | for (ClassReference classReference : classMap.values()) { 20 | if (implicitInheritance.containsKey(classReference.getHandle())) { 21 | throw new IllegalStateException("Already derived implicit classes for " + classReference.getName()); 22 | } 23 | Set allParents = new HashSet<>(); 24 | 25 | //获取classReference的所有父类、超类、接口类 26 | getAllParents(classReference, classMap, allParents); 27 | //添加缓存:类名 -> 所有的父类、超类、接口类 28 | implicitInheritance.put(classReference.getHandle(), allParents); 29 | } 30 | //InheritanceMap翻转集合,转换为{class:[subclass]} 31 | return new InheritanceMap(implicitInheritance); 32 | } 33 | 34 | /** 35 | * 获取classReference的所有父类、超类、接口类 36 | * 37 | * @param classReference 38 | * @param classMap 39 | * @param allParents 40 | */ 41 | private static void getAllParents(ClassReference classReference, Map classMap, Set allParents) { 42 | Set parents = new HashSet<>(); 43 | //把当前classReference类的父类添加到parents 44 | if (classReference.getSuperClass() != null) { 45 | parents.add(new ClassReference.Handle(classReference.getSuperClass())); 46 | } 47 | //把当前classReference类实现的所有接口添加到parents 48 | for (String iface : classReference.getInterfaces()) { 49 | parents.add(new ClassReference.Handle(iface)); 50 | } 51 | 52 | for (ClassReference.Handle immediateParent : parents) { 53 | //从所有类数据集合中,遍历找出classReference的父类、接口 54 | ClassReference parentClassReference = classMap.get(immediateParent); 55 | if (parentClassReference == null) { 56 | LOGGER.debug("No class id for " + immediateParent.getName()); 57 | continue; 58 | } 59 | //继续添加到集合中 60 | allParents.add(parentClassReference.getHandle()); 61 | //继续递归查找,直到把classReference类的所有父类、超类、接口类都添加到allParents 62 | getAllParents(parentClassReference, classMap, allParents); 63 | } 64 | } 65 | 66 | public static Map> getAllMethodImplementations( 67 | InheritanceMap inheritanceMap, Map methodMap) { 68 | 69 | //遍历整合,得到每个类的所有方法实现,形成 类->实现的方法集 的映射 70 | Map> methodsByClass = getMethodsByClass(methodMap); 71 | 72 | //遍历继承关系数据,形成 父类->子孙类集 的映射 73 | Map> subClassMap = new HashMap<>(); 74 | for (Map.Entry> entry : inheritanceMap.entrySet()) { 75 | for (ClassReference.Handle parent : entry.getValue()) { 76 | if (!subClassMap.containsKey(parent)) { 77 | Set subClasses = new HashSet<>(); 78 | subClasses.add(entry.getKey()); 79 | subClassMap.put(parent, subClasses); 80 | } else { 81 | subClassMap.get(parent).add(entry.getKey()); 82 | } 83 | } 84 | } 85 | 86 | Map> methodImplMap = new HashMap<>(); 87 | for (MethodReference method : methodMap.values()) { 88 | // Static methods cannot be overriden 89 | if (method.isStatic()) { 90 | continue; 91 | } 92 | 93 | Set overridingMethods = new HashSet<>(); 94 | Set subClasses = subClassMap.get(method.getClassReference()); 95 | if (subClasses != null) { 96 | for (ClassReference.Handle subClass : subClasses) { 97 | // This class extends ours; see if it has a matching method 98 | Set subClassMethods = methodsByClass.get(subClass); 99 | if (subClassMethods != null) { 100 | for (MethodReference.Handle subClassMethod : subClassMethods) { 101 | if (subClassMethod.getName().equals(method.getName()) && subClassMethod.getDesc().equals(method.getDesc())) { 102 | overridingMethods.add(subClassMethod); 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | if (overridingMethods.size() > 0) { 110 | methodImplMap.put(method.getHandle(), overridingMethods); 111 | } 112 | } 113 | 114 | return methodImplMap; 115 | } 116 | 117 | public static Map> getMethodsByClass( 118 | Map methodMap) { 119 | Map> methodsByClass = new HashMap<>(); 120 | for (MethodReference.Handle method : methodMap.keySet()) { 121 | Handle classReference = method.getClassReference(); 122 | if (!methodsByClass.containsKey(classReference)) { 123 | Set methods = new HashSet<>(); 124 | methods.add(method); 125 | methodsByClass.put(classReference, methods); 126 | } else { 127 | methodsByClass.get(classReference).add(method); 128 | } 129 | } 130 | return methodsByClass; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/fastjson/FastjsonSourceDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector.fastjson; 2 | 3 | import gadgetinspector.ConfigHelper; 4 | import gadgetinspector.SourceDiscovery; 5 | import gadgetinspector.data.ClassReference; 6 | import gadgetinspector.data.GraphCall; 7 | import gadgetinspector.data.InheritanceMap; 8 | import gadgetinspector.data.MethodReference; 9 | import gadgetinspector.data.Source; 10 | import java.io.BufferedReader; 11 | import java.io.IOException; 12 | import java.nio.file.Files; 13 | import java.nio.file.Paths; 14 | import java.util.Arrays; 15 | import java.util.HashSet; 16 | import java.util.Map; 17 | import java.util.Set; 18 | 19 | public class FastjsonSourceDiscovery extends SourceDiscovery { 20 | 21 | public static final Set skipList = new HashSet<>(); 22 | 23 | static { 24 | if (!ConfigHelper.skipSourcesFile.isEmpty()) { 25 | try(BufferedReader bufferedReader = Files.newBufferedReader(Paths.get(ConfigHelper.skipSourcesFile))) { 26 | String line; 27 | while ((line = bufferedReader.readLine()) != null) { 28 | String c; 29 | if (!(c = line.split("#")[0].trim()).isEmpty()) { 30 | skipList.add(line.trim()); 31 | } 32 | } 33 | } catch (IOException e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | } 38 | 39 | @Override 40 | public void discover(Map classMap, 41 | Map methodMap, 42 | InheritanceMap inheritanceMap, Map> graphCallMap) { 43 | 44 | final FastjsonSerializableDecider serializableDecider = new FastjsonSerializableDecider( 45 | methodMap); 46 | 47 | for (MethodReference.Handle method : methodMap.keySet()) { 48 | if (serializableDecider.apply(method.getClassReference())) { 49 | if (skipList.contains(method.getClassReference().getName())) { 50 | continue; 51 | } 52 | if (!checkFastjsonBlackList(method.getClassReference().getName().replace("/","."))) { 53 | continue; 54 | } 55 | if (method.getClassReference().getName().startsWith("javafx")) 56 | continue; 57 | if (method.getClassReference().getName().startsWith("javax")) 58 | continue; 59 | if (method.getName().startsWith("get") && method.getName().length() > 3 && method.getDesc().startsWith("()")) { 60 | if (method.getDesc().matches("\\(\\)Ljava/util/Collection;") || 61 | method.getDesc().matches("\\(\\)Ljava/util/Map;") || 62 | method.getDesc().matches("\\(\\)Ljava/util/concurrent/atomic/AtomicBoolean;") || 63 | method.getDesc().matches("\\(\\)Ljava/util/concurrent/atomic/AtomicInteger;") || 64 | method.getDesc().matches("\\(\\)Ljava/util/concurrent/atomic/AtomicInteger;") || 65 | method.getDesc().matches("\\(\\)Ljava/lang/Object;")) { 66 | String setterName = "set" + method.getName().charAt(3) + method.getName().substring(4); 67 | String desc = "(L" + method.getDesc().substring(method.getDesc().indexOf(")L") + 2, method.getDesc().length() - 1) + ";)V"; 68 | MethodReference.Handle handle = new MethodReference.Handle(method.getClassReference(), setterName, desc); 69 | if (!methodMap.containsKey(handle)) { 70 | addDiscoveredSource(new Source(method, 0)); 71 | } 72 | } 73 | } 74 | if (method.getName().startsWith("set") && method.getDesc().matches("\\(L[\\w/$]+?;\\)V")) { 75 | // if (method.getName().startsWith("set") && (method.getDesc().contains("(Ljava/lang/String;)V"))) { 76 | addDiscoveredSource(new Source(method, 1)); 77 | } 78 | } 79 | } 80 | } 81 | 82 | private boolean checkFastjsonBlackList(String className) { 83 | final long BASIC = 0xcbf29ce484222325L; 84 | final long PRIME = 0x100000001b3L; 85 | 86 | final long h1 = (BASIC ^ className.charAt(0)) * PRIME; 87 | if (h1 == 0xaf64164c86024f1aL) { // [ 88 | return false; 89 | } 90 | 91 | if ((h1 ^ className.charAt(className.length() - 1)) * PRIME == 0x9198507b5af98f0L) { 92 | return false; 93 | } 94 | 95 | final long h3 = (((((BASIC ^ className.charAt(0)) 96 | * PRIME) 97 | ^ className.charAt(1)) 98 | * PRIME) 99 | ^ className.charAt(2)) 100 | * PRIME; 101 | 102 | long hash = h3; 103 | for (int i = 3; i < className.length(); ++i) { 104 | hash ^= className.charAt(i); 105 | hash *= PRIME; 106 | if (Arrays.binarySearch(denyHashCodes, hash) >= 0) { 107 | return false; 108 | } 109 | } 110 | return true; 111 | } 112 | 113 | private long[] denyHashCodes; 114 | 115 | { 116 | denyHashCodes = new long[]{ 117 | 0x80D0C70BCC2FEA02L, 118 | 0x86FC2BF9BEAF7AEFL, 119 | 0x87F52A1B07EA33A6L, 120 | 0x8EADD40CB2A94443L, 121 | 0x8F75F9FA0DF03F80L, 122 | 0x9172A53F157930AFL, 123 | 0x92122D710E364FB8L, 124 | 0x92122D710E364FB8L, 125 | 0x94305C26580F73C5L, 126 | 0x9437792831DF7D3FL, 127 | 0xA123A62F93178B20L, 128 | 0xA85882CE1044C450L, 129 | 0xAA3DAFFDB10C4937L, 130 | 0xAFFF4C95B99A334DL, 131 | 0xB40F341C746EC94FL, 132 | 0xB7E8ED757F5D13A2L, 133 | 0xBCDD9DC12766F0CEL, 134 | 0xC00BE1DEBAF2808BL, 135 | 0xC2664D0958ECFE4CL, 136 | 0xC7599EBFE3E72406L, 137 | 0xC963695082FD728EL, 138 | 0xD1EFCDF4B3316D34L, 139 | 0xD9C9DBF6BBD27BB1L, 140 | 0xDF2DDFF310CDB375L, 141 | 0xE09AE4604842582FL, 142 | 0xE1919804D5BF468FL, 143 | 0xE2EB3AC7E56C467EL, 144 | 0xE603D6A51FAD692BL, 145 | 0xE9184BE55B1D962AL, 146 | 0xE9F20BAD25F60807L, 147 | 0xFC773AE20C827691L, 148 | 0xFD5BFC610056D720L, 149 | 0xFFDD1A80F1ED3405L, 150 | 0x10E067CD55C5E5L, 151 | 0x761619136CC13EL, 152 | 0x3085068CB7201B8L, 153 | 0x45B11BC78A3ABA3L, 154 | 0xB6E292FA5955ADEL, 155 | 0xEE6511B66FD5EF0L, 156 | 0x10B2BDCA849D9B3EL, 157 | 0x144277B467723158L, 158 | 0x14DB2E6FEAD04AF0L, 159 | 0x154B6CB22D294CFAL, 160 | 0x193B2697EAAED41AL, 161 | 0x1E0A8C3358FF3DAEL, 162 | 0x24D2F6048FEF4E49L, 163 | 0x275D0732B877AF29L, 164 | 0x2ADFEFBBFE29D931L, 165 | 0x2B3A37467A344CDFL, 166 | 0x2D308DBBC851B0D8L, 167 | 0x313BB4ABD8D4554CL, 168 | 0x332F0B5369A18310L, 169 | 0x339A3E0B6BEEBEE9L, 170 | 0x33C64B921F523F2FL, 171 | 0x34A81EE78429FDF1L, 172 | 0x3826F4B2380C8B9BL, 173 | 0x398F942E01920CF0L, 174 | 0x42D11A560FC9FBA9L, 175 | 0x43320DC9D2AE0892L, 176 | 0x440E89208F445FB9L, 177 | 0x46C808A4B5841F57L, 178 | 0x4A3797B30328202CL, 179 | 0x4BA3E254E758D70DL, 180 | 0x4EF08C90FF16C675L, 181 | 0x4FD10DDC6D13821FL, 182 | 0x527DB6B46CE3BCBCL, 183 | 0x5728504A6D454FFCL, 184 | 0x599B5C1213A099ACL, 185 | 0x5A5BD85C072E5EFEL, 186 | 0x5AB0CB3071AB40D1L, 187 | 0x5D74D3E5B9370476L, 188 | 0x5D92E6DDDE40ED84L, 189 | 0x62DB241274397C34L, 190 | 0x63A220E60A17C7B9L, 191 | 0x6749835432E0F0D2L, 192 | 0x746BD4A53EC195FBL, 193 | 0x74B50BB9260E31FFL, 194 | 0x75CC60F5871D0FD3L, 195 | 0x767A586A5107FEEFL, 196 | 0x7AA7EE3627A19CF3L 197 | }; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/Util.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | import java.nio.file.Paths; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.net.MalformedURLException; 11 | import java.net.URL; 12 | import java.net.URLClassLoader; 13 | import java.nio.file.FileVisitResult; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.nio.file.SimpleFileVisitor; 17 | import java.nio.file.attribute.BasicFileAttributes; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.jar.JarEntry; 21 | import java.util.jar.JarInputStream; 22 | 23 | public class Util { 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(Util.class); 26 | 27 | public static ClassLoader getWarClassLoader(Path warPath) throws IOException { 28 | //创建临时文件夹,在jvm shutdown自动删除 29 | final Path tmpDir = Files.createTempDirectory("exploded-war"); 30 | // Delete the temp directory at shutdown 31 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 32 | try { 33 | deleteDirectory(tmpDir); 34 | } catch (IOException e) { 35 | LOGGER.error("Error cleaning up temp directory " + tmpDir.toString(), e); 36 | } 37 | })); 38 | 39 | // Extract to war to the temp directory 40 | try (JarInputStream jarInputStream = new JarInputStream(Files.newInputStream(warPath))) { 41 | JarEntry jarEntry; 42 | while ((jarEntry = jarInputStream.getNextJarEntry()) != null) { 43 | Path fullPath = tmpDir.resolve(jarEntry.getName()); 44 | if (!jarEntry.isDirectory()) { 45 | Path dirName = fullPath.getParent(); 46 | if (dirName == null) { 47 | throw new IllegalStateException("Parent of item is outside temp directory."); 48 | } 49 | if (!Files.exists(dirName)) { 50 | Files.createDirectories(dirName); 51 | } 52 | try (OutputStream outputStream = Files.newOutputStream(fullPath)) { 53 | copy(jarInputStream, outputStream); 54 | } 55 | } 56 | } 57 | } 58 | 59 | final List classPathUrls = new ArrayList<>(); 60 | classPathUrls.add(tmpDir.resolve("WEB-INF/classes").toUri().toURL()); 61 | Files.list(tmpDir.resolve("WEB-INF/lib")).forEach(p -> { 62 | try { 63 | classPathUrls.add(p.toUri().toURL()); 64 | } catch (MalformedURLException e) { 65 | throw new RuntimeException(e); 66 | } 67 | }); 68 | URLClassLoader classLoader = new URLClassLoader(classPathUrls.toArray(new URL[classPathUrls.size()])); 69 | return classLoader; 70 | } 71 | 72 | public static ClassLoader getJarAndLibClassLoader(Path jarPath) throws IOException { 73 | //创建临时文件夹,在jvm shutdown自动删除 74 | final Path tmpDir = Files.createTempDirectory("exploded-jar"); 75 | // Delete the temp directory at shutdown 76 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 77 | try { 78 | deleteDirectory(tmpDir); 79 | } catch (IOException e) { 80 | LOGGER.error("Error cleaning up temp directory " + tmpDir.toString(), e); 81 | } 82 | })); 83 | 84 | // Extract to war to the temp directory 85 | try (JarInputStream jarInputStream = new JarInputStream(Files.newInputStream(jarPath))) { 86 | JarEntry jarEntry; 87 | while ((jarEntry = jarInputStream.getNextJarEntry()) != null) { 88 | Path fullPath = tmpDir.resolve(jarEntry.getName()); 89 | if (!jarEntry.isDirectory()) { 90 | Path dirName = fullPath.getParent(); 91 | if (dirName == null) { 92 | throw new IllegalStateException("Parent of item is outside temp directory."); 93 | } 94 | if (!Files.exists(dirName)) { 95 | Files.createDirectories(dirName); 96 | } 97 | try (OutputStream outputStream = Files.newOutputStream(fullPath)) { 98 | copy(jarInputStream, outputStream); 99 | } 100 | } 101 | } 102 | } 103 | 104 | final List classPathUrls = new ArrayList<>(); 105 | classPathUrls.add(tmpDir.resolve("BOOT-INF/classes").toUri().toURL()); 106 | Files.list(tmpDir.resolve("BOOT-INF/lib")).forEach(p -> { 107 | try { 108 | classPathUrls.add(p.toUri().toURL()); 109 | } catch (MalformedURLException e) { 110 | throw new RuntimeException(e); 111 | } 112 | }); 113 | URLClassLoader classLoader = new URLClassLoader(classPathUrls.toArray(new URL[classPathUrls.size()])); 114 | return classLoader; 115 | } 116 | 117 | public static ClassLoader getJarClassLoader(Path ... jarPaths) throws IOException { 118 | final List classPathUrls = new ArrayList<>(jarPaths.length); 119 | for (Path jarPath : jarPaths) { 120 | if (!Files.exists(jarPath) || Files.isDirectory(jarPath)) { 121 | throw new IllegalArgumentException("Path \"" + jarPath + "\" is not a path to a file."); 122 | } 123 | classPathUrls.add(jarPath.toUri().toURL()); 124 | } 125 | URLClassLoader classLoader = new URLClassLoader(classPathUrls.toArray(new URL[classPathUrls.size()])); 126 | return classLoader; 127 | } 128 | 129 | /** 130 | * Recursively delete the directory root and all its contents 131 | * @param root Root directory to be deleted 132 | */ 133 | public static void deleteDirectory(Path root) throws IOException { 134 | Files.walkFileTree(root, new SimpleFileVisitor() { 135 | @Override 136 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 137 | Files.delete(file); 138 | return FileVisitResult.CONTINUE; 139 | } 140 | 141 | @Override 142 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 143 | Files.delete(dir); 144 | return FileVisitResult.CONTINUE; 145 | } 146 | }); 147 | } 148 | 149 | /** 150 | * Copy inputStream to outputStream. Neither stream is closed by this method. 151 | */ 152 | public static void copy(InputStream inputStream, OutputStream outputStream) throws IOException { 153 | final byte[] buffer = new byte[4096]; 154 | int n; 155 | while ((n = inputStream.read(buffer)) > 0) { 156 | outputStream.write(buffer, 0, n); 157 | } 158 | } 159 | 160 | public static void main(String[] args) throws IOException { 161 | //创建临时文件夹,在jvm shutdown自动删除 162 | final Path tmpDir = Files.createTempDirectory("exploded-war"); 163 | // Delete the temp directory at shutdown 164 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 165 | try { 166 | deleteDirectory(tmpDir); 167 | } catch (IOException e) { 168 | LOGGER.error("Error cleaning up temp directory " + tmpDir.toString(), e); 169 | } 170 | })); 171 | try (JarInputStream jarInputStream = new JarInputStream(Files.newInputStream(Paths.get( 172 | "/Users/xuanyonghao/dev-project/learn-jdbc/build/libs/learn-jdbc-1.0-SNAPSHOT.jar")))) { 173 | JarEntry jarEntry; 174 | while ((jarEntry = jarInputStream.getNextJarEntry()) != null) { 175 | if (!jarEntry.isDirectory()) { 176 | System.out.println(jarEntry.getName()); 177 | } 178 | } 179 | } catch (IOException e) { 180 | e.printStackTrace(); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/MethodDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | import gadgetinspector.data.ClassReference; 4 | import gadgetinspector.data.DataLoader; 5 | import gadgetinspector.data.InheritanceDeriver; 6 | import gadgetinspector.data.MethodReference; 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | import org.objectweb.asm.*; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.nio.file.Paths; 16 | import java.sql.SQLException; 17 | import java.util.ArrayList; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | public class MethodDiscovery { 23 | 24 | private static final Logger LOGGER = LoggerFactory.getLogger(MethodDiscovery.class); 25 | 26 | private final List discoveredClasses = new ArrayList<>(); 27 | private final List discoveredMethods = new ArrayList<>(); 28 | 29 | public void save() throws IOException { 30 | //保存和读取使用Factory实现 31 | 32 | //classes.dat数据格式: 33 | //类名(例:java/lang/String) 父类 接口A,接口B,接口C 是否接口 字段1!字段1access!字段1类型!字段2!字段2access!字段1类型 34 | DataLoader.saveData(Paths.get("classes.dat"), new ClassReference.Factory(), discoveredClasses); 35 | 36 | //methods.dat数据格式: 37 | //类名 方法名 方法描述 是否静态方法 38 | DataLoader.saveData(Paths.get("methods.dat"), new MethodReference.Factory(), discoveredMethods); 39 | 40 | //形成 类名(ClassReference.Handle)->类(ClassReference) 的映射关系 41 | Map classMap = new HashMap<>(); 42 | for (ClassReference clazz : discoveredClasses) { 43 | classMap.put(clazz.getHandle(), clazz); 44 | } 45 | //保存classes.dat和methods.dat的同时,对所有的class进行递归整合,得到集合{class:[subclass]}, 46 | // class为subclass父类、超类或实现的接口类,保存至inheritanceMap.dat 47 | InheritanceDeriver.derive(classMap).save(); 48 | } 49 | 50 | public void discover(final ClassResourceEnumerator classResourceEnumerator) throws Exception { 51 | for (ClassResourceEnumerator.ClassResource classResource : classResourceEnumerator.getAllClasses()) { 52 | try (InputStream in = classResource.getInputStream()) { 53 | ClassReader cr = new ClassReader(in); 54 | try { 55 | //使用asm的ClassVisitor、MethodVisitor,利用观察模式去扫描所有的class和method并记录 56 | cr.accept(new MethodDiscoveryClassVisitor(), ClassReader.EXPAND_FRAMES); 57 | } catch (Exception e) { 58 | LOGGER.error("Exception analyzing: " + classResource.getName(), e); 59 | } 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | } 65 | class MethodAnnotationScanner extends MethodVisitor{ 66 | private ClassReference.Handle myClassHandle; 67 | private String name; 68 | private String desc; 69 | private boolean isStatic; 70 | private String parameterAnnotationDesc = ""; 71 | private String methodAnnotationDesc = ""; 72 | 73 | public MethodAnnotationScanner() { 74 | super(Opcodes.ASM6); 75 | } 76 | 77 | public MethodAnnotationScanner(ClassReference.Handle myClassHandle, String name, String desc, boolean isStatic) { 78 | super(Opcodes.ASM6); 79 | this.myClassHandle = myClassHandle; 80 | this.name = name; 81 | this.desc = desc; 82 | this.isStatic = isStatic; 83 | } 84 | 85 | @Override 86 | public AnnotationVisitor visitParameterAnnotation(final int parameter, final String descriptor, final boolean visible) { 87 | if (mv != null) { 88 | return mv.visitParameterAnnotation(parameter, descriptor, visible); 89 | } 90 | this.parameterAnnotationDesc += descriptor; 91 | return null; 92 | } 93 | 94 | @Override 95 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 96 | this.methodAnnotationDesc += desc; 97 | return super.visitAnnotation(desc, visible); 98 | } 99 | 100 | @Override 101 | public void visitEnd() { 102 | if (mv != null) { 103 | mv.visitEnd(); 104 | } 105 | this.methodAnnotationDesc = this.methodAnnotationDesc.length()==0?"none":this.methodAnnotationDesc; 106 | this.parameterAnnotationDesc = this.parameterAnnotationDesc.length()==0?"none":this.parameterAnnotationDesc; 107 | discoveredMethods.add(new MethodReference( 108 | this.myClassHandle,//类名 109 | this.name, 110 | this.desc, 111 | this.isStatic, 112 | this.methodAnnotationDesc, 113 | this.parameterAnnotationDesc)); 114 | } 115 | } 116 | 117 | private class MethodDiscoveryClassVisitor extends ClassVisitor { 118 | 119 | private String name; 120 | private String superName; 121 | private String[] interfaces; 122 | boolean isInterface; 123 | private List members;//类的所有字段 124 | private ClassReference.Handle classHandle; 125 | private Set annotations; 126 | 127 | private MethodDiscoveryClassVisitor() throws SQLException { 128 | super(Opcodes.ASM6); 129 | } 130 | 131 | @Override 132 | public void visit ( int version, int access, String name, String signature, String superName, String[]interfaces) 133 | { 134 | this.name = name; 135 | this.superName = superName; 136 | this.interfaces = interfaces; 137 | this.isInterface = (access & Opcodes.ACC_INTERFACE) != 0; 138 | this.members = new ArrayList<>(); 139 | this.classHandle = new ClassReference.Handle(name);//类名 140 | annotations = new HashSet<>(); 141 | super.visit(version, access, name, signature, superName, interfaces); 142 | } 143 | 144 | @Override 145 | public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { 146 | annotations.add(descriptor); 147 | return super.visitAnnotation(descriptor, visible); 148 | } 149 | 150 | public FieldVisitor visitField(int access, String name, String desc, 151 | String signature, Object value) { 152 | if ((access & Opcodes.ACC_STATIC) == 0) { 153 | Type type = Type.getType(desc); 154 | String typeName; 155 | if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { 156 | typeName = type.getInternalName(); 157 | } else { 158 | typeName = type.getDescriptor(); 159 | } 160 | members.add(new ClassReference.Member(name, access, new ClassReference.Handle(typeName))); 161 | } 162 | return super.visitField(access, name, desc, signature, value); 163 | } 164 | 165 | @Override 166 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 167 | boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; 168 | 169 | //return super.visitMethod(access, name, desc, signature, exceptions); 170 | return new MethodAnnotationScanner(classHandle, name, desc, isStatic); 171 | } 172 | 173 | @Override 174 | public void visitEnd() { 175 | ClassReference classReference = new ClassReference( 176 | name, 177 | superName, 178 | interfaces, 179 | isInterface, 180 | members.toArray(new ClassReference.Member[members.size()]), 181 | annotations);//把所有找到的字段封装 182 | //找到一个方法遍历完成后,添加类到缓存 183 | discoveredClasses.add(classReference); 184 | 185 | super.visitEnd(); 186 | } 187 | 188 | } 189 | 190 | public static void main(String[] args) throws Exception { 191 | ClassLoader classLoader = Util.getWarClassLoader(Paths.get(args[0])); 192 | 193 | MethodDiscovery methodDiscovery = new MethodDiscovery(); 194 | methodDiscovery.discover(new ClassResourceEnumerator(classLoader)); 195 | methodDiscovery.save(); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/MavenCrawerPopular.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.io.OutputStream; 8 | import java.io.OutputStreamWriter; 9 | import java.io.Writer; 10 | import java.nio.charset.StandardCharsets; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | import java.nio.file.StandardOpenOption; 15 | import java.security.cert.CertificateException; 16 | import java.security.cert.X509Certificate; 17 | import java.util.HashSet; 18 | import java.util.Queue; 19 | import java.util.Scanner; 20 | import java.util.Set; 21 | import java.util.concurrent.ExecutorService; 22 | import java.util.concurrent.Executors; 23 | import java.util.concurrent.LinkedBlockingDeque; 24 | import javax.net.ssl.SSLContext; 25 | import org.apache.http.HttpHost; 26 | import org.apache.http.client.config.RequestConfig; 27 | import org.apache.http.client.methods.CloseableHttpResponse; 28 | import org.apache.http.client.methods.HttpGet; 29 | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 30 | import org.apache.http.impl.client.CloseableHttpClient; 31 | import org.apache.http.impl.client.HttpClientBuilder; 32 | import org.apache.http.impl.client.HttpClients; 33 | import org.apache.http.ssl.SSLContextBuilder; 34 | import org.apache.http.ssl.TrustStrategy; 35 | import org.jsoup.Jsoup; 36 | import org.jsoup.nodes.Document; 37 | import org.jsoup.nodes.Element; 38 | import org.jsoup.select.Elements; 39 | import org.slf4j.Logger; 40 | import org.slf4j.LoggerFactory; 41 | 42 | /** 43 | * @author threedr3am 44 | */ 45 | public class MavenCrawerPopular { 46 | private static final Logger LOGGER = LoggerFactory.getLogger(MavenCrawerPopular.class); 47 | 48 | private static SSLConnectionSocketFactory sslsf; 49 | private static HttpClientBuilder httpClientBuilder; 50 | static { 51 | try { 52 | SSLContext sslContext = new SSLContextBuilder() 53 | .loadTrustMaterial(null, new TrustStrategy() { 54 | // 信任所有 55 | public boolean isTrusted(X509Certificate[] chain, 56 | String authType) 57 | throws CertificateException { 58 | return true; 59 | } 60 | }).build(); 61 | sslsf = new SSLConnectionSocketFactory( 62 | sslContext); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | httpClientBuilder = HttpClients 67 | .custom() 68 | .setProxy(new HttpHost("127.0.0.1", 7890)) 69 | .disableRedirectHandling() 70 | .disableCookieManagement() 71 | ; 72 | httpClientBuilder.setSSLSocketFactory(sslsf); 73 | } 74 | 75 | public static void start() { 76 | crawBusExecutor.execute(MavenCrawerPopular::crawJar); 77 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 78 | downloadExecutor.shutdown(); 79 | crawBusExecutor.shutdown(); 80 | LOGGER.info("程序关闭,shutdown all thread!"); 81 | })); 82 | } 83 | 84 | /** 85 | * 从maven爬取jar包 86 | */ 87 | private static void crawJar() { 88 | int count = 0; 89 | Queue newScanJarHistoryAppend = new LinkedBlockingDeque<>(); 90 | Set scanJarHistory = new HashSet<>(); 91 | Path filePath = Paths.get("history/popular-craw-history.dat"); 92 | //读爬取历史jar 93 | try { 94 | if (ConfigHelper.onlyCrawMavenPopular && Files 95 | .exists(filePath)) { 96 | try (InputStream inputStream = Files 97 | .newInputStream(filePath); 98 | Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name())) { 99 | while (scanner.hasNext()) { 100 | String jar = scanner.nextLine(); 101 | String name = null; 102 | int index = jar.indexOf("."); 103 | if (index != -1) { 104 | name = jar.substring(0, index); 105 | index = name.lastIndexOf("-"); 106 | if (index != -1) { 107 | name = name.substring(0, index); 108 | } 109 | } 110 | if (name != null) { 111 | scanJarHistory.add(name); 112 | } 113 | if (jar.length() > 0) { 114 | scanJarHistory.add(jar.trim()); 115 | } 116 | } 117 | } 118 | } 119 | } catch (Exception e) { 120 | e.printStackTrace(); 121 | } 122 | 123 | //爬虫根线程 124 | crawBusExecutor.execute(() -> { 125 | craw(newScanJarHistoryAppend, scanJarHistory); 126 | }); 127 | 128 | //写爬取历史jar 129 | try { 130 | if (!Files.exists(filePath)) 131 | Files.createFile(filePath); 132 | try (OutputStream outputStream = Files.newOutputStream(filePath, StandardOpenOption.APPEND); 133 | Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { 134 | while (true) { 135 | String jar = newScanJarHistoryAppend.poll(); 136 | if (jar != null && jar.length() > 0) { 137 | writer.write(jar); 138 | writer.write("\n"); 139 | writer.flush(); 140 | if (++count % 10 == 0) { 141 | LOGGER.info("-------------------this task already craw " + count 142 | + " jars------------------"); 143 | } 144 | } 145 | } 146 | } 147 | } catch (Exception e) { 148 | e.printStackTrace(); 149 | } 150 | } 151 | 152 | //爬虫根线程池(只爬level=0的根) 153 | private static ExecutorService crawBusExecutor = Executors.newFixedThreadPool(2); 154 | //下载线程池 155 | private static ExecutorService downloadExecutor = Executors.newFixedThreadPool(8); 156 | 157 | /** 158 | * 核心爬取逻辑 159 | * 160 | * @param newScanJarHistoryAppend 161 | * @param scanJarHistory 162 | */ 163 | private static void craw(Queue newScanJarHistoryAppend, Set scanJarHistory) { 164 | String root = "https://mvnrepository.com/popular"; 165 | for (int i = 1; i <= 10; i++) { 166 | String url = root + "?p=" + i; 167 | LOGGER.info("craw " + url + " ... "); 168 | try { 169 | String content = crawHtmlContent(url); 170 | if (content == null || content.length() <= 0) 171 | continue; 172 | Document doc = Jsoup.parse(content); 173 | if (doc == null) 174 | continue; 175 | Elements imTitles = doc.getElementsByClass("im-title"); 176 | if (imTitles == null || imTitles.size() <= 0) 177 | continue; 178 | for (Element imTitle : imTitles) { 179 | Elements hrefs = imTitle.select("a[href]"); 180 | if (hrefs.size() <= 0) 181 | continue; 182 | String href = hrefs.first().attr("href"); 183 | if (!href.startsWith("/") || href.endsWith("/")) 184 | continue; 185 | 186 | content = crawHtmlContent("https://mvnrepository.com" + href); 187 | if (content == null || content.length() <= 0) 188 | continue; 189 | doc = Jsoup.parse(content); 190 | if (doc == null) 191 | continue; 192 | Elements vbtns = doc.getElementsByClass("vbtn"); 193 | if (vbtns == null || vbtns.size() <= 0) 194 | continue; 195 | Element vbtn = vbtns.first(); 196 | if (vbtn == null) 197 | continue; 198 | String href2 = vbtn.attr("href"); 199 | if (href2.startsWith("/") || href.endsWith("/")) 200 | continue; 201 | href2 = href2.substring(href2.indexOf("/")); 202 | 203 | content = crawHtmlContent("https://mvnrepository.com" + href + href2); 204 | if (content == null || content.length() <= 0) 205 | continue; 206 | doc = Jsoup.parse(content); 207 | if (doc == null) 208 | continue; 209 | vbtns = doc.getElementsByClass("vbtn"); 210 | if (vbtns == null || vbtns.size() <= 0) 211 | continue; 212 | for (Element element : vbtns) { 213 | href = element.attr("href"); 214 | if (href != null && href.length() > 0 && href.endsWith(".jar")) { 215 | String jar = href.substring(href.lastIndexOf("/") + 1); 216 | String name = null; 217 | int index = jar.indexOf("."); 218 | if (index != -1) { 219 | name = jar.substring(0, index); 220 | index = name.lastIndexOf("-"); 221 | if (index != -1) { 222 | name = name.substring(0, index); 223 | } 224 | } 225 | if (!jar.contains("/") && !jar.contains("javadoc") && !jar.contains("sources") && !jar.contains("test")) { 226 | if (name != null && !scanJarHistory.contains(name) && !scanJarHistory.contains(jar)) { 227 | scanJarHistory.add(name); 228 | String finalHref = href; 229 | downloadExecutor.execute(() -> { 230 | if (download(finalHref, jar)) { 231 | newScanJarHistoryAppend 232 | .offer(jar); 233 | } 234 | }); 235 | } 236 | break; 237 | } 238 | } 239 | } 240 | } 241 | } catch (Exception e) { 242 | e.printStackTrace(); 243 | } 244 | } 245 | } 246 | 247 | /** 248 | * 爬取html内容 249 | * 250 | * @param url 251 | * @return 252 | */ 253 | private static String crawHtmlContent(String url) { 254 | LOGGER.info("craw html content:" + url + " ... "); 255 | RequestConfig timeoutConfig = RequestConfig.custom() 256 | .setConnectTimeout(60000).setConnectionRequestTimeout(60000) 257 | .setSocketTimeout(60000).build(); 258 | HttpGet httpGet = new HttpGet(url); 259 | httpGet.setConfig(timeoutConfig); 260 | try { 261 | CloseableHttpClient httpClient = null; 262 | CloseableHttpResponse response = null; 263 | try { 264 | httpClient = httpClientBuilder.build(); 265 | response = httpClient.execute(httpGet); 266 | int status = response.getStatusLine().getStatusCode(); 267 | if (status == 200) { 268 | StringBuilder stringBuilder = new StringBuilder(1024); 269 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); 270 | String line; 271 | while ((line = bufferedReader.readLine()) != null) 272 | stringBuilder.append(line); 273 | return stringBuilder.toString(); 274 | } else { 275 | LOGGER.error("craw html content url:" + url + " fail!"); 276 | } 277 | } finally { 278 | response.close(); 279 | httpClient.close(); 280 | httpGet.releaseConnection(); 281 | } 282 | } catch (Exception e) { 283 | e.printStackTrace(); 284 | } 285 | return null; 286 | } 287 | 288 | /** 289 | * 下载jar包 290 | * 291 | * @param href 292 | * @param file 293 | */ 294 | private static boolean download(String href, String file) { 295 | LOGGER.info("download jar:" + href + " ... "); 296 | String dir = ConfigHelper.crawMavenJarPath; 297 | RequestConfig timeoutConfig = RequestConfig.custom() 298 | .setConnectTimeout(60000).setConnectionRequestTimeout(60000) 299 | .setSocketTimeout(60000).build(); 300 | HttpGet httpGet = new HttpGet(href); 301 | httpGet.setConfig(timeoutConfig); 302 | try { 303 | CloseableHttpClient httpClient = null; 304 | CloseableHttpResponse response = null; 305 | try { 306 | httpClient = httpClientBuilder.build(); 307 | response = httpClient.execute(httpGet); 308 | int status = response.getStatusLine().getStatusCode(); 309 | if (status == 200) { 310 | Path path = Paths.get(dir + file); 311 | if (!Files.exists(path)) { 312 | Files.copy(response.getEntity().getContent(), path); 313 | } 314 | } else { 315 | LOGGER.error("download jar:" + href + " fail!"); 316 | return false; 317 | } 318 | } finally { 319 | response.close(); 320 | httpClient.close(); 321 | httpGet.releaseConnection(); 322 | } 323 | } catch (Exception e) { 324 | e.printStackTrace(); 325 | return false; 326 | } 327 | return true; 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/MavenCrawer.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.io.OutputStream; 8 | import java.io.OutputStreamWriter; 9 | import java.io.Writer; 10 | import java.nio.charset.StandardCharsets; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | import java.nio.file.StandardOpenOption; 15 | import java.security.cert.CertificateException; 16 | import java.security.cert.X509Certificate; 17 | import java.util.HashSet; 18 | import java.util.Queue; 19 | import java.util.Scanner; 20 | import java.util.Set; 21 | import java.util.concurrent.ExecutorService; 22 | import java.util.concurrent.Executors; 23 | import java.util.concurrent.LinkedBlockingDeque; 24 | import javax.net.ssl.SSLContext; 25 | import org.apache.http.HttpHost; 26 | import org.apache.http.client.config.RequestConfig; 27 | import org.apache.http.client.methods.CloseableHttpResponse; 28 | import org.apache.http.client.methods.HttpGet; 29 | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 30 | import org.apache.http.impl.client.CloseableHttpClient; 31 | import org.apache.http.impl.client.HttpClientBuilder; 32 | import org.apache.http.impl.client.HttpClients; 33 | import org.apache.http.ssl.SSLContextBuilder; 34 | import org.apache.http.ssl.TrustStrategy; 35 | import org.jsoup.Jsoup; 36 | import org.jsoup.nodes.Document; 37 | import org.jsoup.nodes.Element; 38 | import org.jsoup.select.Elements; 39 | import org.slf4j.Logger; 40 | import org.slf4j.LoggerFactory; 41 | 42 | /** 43 | * @author threedr3am 44 | */ 45 | public class MavenCrawer { 46 | private static final Logger LOGGER = LoggerFactory.getLogger(MavenCrawer.class); 47 | 48 | private static SSLConnectionSocketFactory sslsf; 49 | private static HttpClientBuilder httpClientBuilder; 50 | static { 51 | try { 52 | SSLContext sslContext = new SSLContextBuilder() 53 | .loadTrustMaterial(null, new TrustStrategy() { 54 | // 信任所有 55 | public boolean isTrusted(X509Certificate[] chain, 56 | String authType) 57 | throws CertificateException { 58 | return true; 59 | } 60 | }).build(); 61 | sslsf = new SSLConnectionSocketFactory( 62 | sslContext); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | httpClientBuilder = HttpClients 67 | .custom() 68 | .setProxy(new HttpHost("127.0.0.1", 7890)) 69 | .disableRedirectHandling() 70 | .disableCookieManagement() 71 | ; 72 | httpClientBuilder.setSSLSocketFactory(sslsf); 73 | } 74 | 75 | public static void start() { 76 | crawBusExecutor.execute(MavenCrawer::crawJar); 77 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 78 | downloadExecutor.shutdown(); 79 | crawWorkerExecutor.shutdown(); 80 | crawBusExecutor.shutdown(); 81 | LOGGER.info("程序关闭,shutdown all thread!"); 82 | })); 83 | } 84 | 85 | /** 86 | * 从maven爬取jar包 87 | */ 88 | private static void crawJar() { 89 | int count = 0; 90 | Queue newScanJarHistoryAppend = new LinkedBlockingDeque<>(); 91 | Set scanJarHistory = new HashSet<>(); 92 | Path filePath = Paths.get("history/craw-history.dat"); 93 | //读爬取历史jar 94 | try { 95 | if (ConfigHelper.crawMaven && Files 96 | .exists(filePath)) { 97 | try (InputStream inputStream = Files 98 | .newInputStream(filePath); 99 | Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name())) { 100 | while (scanner.hasNext()) { 101 | String jar = scanner.nextLine(); 102 | String name = null; 103 | int index = jar.indexOf("."); 104 | if (index != -1) { 105 | name = jar.substring(0, index); 106 | index = name.lastIndexOf("-"); 107 | if (index != -1) { 108 | name = name.substring(0, index); 109 | } 110 | } 111 | if (name != null) { 112 | scanJarHistory.add(name); 113 | } 114 | if (jar.length() > 0) { 115 | scanJarHistory.add(jar.trim()); 116 | } 117 | } 118 | } 119 | } 120 | } catch (Exception e) { 121 | e.printStackTrace(); 122 | } 123 | 124 | //爬虫根线程 125 | crawBusExecutor.execute(() -> { 126 | String parentUrl = "https://repo1.maven.org/maven2/org/springframework/"; 127 | craw(newScanJarHistoryAppend, scanJarHistory, parentUrl, 0); 128 | }); 129 | 130 | //写爬取历史jar 131 | try { 132 | if (!Files.exists(filePath)) 133 | Files.createFile(filePath); 134 | try (OutputStream outputStream = Files.newOutputStream(filePath, StandardOpenOption.APPEND); 135 | Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { 136 | while (true) { 137 | String jar = newScanJarHistoryAppend.poll(); 138 | if (jar != null && jar.length() > 0) { 139 | writer.write(jar); 140 | writer.write("\n"); 141 | writer.flush(); 142 | if (++count % 10 == 0) { 143 | LOGGER.info("-------------------this task already craw " + count 144 | + " jars------------------"); 145 | } 146 | } 147 | } 148 | } 149 | } catch (Exception e) { 150 | e.printStackTrace(); 151 | } 152 | } 153 | 154 | //爬虫根线程池(只爬level=0的根) 155 | private static ExecutorService crawBusExecutor = Executors.newFixedThreadPool(8); 156 | //下载线程池 157 | private static ExecutorService downloadExecutor = Executors.newFixedThreadPool(8); 158 | //爬虫线程池 159 | private static ExecutorService crawWorkerExecutor = Executors.newFixedThreadPool(8); 160 | 161 | /** 162 | * 核心爬取逻辑 163 | * 164 | * @param newScanJarHistoryAppend 165 | * @param scanJarHistory 166 | * @param parentUrl 167 | * @param level 168 | */ 169 | private static void craw(Queue newScanJarHistoryAppend, Set scanJarHistory, 170 | String parentUrl, int level) { 171 | try { 172 | //检测该分支是否已爬取过 173 | if (checkDir(parentUrl)) 174 | return; 175 | String content = crawHtmlContent(parentUrl); 176 | if (content != null || content.length() >= 0) { 177 | Document doc = Jsoup.parse(content); 178 | if (doc != null) { 179 | Elements contents = doc.select("pre"); 180 | if (contents != null || contents.size() >= 0) { 181 | Elements as = contents.first().select("a"); 182 | for (int i = as.size() - 1; i > 0; i--) { 183 | Element a = as.get(i); 184 | String href = a.attr("href"); 185 | if (href.equals("../")) { 186 | continue; 187 | } 188 | if (href.endsWith(".jar")) { 189 | String jar = href; 190 | String name = null; 191 | int index = jar.indexOf("."); 192 | if (index != -1) { 193 | name = jar.substring(0, index); 194 | index = name.lastIndexOf("-"); 195 | if (index != -1) { 196 | name = name.substring(0, index); 197 | } 198 | } 199 | if (!jar.contains("/") && !jar.contains("javadoc") && !jar 200 | .contains("sources") && !jar.contains("test")) { 201 | if (name != null && !scanJarHistory.contains(name) && !scanJarHistory.contains(jar)) { 202 | scanJarHistory.add(name); 203 | downloadExecutor.execute(() -> { 204 | if (download(parentUrl, href)) { 205 | newScanJarHistoryAppend 206 | .offer(jar); 207 | } 208 | }); 209 | continue; 210 | } 211 | } 212 | } else if (href.endsWith("/")) { 213 | if (level == 0) { 214 | //level=1的节点,都用多worker线程处理,每个线程处理一个1级分支 215 | crawWorkerExecutor.execute(() -> { 216 | craw(newScanJarHistoryAppend, scanJarHistory, parentUrl + href, 217 | level + 1); 218 | }); 219 | } else { 220 | //非根非1级节点,都继续用当前线程递归,直到jar包为止 221 | craw(newScanJarHistoryAppend, scanJarHistory, parentUrl + href, 222 | level + 1); 223 | } 224 | } 225 | } 226 | } 227 | } 228 | } 229 | //记录符合层级的分支目录,下次启动程序时,不用爬取也可以快速跳过 230 | over(parentUrl, level); 231 | } catch (Exception e) { 232 | e.printStackTrace(); 233 | } 234 | } 235 | 236 | /** 237 | * 已被爬取过的目录历史 238 | */ 239 | private static Set mavenDirHistory; 240 | 241 | private static void over(String parentUrl, int level) { 242 | String saveDir = parentUrl.replace("https://repo1.maven.org/maven2", ""); 243 | String[] tmp = saveDir.split("/"); 244 | if (tmp == null || tmp.length == 2 ? level != 2 : level != 1) { 245 | return; 246 | } 247 | if (ConfigHelper.crawMaven) { 248 | if (!mavenDirHistory.contains(saveDir)) { 249 | mavenDirHistory.add(saveDir); 250 | try { 251 | Path filePath = Paths.get("history/craw-dir-history.dat"); 252 | if (!Files.exists(filePath)) 253 | Files.createFile(filePath); 254 | try (OutputStream outputStream = Files.newOutputStream(filePath, StandardOpenOption.APPEND); 255 | Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { 256 | writer.write(saveDir); 257 | writer.write("\n"); 258 | writer.flush(); 259 | } 260 | } catch (Exception e) { 261 | e.printStackTrace(); 262 | } 263 | } 264 | } 265 | return; 266 | } 267 | 268 | private static boolean checkDir(String parentUrl) { 269 | String checkDir = parentUrl.replace("https://repo1.maven.org/maven2", ""); 270 | if (mavenDirHistory == null) { 271 | mavenDirHistory = new HashSet<>(); 272 | Path filePath = Paths.get("history/craw-dir-history.dat"); 273 | if (Files.exists(filePath)) { 274 | try (InputStream inputStream = Files 275 | .newInputStream(filePath); 276 | Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name())) { 277 | while (scanner.hasNext()) { 278 | String dir = scanner.nextLine(); 279 | if (dir.length() > 0) { 280 | mavenDirHistory.add(dir.trim()); 281 | } 282 | } 283 | } catch (IOException e) { 284 | e.printStackTrace(); 285 | } 286 | } 287 | } 288 | return mavenDirHistory.contains(checkDir); 289 | } 290 | 291 | /** 292 | * 爬取html内容 293 | * 294 | * @param url 295 | * @return 296 | */ 297 | private static String crawHtmlContent(String url) { 298 | LOGGER.info("craw html content:" + url + " ... "); 299 | RequestConfig timeoutConfig = RequestConfig.custom() 300 | .setConnectTimeout(60000).setConnectionRequestTimeout(60000) 301 | .setSocketTimeout(60000).build(); 302 | HttpGet httpGet = new HttpGet(url); 303 | httpGet.setConfig(timeoutConfig); 304 | try { 305 | CloseableHttpClient httpClient = null; 306 | CloseableHttpResponse response = null; 307 | try { 308 | httpClient = httpClientBuilder.build(); 309 | response = httpClient.execute(httpGet); 310 | int status = response.getStatusLine().getStatusCode(); 311 | if (status == 200) { 312 | StringBuilder stringBuilder = new StringBuilder(1024); 313 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); 314 | String line; 315 | while ((line = bufferedReader.readLine()) != null) 316 | stringBuilder.append(line); 317 | return stringBuilder.toString(); 318 | } else { 319 | LOGGER.error("craw html content url:" + url + " fail!"); 320 | } 321 | } finally { 322 | response.close(); 323 | httpClient.close(); 324 | httpGet.releaseConnection(); 325 | } 326 | } catch (Exception e) { 327 | e.printStackTrace(); 328 | } 329 | return null; 330 | } 331 | 332 | /** 333 | * 下载jar包 334 | * 335 | * @param href 336 | * @param file 337 | */ 338 | private static boolean download(String href, String file) { 339 | LOGGER.info("download jar:" + href + file + " ... "); 340 | String dir = ConfigHelper.crawMavenJarPath; 341 | RequestConfig timeoutConfig = RequestConfig.custom() 342 | .setConnectTimeout(60000).setConnectionRequestTimeout(60000) 343 | .setSocketTimeout(60000).build(); 344 | HttpGet httpGet = new HttpGet(href + file); 345 | httpGet.setConfig(timeoutConfig); 346 | try { 347 | CloseableHttpClient httpClient = null; 348 | CloseableHttpResponse response = null; 349 | try { 350 | httpClient = httpClientBuilder.build(); 351 | response = httpClient.execute(httpGet); 352 | int status = response.getStatusLine().getStatusCode(); 353 | if (status == 200) { 354 | Path path = Paths.get(dir + file); 355 | if (!Files.exists(path)) { 356 | Files.copy(response.getEntity().getContent(), path); 357 | } 358 | } else { 359 | LOGGER.error("download jar:" + href + file + " fail!"); 360 | return false; 361 | } 362 | } finally { 363 | response.close(); 364 | httpClient.close(); 365 | httpGet.releaseConnection(); 366 | } 367 | } catch (Exception e) { 368 | e.printStackTrace(); 369 | return false; 370 | } 371 | return true; 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 修复了一些bug,优化并增加了一些功能,如slink点、利用链、识别函数注解、参数注解等等。并增加了web项目扫描策略,source点为路由入口,使用命令: 3 | ``` 4 | --config 5 | webservice 6 | --noTaintTrack 7 | --maxChainLength 8 | 16 9 | --similarLevel 10 | 4 11 | --maxRepeatBranchesTimes 12 | 10 13 | --skipSourcesFile 14 | /myGadgetinspector/webservice-skip-sources.demo 15 | /temp/halo.jar 16 | ``` 17 | 参数介绍: 18 | 19 | --similarLevel n:解决路径爆炸,即中间路径重复率过高的问题,对每条链,会取它的前n条链和最后1条链作为去重因子,如有重复,则取最短链 20 | 21 | --maxRepeatBranchesTimes n:表示某个分支函数最多可以出现在所有利用链中几次,默认20 22 | 23 | 文件说明: 24 | webservice-skip-sources.demo为需要忽略的路由类 25 | 26 | 如halo开源项目扫描结果如下(下面利用链有目录遍历漏洞,已报给厂商): 27 | ``` 28 | Using classpath: [/temp/halo.jar] 29 | run/halo/app/controller/admin/api/BackupController.getMarkdownBackup(Ljava/lang/String;)Lrun/halo/app/model/dto/BackupDTO; (0) 30 | java/nio/file/Paths.get(Ljava/lang/String;[Ljava/lang/String;)Ljava/nio/file/Path; (0) 31 | 32 | run/halo/app/controller/admin/api/BackupController.getDataBackup(Ljava/lang/String;)Lrun/halo/app/model/dto/BackupDTO; (0) 33 | java/nio/file/Paths.get(Ljava/lang/String;[Ljava/lang/String;)Ljava/nio/file/Path; (0) 34 | 35 | run/halo/app/controller/admin/api/BackupController.getWorkDirBackup(Ljava/lang/String;)Lrun/halo/app/model/dto/BackupDTO; (0) 36 | java/nio/file/Paths.get(Ljava/lang/String;[Ljava/lang/String;)Ljava/nio/file/Path; (0) 37 | 38 | javax/xml/ws/handler/MessageContext$Scope.valueOf(Ljava/lang/String;)Ljavax/xml/ws/handler/MessageContext$Scope; (0) 39 | java/lang/Enum.valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; (0) 40 | java/lang/Class.enumConstantDirectory()Ljava/util/Map; (0) 41 | java/lang/Class.getEnumConstantsShared()[Ljava/lang/Object; (0) 42 | java/lang/reflect/Method.invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; (0) 43 | 44 | com/sun/xml/internal/ws/handler/HandlerProcessor.closeHandlers(Ljavax/xml/ws/handler/MessageContext;II)V (0) 45 | java/util/logging/Logger.log(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/Throwable;)V (0) 46 | java/util/logging/Logger.doLog(Ljava/util/logging/LogRecord;)V (0) 47 | java/util/logging/Logger.getEffectiveLoggerBundle()Ljava/util/logging/Logger$LoggerBundle; (0) 48 | java/util/logging/Logger.findResourceBundle(Ljava/lang/String;Z)Ljava/util/ResourceBundle; (0) 49 | java/util/ResourceBundle.getBundle(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;)Ljava/util/ResourceBundle; (0) 50 | java/util/ResourceBundle.getBundleImpl(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle; (0) 51 | java/util/ResourceBundle.findBundle(Ljava/util/ResourceBundle$CacheKey;Ljava/util/List;Ljava/util/List;ILjava/util/ResourceBundle$Control;Ljava/util/ResourceBundle;)Ljava/util/ResourceBundle; (0) 52 | java/util/ResourceBundle.findBundleInCache(Ljava/util/ResourceBundle$CacheKey;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle; (0) 53 | java/util/ResourceBundle$Control.needsReload(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/util/ResourceBundle;J)Z (0) 54 | java/net/URL.openConnection()Ljava/net/URLConnection; (0) 55 | 56 | com/sun/xml/internal/ws/handler/HandlerTube.closeServersideHandlers(Ljavax/xml/ws/handler/MessageContext;)V (0) 57 | com/sun/xml/internal/ws/handler/HandlerProcessor.closeHandlers(Ljavax/xml/ws/handler/MessageContext;II)V (0) 58 | java/util/logging/Logger.log(Ljava/util/logging/Level;Ljava/lang/String;Ljava/lang/Throwable;)V (0) 59 | java/util/logging/Logger.doLog(Ljava/util/logging/LogRecord;)V (0) 60 | java/util/logging/Logger.getEffectiveLoggerBundle()Ljava/util/logging/Logger$LoggerBundle; (0) 61 | java/util/logging/Logger.findResourceBundle(Ljava/lang/String;Z)Ljava/util/ResourceBundle; (0) 62 | java/util/ResourceBundle.getBundle(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;)Ljava/util/ResourceBundle; (0) 63 | java/util/ResourceBundle.getBundleImpl(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle; (0) 64 | java/util/ResourceBundle.findBundle(Ljava/util/ResourceBundle$CacheKey;Ljava/util/List;Ljava/util/List;ILjava/util/ResourceBundle$Control;Ljava/util/ResourceBundle;)Ljava/util/ResourceBundle; (0) 65 | java/util/ResourceBundle.findBundleInCache(Ljava/util/ResourceBundle$CacheKey;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle; (0) 66 | java/util/ResourceBundle$Control.needsReload(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/util/ResourceBundle;J)Z (0) 67 | java/net/URL.openConnection()Ljava/net/URLConnection; (0) 68 | 69 | ``` 70 | 71 | ## threedr3am版本https://github.com/threedr3am/gadgetinspector 72 | ================ 73 | ##### 一、加入了Fastjson的gadget chain挖掘 74 | 75 | 使用方式,main方法启动,启动参数: 76 | ``` 77 | --config fastjson /xxxxx/xxxxx/xxxxx/xxxxx.jar /xxxxx/xxxxx/xxxxx/xxxxx2.jar /xxxxx/xxxxx/xxxxx/xxxxx3.jar 78 | ``` 79 | 80 | ##### 二、加入SQLInject的检测 81 | 82 | slink暂时只加入了JdbcTemplate的检测,后续慢慢加入mybatis、原生jdbc、jpa、hibernate等。 83 | 84 | 使用方式,main方法启动,启动参数(新加入--boot参数,用于在spring-boot项目的jar包含有其它jar依赖的情况下): 85 | ``` 86 | --config sqlinject --boot /xxxxx/xxxx/jdbc-1.0-SNAPSHOT.jar 87 | ``` 88 | 89 | #### 三、不使用污点分析(挖掘更全面的链) 90 | 91 | 最新加入--NoTaintTrack参数,指定不使用污点分析,将会把所有链都搜索出来,好处是不会遗漏,坏处是需要大量的人工审计 92 | 93 | 使用方式,main方法启动,启动参数: 94 | ``` 95 | --config fastjson --boot --noTaintTrack /xxxxx/xxxxx/xxxxx/xxxxx.jar 96 | ``` 97 | 建议使用: 98 | ``` 99 | --config fastjson 100 | --noTaintTrack 101 | --craw 0 102 | --max 30 103 | --history scan-history-fastjson-jndi.dat 104 | --slink JNDI 105 | --skipSourcesFile /Users/threedr3am/xxx/gadgetinspector/fastjson-skip-sources.demo 106 | /Users/threedr3am/.m2/repository/ 107 | ``` 108 | 遍历/Users/threedr3am/.m2/repository/目录,把找到的jar包,30个一批的形式去不使用污点分析,挖掘JNDI slink的fastjson gadget 109 | 110 | #### 参数描述 111 | 1. --config xxx:挖掘什么样的gadget chains(jackson、fastjson、sqlinject、jserial...) 112 | 2. --boot:指定该jar为SpringBoot项目jar包 113 | 3. --noTaintTrack:不使用污点分析,将会把所有链都搜索出来,好处是不会遗漏,坏处是需要大量的人工审计 114 | 4. --mybatis.xml xxx:当挖掘sqlinject时,若工程使用了Mybatis,则可通过指定mapper xml所在目录,进行挖掘Mybatis的sql注入 115 | 5. --resume:是否项目启动时不删除所有dat数据文件 116 | 6. --opLevel 1:链聚合优化等级,1表示一层优化,默认0不优化 117 | 7. --history recordFileName:启用历史扫描jar包记录,方便大规模扫描时不重复扫描旧jar包,好处时减少工作时间,坏处是遇到依赖组合的gadget可能扫不出来 118 | 8. --max 100:最多扫描100个jar包 119 | 10. --onlyJDK:仅扫描jdk依赖(rt.jar、jce.jar) 120 | 11. --maxChainLength 5:只输出chain长度小于等于5的chain 121 | 12. --crawMaven /Users/threedr3am/jar/:使用内置爬虫,自动爬取maven仓库,jar存储至/Users/threedr3am/jar/,maven仓库极慢,可以挂代理 122 | 13. --onlyCrawMaven:只启动maven爬虫 123 | 14. --onlyCrawMavenPopular:只启动maven-popular爬虫 124 | 15. --onlyCrawNexus:只启动nexus爬虫 125 | 16. --craw 10:启用爬虫功能,每10分钟分析一遍,出一次报告。若配置--crawMaven,则使用内置爬虫功能,爬取maven仓库 126 | 17. --slink JNDI:指定挖掘的slinks,可选JNDI、SSRFAndXXE、EXEC、FileIO、Reflect、BCEL(hessian专用),默认不填挖掘除专用外的所有slinks 127 | 18. --skipSourcesFile /xxx/xxxx/xxx.txt: 跳过哪些经常误报的class source,参考文件fastjson-skip-sources.demo 128 | 19. --slinksFile /xxx/xxxx/xxx.txt: 自定义挖掘的slinks,使用后--slink参数忽略,参考文件fastjson-slinks.demo 129 | 130 | ## JackOfMostTrades版本https://github.com/JackOfMostTrades/gadgetinspector 131 | ================ 132 | 133 | This project inspects Java libraries and classpaths for gadget chains. Gadgets chains are used to construct exploits for deserialization vulnerabilities. By automatically discovering possible gadgets chains in an application's classpath penetration testers can quickly construct exploits and application security engineers can assess the impact of a deserialization vulnerability and prioritize its remediation. 134 | 135 | This project was presented at Black Hat USA 2018. Learn more about it there! (Links pending) 136 | 137 | DISCLAIMER: This project is alpha at best. It needs tests and documentation added. Feel free to help by adding either! 138 | 139 | Building 140 | ======== 141 | 142 | Assuming you have a JDK installed on your system, you should be able to just run `./gradlew shadowJar`. You can then run the application with `java -jar build/libs/gadget-inspector-all.jar `. 143 | 144 | How to Use 145 | ========== 146 | 147 | This application expects as argument(s) either a path to a war file (in which case the war will be exploded and all of its classes and libraries used as a classpath) or else any number of jars. 148 | 149 | Note that the analysis can be memory intensive (and so far gadget inspector has not been optimized at all to be less memory greedy). For small libraries you probably want to allocate at least 2GB of heap size (i.e. with the `-Xmx2G` flag). For larger applications you will want to use as much memory as you can spare. 150 | 151 | The toolkit will go through several stages of classpath inspection to build up datasets for use in later stages. These datasets are written to files with a `.dat` extension and can be discarded after your run (they are written mostly so that earlier stages can be skipped during development). 152 | 153 | After the analysis has run the file `gadget-chains.txt` will be written. 154 | 155 | 156 | Example 157 | ======= 158 | 159 | The following is an example from running against [`commons-collections-3.2.1.jar`](http://central.maven.org/maven2/commons-collections/commons-collections/3.2.1/commons-collections-3.2.1.jar), e.g. with 160 | 161 | ``` 162 | wget http://central.maven.org/maven2/commons-collections/commons-collections/3.2.1/commons-collections-3.2.1.jar 163 | java -Xmx2G -jar build/libs/gadget-inspector-all.jar commons-collections-3.2.1.jar 164 | ``` 165 | 166 | In gadget-chains.txt there is the following chain: 167 | ``` 168 | com/sun/corba/se/spi/orbutil/proxy/CompositeInvocationHandlerImpl.invoke(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object; (-1) 169 | com/sun/corba/se/spi/orbutil/proxy/CompositeInvocationHandlerImpl.invoke(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object; (0) 170 | org/apache/commons/collections/map/DefaultedMap.get(Ljava/lang/Object;)Ljava/lang/Object; (0) 171 | org/apache/commons/collections/functors/InvokerTransformer.transform(Ljava/lang/Object;)Ljava/lang/Object; (0) 172 | java/lang/reflect/Method.invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; (0) 173 | ``` 174 | 175 | The entry point of this chain is an implementation of the JDK `InvocationHandler` class. Using the same trick as in the original commons-collections gadget chain, any serializable implementation of this class is reachable in a gadget chain, so the discovered chain starts here. This method invokes `classToInvocationHandler.get()`. The discovered gadget chain indicates that the `classToInvocationHandler` can be serialized as a `DefaultedMap` so that the this invocation jumps to `DefaultedMap.get()`. The next step in the chain invokes `value.transform()` from this method. The parameter `value` in this class can be serialized as a `InvokerTransformer`. Inside this class's `transform` method we see that we call `cls.getMethodName(iMethodName, ...).invoke(...)`. Gadget inspector determined that `iMethodName` is attacker controllable as a serialized member, and thus an attacker can execute an arbitrary method on the class. 176 | 177 | This gadget chain is the building block of the [full commons-collections gadget](https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java) chain discovered by Frohoff. In the above case, the gadget inspector happened to discovery entry through `CompositeInvocationHandlerImpl` and `DefaultedMap` instead of `AnnotationInvocationHandler` and `LazyMap`, but is largely the same. 178 | 179 | 180 | Other Examples 181 | ============== 182 | 183 | If you're looking for more examples of what kind of chains this tool can find, the following libraries also have some interesting results: 184 | 185 | * http://central.maven.org/maven2/org/clojure/clojure/1.8.0/clojure-1.8.0.jar 186 | * https://mvnrepository.com/artifact/org.scala-lang/scala-library/2.12.5 187 | * http://central.maven.org/maven2/org/python/jython-standalone/2.5.3/jython-standalone-2.5.3.jar 188 | 189 | Don't forget that you can also point gadget inspector at a complete application (packaged as a JAR or WAR). For example, when analyzing the war for the [Zksample2](https://sourceforge.net/projects/zksample2/) application we get the following gadget chain: 190 | 191 | ``` 192 | net/sf/jasperreports/charts/design/JRDesignPieDataset.readObject(Ljava/io/ObjectInputStream;)V (1) 193 | org/apache/commons/collections/FastArrayList.add(Ljava/lang/Object;)Z (0) 194 | java/util/ArrayList.clone()Ljava/lang/Object; (0) 195 | org/jfree/data/KeyToGroupMap.clone()Ljava/lang/Object; (0) 196 | org/jfree/data/KeyToGroupMap.clone(Ljava/lang/Object;)Ljava/lang/Object; (0) 197 | java/lang/reflect/Method.invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; (0) 198 | ``` 199 | 200 | As you can see, this utilizes several different libraries contained in the application in order to build up the chain. 201 | 202 | FAQ 203 | === 204 | 205 | **Q:** If gadget inspector finds a gadget chain, can an exploit be built from it? 206 | 207 | **A:** Not always. The analysis uses some simplifying assumptions and can report false positives (gadget chains that don't actually exist). As a simple example, it doesn't try to solve for the satisfiability of branch conditions. Thus it will report the following as a gadget chain: 208 | 209 | ```java 210 | public class MySerializableClass implements Serializable { 211 | public void readObject(ObjectInputStream ois) { 212 | if (false) System.exit(0); 213 | ois.defaultReadObject(); 214 | } 215 | } 216 | ``` 217 | 218 | Furthermore, gadget inspector has pretty broad conditions on those functions it considers interesting. For example, it treats reflection as interesting (i.e. calls to `Method.invoke()` where an attacker can control the method), but often times overlooked assertions mean that an attacker can *influence* the method invoked but does not have complete control. For example, an attacker may be able to invoke the "getError()" method in any class, but not any other method name. 219 | 220 | 221 | **Q:** If no gadget chains were found, does that mean my application is safe from exploitation? 222 | 223 | **A:** No! For one, the gadget inspector has a very narrow set of "sink" functions which it considers to have "interesting" side effects. This certainly doesn't mean there aren't other interesting or dangerous behaviors not in the list. 224 | 225 | Furthermore, there are a number of limitations to static analysis that mean the gadget inspector will always have blindspots. As an example, gadget inspector would presently miss this because it doesn't follow reflection calls. 226 | 227 | ```java 228 | public class MySerializableClass implements Serializable { 229 | public void readObject(ObjectInputStream ois) { 230 | System.class.getMethod("exit", int.class).invoke(null, 0); 231 | } 232 | } 233 | ``` 234 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/CallGraphDiscovery.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | import gadgetinspector.config.GIConfig; 4 | import gadgetinspector.config.JavaDeserializationConfig; 5 | import gadgetinspector.data.*; 6 | import java.util.Collections; 7 | import org.objectweb.asm.*; 8 | import org.objectweb.asm.commons.JSRInlinerAdapter; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.nio.file.Paths; 15 | import java.util.HashSet; 16 | import java.util.Map; 17 | import java.util.Set; 18 | import java.util.regex.Pattern; 19 | 20 | public class CallGraphDiscovery { 21 | private static final Logger LOGGER = LoggerFactory.getLogger(CallGraphDiscovery.class); 22 | 23 | private final Set discoveredCalls = new HashSet<>(); 24 | 25 | public void discover(final ClassResourceEnumerator classResourceEnumerator, GIConfig config) throws IOException { 26 | //加载所有方法信息 27 | Map methodMap = DataLoader.loadMethods(); 28 | //加载所有类信息 29 | Map classMap = DataLoader.loadClasses(); 30 | //加载所有父子类、超类、实现类关系 31 | InheritanceMap inheritanceMap = InheritanceMap.load(); 32 | //加载所有方法参数和返回值的污染关联 33 | Map> passthroughDataflow = ConfigHelper.taintTrack ? PassthroughDiscovery.load() : Collections.EMPTY_MAP; 34 | 35 | SerializableDecider serializableDecider = config.getSerializableDecider(methodMap, inheritanceMap); 36 | 37 | for (ClassResourceEnumerator.ClassResource classResource : classResourceEnumerator.getAllClasses()) { 38 | try (InputStream in = classResource.getInputStream()) { 39 | ClassReader cr = new ClassReader(in); 40 | try { 41 | cr.accept(new ModelGeneratorClassVisitor(classMap, inheritanceMap, passthroughDataflow, serializableDecider, Opcodes.ASM6), 42 | ClassReader.EXPAND_FRAMES); 43 | } catch (Exception e) { 44 | LOGGER.error("Error analyzing: " + classResource.getName(), e); 45 | } 46 | } catch (Exception e) { 47 | e.printStackTrace(); 48 | } 49 | } 50 | } 51 | 52 | public void save() throws IOException { 53 | DataLoader.saveData(Paths.get("callgraph.dat"), new GraphCall.Factory(), discoveredCalls); 54 | } 55 | 56 | private class ModelGeneratorClassVisitor extends ClassVisitor { 57 | 58 | private final Map classMap; 59 | private final InheritanceMap inheritanceMap; 60 | private final Map> passthroughDataflow; 61 | private final SerializableDecider serializableDecider; 62 | 63 | public ModelGeneratorClassVisitor(Map classMap, 64 | InheritanceMap inheritanceMap, 65 | Map> passthroughDataflow, 66 | SerializableDecider serializableDecider, int api) { 67 | super(api); 68 | this.classMap = classMap; 69 | this.inheritanceMap = inheritanceMap; 70 | this.passthroughDataflow = passthroughDataflow; 71 | this.serializableDecider = serializableDecider; 72 | } 73 | 74 | private String name; 75 | private String signature; 76 | private String superName; 77 | private String[] interfaces; 78 | 79 | @Override 80 | public void visit(int version, int access, String name, String signature, 81 | String superName, String[] interfaces) { 82 | super.visit(version, access, name, signature, superName, interfaces); 83 | this.name = name; 84 | this.signature = signature; 85 | this.superName = superName; 86 | this.interfaces = interfaces; 87 | } 88 | 89 | @Override 90 | public MethodVisitor visitMethod(int access, String name, String desc, 91 | String signature, String[] exceptions) { 92 | MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 93 | ModelGeneratorMethodVisitor modelGeneratorMethodVisitor = new ModelGeneratorMethodVisitor(classMap, 94 | inheritanceMap, passthroughDataflow, serializableDecider, api, mv, this.name, access, name, desc, signature, exceptions); 95 | 96 | return new JSRInlinerAdapter(modelGeneratorMethodVisitor, access, name, desc, signature, exceptions); 97 | } 98 | 99 | @Override 100 | public void visitOuterClass(String owner, String name, String desc) { 101 | // TODO: Write some tests to make sure we can ignore this 102 | super.visitOuterClass(owner, name, desc); 103 | } 104 | 105 | @Override 106 | public void visitInnerClass(String name, String outerName, String innerName, int access) { 107 | // TODO: Write some tests to make sure we can ignore this 108 | super.visitInnerClass(name, outerName, innerName, access); 109 | } 110 | 111 | @Override 112 | public void visitEnd() { 113 | super.visitEnd(); 114 | } 115 | } 116 | 117 | private class ModelGeneratorMethodVisitor extends TaintTrackingMethodVisitor { 118 | 119 | private final Map classMap; 120 | private final InheritanceMap inheritanceMap; 121 | private final SerializableDecider serializableDecider; 122 | private final String owner; 123 | private final int access; 124 | private final String name; 125 | private final String desc; 126 | 127 | public ModelGeneratorMethodVisitor(Map classMap, 128 | InheritanceMap inheritanceMap, 129 | Map> passthroughDataflow, 130 | SerializableDecider serializableDecider, final int api, final MethodVisitor mv, 131 | final String owner, int access, String name, String desc, String signature, 132 | String[] exceptions) { 133 | super(inheritanceMap, passthroughDataflow, api, mv, owner, access, name, desc, signature, exceptions); 134 | this.classMap = classMap; 135 | this.inheritanceMap = inheritanceMap; 136 | this.serializableDecider = serializableDecider; 137 | this.owner = owner; 138 | this.access = access; 139 | this.name = name; 140 | this.desc = desc; 141 | } 142 | 143 | @Override 144 | public void visitCode() { 145 | super.visitCode(); 146 | 147 | int localIndex = 0; 148 | int argIndex = 0; 149 | //使用arg前缀来表示方法入参,后续用于判断是否为目标调用方法的入参 150 | if ((this.access & Opcodes.ACC_STATIC) == 0) { 151 | setLocalTaint(localIndex, "arg" + argIndex); 152 | localIndex += 1; 153 | argIndex += 1; 154 | } 155 | for (Type argType : Type.getArgumentTypes(desc)) { 156 | setLocalTaint(localIndex, "arg" + argIndex); 157 | localIndex += argType.getSize(); 158 | argIndex += 1; 159 | } 160 | } 161 | 162 | @Override 163 | public void visitFieldInsn(int opcode, String owner, String name, String desc) { 164 | 165 | switch (opcode) { 166 | case Opcodes.GETSTATIC: 167 | break; 168 | case Opcodes.PUTSTATIC: 169 | break; 170 | case Opcodes.GETFIELD://入操作栈 171 | Type type = Type.getType(desc); 172 | if (type.getSize() == 1) { 173 | Boolean isTransient = null; 174 | 175 | // If a field type could not possibly be serialized, it's effectively transient 176 | if (!couldBeSerialized(serializableDecider, inheritanceMap, new ClassReference.Handle(type.getInternalName()))) { 177 | isTransient = Boolean.TRUE; 178 | } else { 179 | ClassReference clazz = classMap.get(new ClassReference.Handle(owner)); 180 | while (clazz != null) { 181 | for (ClassReference.Member member : clazz.getMembers()) { 182 | if (member.getName().equals(name)) { 183 | isTransient = (member.getModifiers() & Opcodes.ACC_TRANSIENT) != 0; 184 | break; 185 | } 186 | } 187 | if (isTransient != null) { 188 | break; 189 | } 190 | clazz = classMap.get(new ClassReference.Handle(clazz.getSuperClass())); 191 | } 192 | } 193 | 194 | Set newTaint = new HashSet<>(); 195 | if (!Boolean.TRUE.equals(isTransient)) { 196 | for (String s : getStackTaint(0)) { 197 | newTaint.add(s + "." + name); 198 | } 199 | } 200 | super.visitFieldInsn(opcode, owner, name, desc); 201 | //在调用方法前,都会先入栈,作为参数 202 | setStackTaint(0, newTaint); 203 | return; 204 | } 205 | break; 206 | case Opcodes.PUTFIELD: 207 | break; 208 | default: 209 | throw new IllegalStateException("Unsupported opcode: " + opcode); 210 | } 211 | 212 | super.visitFieldInsn(opcode, owner, name, desc); 213 | } 214 | 215 | @Override 216 | public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 217 | //获取被调用method的参数和类型,非静态方法需要把实例类型放在第一个元素 218 | ClassReference.Handle classReferenceTest = new ClassReference.Handle(this.owner); 219 | if (classReferenceTest.getName().contains("com/project/webapp/controller/UserController") 220 | && new ClassReference.Handle(owner).getName().contains("java/io/ObjectInputStream")){ 221 | System.out.println("test by 5wiming"); 222 | } 223 | Type[] argTypes = Type.getArgumentTypes(desc); 224 | if (opcode != Opcodes.INVOKESTATIC) { 225 | Type[] extendedArgTypes = new Type[argTypes.length+1]; 226 | System.arraycopy(argTypes, 0, extendedArgTypes, 1, argTypes.length); 227 | extendedArgTypes[0] = Type.getObjectType(owner); 228 | argTypes = extendedArgTypes; 229 | } 230 | 231 | switch (opcode) { 232 | case Opcodes.INVOKESTATIC: 233 | case Opcodes.INVOKEVIRTUAL: 234 | case Opcodes.INVOKESPECIAL: 235 | case Opcodes.INVOKEINTERFACE: 236 | if (!ConfigHelper.taintTrack) { 237 | //不进行污点分析,全部调用关系都记录 238 | discoveredCalls.add(new GraphCall( 239 | new MethodReference.Handle(new ClassReference.Handle(this.owner), this.name, this.desc), 240 | new MethodReference.Handle(new ClassReference.Handle(owner), name, desc), 241 | 0, 242 | "", 243 | 0)); 244 | break; 245 | } 246 | int stackIndex = 0; 247 | for (int i = 0; i < argTypes.length; i++) { 248 | //最右边的参数,就是最后入栈,即在栈顶 249 | int argIndex = argTypes.length-1-i; 250 | Type type = argTypes[argIndex]; 251 | //操作数栈出栈,调用方法前,参数都已入栈 252 | Set taint = getStackTaint(stackIndex); 253 | if (taint.size() > 0) { 254 | for (String argSrc : taint) { 255 | //取出出栈的参数,判断是否为当前方法的入参,arg前缀 256 | if (!argSrc.substring(0, 3).equals("arg")) { 257 | throw new IllegalStateException("Invalid taint arg: " + argSrc); 258 | } 259 | int dotIndex = argSrc.indexOf('.'); 260 | int srcArgIndex; 261 | String srcArgPath; 262 | if (dotIndex == -1) { 263 | srcArgIndex = Integer.parseInt(argSrc.substring(3)); 264 | srcArgPath = null; 265 | } else { 266 | srcArgIndex = Integer.parseInt(argSrc.substring(3, dotIndex)); 267 | srcArgPath = argSrc.substring(dotIndex+1); 268 | } 269 | //记录参数流动关系 270 | //argIndex:当前方法参数索引,srcArgIndex:对应上一级方法的参数索引 271 | 272 | discoveredCalls.add(new GraphCall( 273 | new MethodReference.Handle(new ClassReference.Handle(this.owner), this.name, this.desc), 274 | new MethodReference.Handle(new ClassReference.Handle(owner), name, desc), 275 | srcArgIndex, 276 | srcArgPath, 277 | argIndex)); 278 | } 279 | } 280 | 281 | stackIndex += type.getSize(); 282 | } 283 | break; 284 | default: 285 | throw new IllegalStateException("Unsupported opcode: " + opcode); 286 | } 287 | 288 | super.visitMethodInsn(opcode, owner, name, desc, itf); 289 | } 290 | } 291 | 292 | public static void main(String[] args) throws Exception { 293 | ClassLoader classLoader = Util.getWarClassLoader(Paths.get(args[0])); 294 | 295 | CallGraphDiscovery callGraphDiscovery = new CallGraphDiscovery(); 296 | callGraphDiscovery.discover(new ClassResourceEnumerator(classLoader), new JavaDeserializationConfig()); 297 | callGraphDiscovery.save(); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/main/java/gadgetinspector/GadgetInspector.java: -------------------------------------------------------------------------------- 1 | package gadgetinspector; 2 | 3 | import gadgetinspector.config.ConfigRepository; 4 | import gadgetinspector.config.GIConfig; 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.io.OutputStreamWriter; 10 | import java.io.Writer; 11 | import java.nio.charset.StandardCharsets; 12 | import java.nio.file.FileVisitResult; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | import java.nio.file.Paths; 16 | import java.nio.file.SimpleFileVisitor; 17 | import java.nio.file.StandardOpenOption; 18 | import java.nio.file.attribute.BasicFileAttributes; 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.HashSet; 22 | import java.util.List; 23 | import java.util.Scanner; 24 | import java.util.Set; 25 | import java.util.concurrent.atomic.AtomicBoolean; 26 | import java.util.concurrent.atomic.AtomicInteger; 27 | import java.util.stream.Collectors; 28 | import org.apache.log4j.ConsoleAppender; 29 | import org.apache.log4j.Level; 30 | import org.apache.log4j.PatternLayout; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | 34 | /** 35 | * Main entry point for running an end-to-end analysis. Deletes all data files before starting and writes discovered 36 | * gadget chains to gadget-chains.txt. 37 | */ 38 | public class GadgetInspector { 39 | private static final Logger LOGGER = LoggerFactory.getLogger(GadgetInspector.class); 40 | 41 | private static void printUsage() { 42 | System.out.println("Usage:\n Pass either a single argument which will be interpreted as a WAR, or pass " + 43 | "any number of arguments which will be intepretted as a list of JARs forming a classpath."); 44 | 45 | } 46 | 47 | static { 48 | try { 49 | if (!Files.exists(Paths.get("gadget-result"))) { 50 | Files.createDirectory(Paths.get("gadget-result")); 51 | } 52 | if (!Files.exists(Paths.get("conf"))) { 53 | Files.createDirectory(Paths.get("conf")); 54 | } 55 | if (!Files.exists(Paths.get("history"))) { 56 | Files.createDirectory(Paths.get("history")); 57 | } 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | 63 | public static void main(String[] args) throws Exception { 64 | if (args.length == 0) { 65 | printUsage(); 66 | System.exit(1); 67 | } 68 | 69 | //配置log4j用于输出日志 70 | configureLogging(); 71 | 72 | //是否不删除所有的dat文件 73 | boolean resume = false; 74 | //是否Spring-Boot jar项目 75 | boolean boot = false; 76 | //fuzz类型,默认java原生序列化 77 | GIConfig config = ConfigRepository.getConfig("jserial"); 78 | 79 | int argIndex = 0; 80 | while (argIndex < args.length) { 81 | String arg = args[argIndex]; 82 | if (!arg.startsWith("--")) { 83 | break; 84 | } 85 | if (arg.equals("--resume")) { 86 | //不删除dat文件 87 | resume = true; 88 | } else if (arg.equals("--config")) { 89 | //--config参数指定fuzz类型 90 | config = ConfigRepository.getConfig(args[++argIndex]); 91 | if (config == null) { 92 | throw new IllegalArgumentException("Invalid config name: " + args[argIndex]); 93 | } 94 | ConfigHelper.giConfig = config; 95 | } else if (arg.equals("--boot")) { 96 | //指定为Spring-Boot jar项目 97 | boot = true; 98 | } else if (arg.equals("--mybatis.xml")) { 99 | //mybatis mapper xml目录位置 100 | ConfigHelper.mybatisMapperXMLPath = args[++argIndex]; 101 | } else if (arg.equals("--noTaintTrack")) { 102 | //是否污点分析,若不使用污点分析,将会把所有链都搜索出来,好处是不会遗漏,坏处是需要大量的人工审计 103 | ConfigHelper.taintTrack = false; 104 | } else if (arg.equals("--opLevel")) { 105 | //链聚合优化等级,--OpLevel 1表示一层优化,默认0不优化 106 | ConfigHelper.opLevel = Integer.parseInt(args[++argIndex]); 107 | } else if (arg.equals("--history")) { 108 | //启用历史扫描jar包记录,方便大规模扫描时不重复扫描旧jar包,好处时减少工作时间,坏处是遇到依赖组合的gadget可能扫不出来 109 | ConfigHelper.history = true; 110 | ConfigHelper.historyRecordFile += args[++argIndex]; 111 | } else if (arg.equals("--max")) { 112 | //大规模扫描jar时,指定最大数量 113 | ConfigHelper.maxJarCount = Integer.parseInt(args[++argIndex]); 114 | }else if (arg.equals("--similarLevel")) { 115 | //大规模扫描jar时,指定最大数量 116 | ConfigHelper.similarLevel = Integer.parseInt(args[++argIndex]); 117 | } else if (arg.equals("--onlyJDK")) { 118 | //仅扫描jdk 119 | ConfigHelper.onlyJDK = true; 120 | } else if (arg.equals("--maxChainLength")) { 121 | //仅输出小于该链长度的链 122 | ConfigHelper.maxChainLength = Integer.parseInt(args[++argIndex]); 123 | } else if (arg.equals("--craw")) { 124 | //启用爬虫功能,每*分钟分析一遍,出一次报告。若配置--crawMaven,则使用内置爬虫功能,爬取maven仓库 125 | ConfigHelper.craw = true; 126 | ConfigHelper.crawMin = Integer.parseInt(args[++argIndex]); 127 | } else if(arg.equals("--maxRepeatBranchesTimes")){ 128 | ConfigHelper.maxRepeatBranchesTimes = Integer.parseInt(args[++argIndex]); 129 | } else if (arg.equals("--crawMaven")) { 130 | //爬取maven仓库 131 | ConfigHelper.crawMaven = true; 132 | ConfigHelper.crawMavenJarPath = args[++argIndex]; 133 | if (!ConfigHelper.crawMavenJarPath.endsWith("/")) 134 | ConfigHelper.crawMavenJarPath += "/"; 135 | } else if (arg.equals("--onlyCrawMaven")) { 136 | //只爬取maven仓库,不挖掘gadget 137 | ConfigHelper.onlyCrawMaven = true; 138 | } else if (arg.equals("--onlyCrawNexus")) { 139 | //只爬取nexus本地仓库,不挖掘gadget 140 | ConfigHelper.onlyCrawNexus = true; 141 | } else if (arg.equals("--onlyCrawMavenPopular")) { 142 | //只爬取maven仓库popular,不挖掘gadget 143 | ConfigHelper.onlyCrawMavenPopular = true; 144 | } else if (arg.equals("--skipSourcesFile")) { 145 | //跳过的sources 146 | ConfigHelper.skipSourcesFile = args[++argIndex]; 147 | } else if (arg.equals("--slinksFile")) { 148 | //自定义slinks,每行空格分离三段( 149 | // 类 方法名 方法描述,例:java/rmi/registry/Registry lookup (Ljava/lang/String;)Ljava/rmi/Remote;), 150 | // 缺少的不判断(例:java/rmi/registry/Registry lookup,不判断方法描述) 151 | ConfigHelper.slinksFile = args[++argIndex]; 152 | } else if (arg.equals("--slink")) { 153 | for (int i = argIndex + 1; i < args.length - 1; i++) { 154 | if (!args[i].startsWith("--")) { 155 | ConfigHelper.slinks.add(args[++argIndex]); 156 | } else { 157 | break; 158 | } 159 | } 160 | } else { 161 | throw new IllegalArgumentException("Unexpected argument: " + arg); 162 | } 163 | 164 | argIndex += 1; 165 | } 166 | 167 | if (ConfigHelper.crawMaven) { 168 | if (!ConfigHelper.onlyCrawMavenPopular && !ConfigHelper.onlyCrawNexus) { 169 | MavenCrawer.start(); 170 | } 171 | if (!ConfigHelper.onlyCrawMaven && !ConfigHelper.onlyCrawNexus) { 172 | MavenCrawerPopular.start(); 173 | } 174 | if (!ConfigHelper.onlyCrawMaven && !ConfigHelper.onlyCrawMavenPopular) { 175 | // MavenCrawerNexus.start(); 176 | } 177 | } 178 | 179 | for (int i = 0; (i < 1 || ConfigHelper.craw) && (!ConfigHelper.onlyCrawMaven && !ConfigHelper.onlyCrawNexus && !ConfigHelper.onlyCrawMavenPopular); i++) { 180 | AtomicBoolean haveNewJar = new AtomicBoolean(false); 181 | List pathList = new ArrayList<>(); 182 | ClassLoader classLoader = initJarData(args, boot, argIndex, haveNewJar, pathList); 183 | if (ConfigHelper.craw && !haveNewJar.getAndSet(false)) { 184 | Thread.sleep(60 * 1000L); 185 | continue; 186 | } 187 | 188 | //类枚举加载器,具有两个方法 189 | //getRuntimeClasses获取rt.jar的所有class 190 | //getAllClasses获取rt.jar以及classLoader加载的class 191 | final ClassResourceEnumerator classResourceEnumerator = new ClassResourceEnumerator( 192 | classLoader); 193 | 194 | //删除所有的dat文件 195 | if (!resume) { 196 | // Delete all existing dat files 197 | LOGGER.info("Deleting stale data..."); 198 | for (String datFile : Arrays 199 | .asList("classes.dat", "methods.dat", "inheritanceMap.dat", 200 | "passthrough.dat", "callgraph.dat", "sources.dat", "methodimpl.dat", 201 | "slinks.dat")) { 202 | final Path path = Paths.get(datFile); 203 | if (Files.exists(path)) { 204 | Files.delete(path); 205 | } 206 | } 207 | } 208 | 209 | try { 210 | //扫描java runtime所有的class(rt.jar)和指定的jar或war中的所有class 211 | beginDiscovery(config, classResourceEnumerator, pathList); 212 | } catch (Throwable t) { 213 | t.printStackTrace(); 214 | //捕获异常,避免异常导致程序终止 215 | } 216 | 217 | saveHistory(haveNewJar, pathList); 218 | Thread.sleep(ConfigHelper.crawMin * 60 * 1000L); 219 | LOGGER.info("Analysis complete!"); 220 | } 221 | } 222 | 223 | /** 224 | * 保存已被扫描的jar包记录 225 | * 226 | * @param haveNewJar 227 | * @param pathList 228 | * @throws IOException 229 | */ 230 | private static void saveHistory(AtomicBoolean haveNewJar, List pathList) 231 | throws IOException { 232 | if (ConfigHelper.history && pathList.size() > 0) { 233 | haveNewJar.set(true); 234 | Path filePath = Paths.get(ConfigHelper.historyRecordFile); 235 | if (!Files.exists(filePath)) 236 | Files.createFile(filePath); 237 | try (OutputStream outputStream = Files 238 | .newOutputStream(filePath, 239 | StandardOpenOption.APPEND); 240 | Writer writer = new OutputStreamWriter(outputStream, 241 | StandardCharsets.UTF_8)) { 242 | for (String jar : pathList.stream().map(path -> path.getFileName().toString()).collect( 243 | Collectors.toSet())) { 244 | writer.write(jar); 245 | writer.write("\n"); 246 | } 247 | writer.flush(); 248 | } 249 | } 250 | } 251 | 252 | private static ClassLoader initJarData(String[] args, boolean boot, int argIndex, 253 | AtomicBoolean haveNewJar, List pathList) 254 | throws IOException { 255 | ClassLoader classLoader = null; 256 | if (!ConfigHelper.onlyJDK) { 257 | //程序参数的最后一部分,即最后一个具有前缀--的参数(例:--resume)后 258 | if (args.length == argIndex + 1 && args[argIndex].toLowerCase().endsWith(".war")) { 259 | //加载war文件 260 | Path path = Paths.get(args[argIndex]); 261 | LOGGER.info("Using WAR classpath: " + path); 262 | //实现为URLClassLoader,加载war包下的WEB-INF/lib和WEB-INF/classes 263 | classLoader = Util.getWarClassLoader(path); 264 | } else if (args.length == argIndex + 1 && args[argIndex].toLowerCase().endsWith(".jar") 265 | && boot) { 266 | Path path = Paths.get(args[argIndex]); 267 | LOGGER.info("Using JAR classpath: " + path); 268 | //实现为URLClassLoader,加载jar包下的BOOT-INF/lib和BOOT-INF/classes 269 | classLoader = Util.getJarAndLibClassLoader(path); 270 | } else { 271 | //加载jar文件,java命令后部,可配置多个 272 | Set scanJarHistory = new HashSet<>(); 273 | Path filePath = Paths.get(ConfigHelper.historyRecordFile); 274 | if (ConfigHelper.history && Files.exists(filePath)) { 275 | try (InputStream inputStream = Files 276 | .newInputStream(filePath); 277 | Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name())) { 278 | while (scanner.hasNext()) { 279 | String jar = scanner.nextLine(); 280 | if (jar.length() > 0) { 281 | scanJarHistory.add(jar.trim()); 282 | } 283 | } 284 | } 285 | } 286 | AtomicInteger jarCount = new AtomicInteger(0); 287 | for (int i = 0; i < args.length - argIndex; i++) { 288 | String pathStr = args[argIndex + i]; 289 | if (!pathStr.endsWith(".jar")) { 290 | //todo 主要用于大批量的挖掘链 291 | //非.jar结尾,即目录,需要遍历目录找出所有jar文件 292 | File file = Paths.get(pathStr).toFile(); 293 | if (file == null || !file.exists()) 294 | continue; 295 | Files.walkFileTree(file.toPath(), new SimpleFileVisitor() { 296 | @Override 297 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 298 | if (file.getFileName().toString().endsWith(".jar")) { 299 | File readFile = file.toFile(); 300 | Path path = Paths.get(readFile.getAbsolutePath()); 301 | if (Files.exists(path)) { 302 | if (ConfigHelper.history) { 303 | if (!scanJarHistory 304 | .contains(path.getFileName().toString())) { 305 | if (jarCount.incrementAndGet() 306 | <= ConfigHelper.maxJarCount) { 307 | pathList.add(path); 308 | } 309 | } 310 | } else { 311 | if (jarCount.incrementAndGet() 312 | <= ConfigHelper.maxJarCount) { 313 | pathList.add(path); 314 | } 315 | } 316 | } 317 | } 318 | return FileVisitResult.CONTINUE; 319 | } 320 | }); 321 | 322 | continue; 323 | } 324 | Path path = Paths.get(pathStr).toAbsolutePath(); 325 | if (!Files.exists(path)) { 326 | throw new IllegalArgumentException("Invalid jar path: " + path); 327 | } 328 | pathList.add(path); 329 | } 330 | LOGGER.info("Using classpath: " + Arrays.toString(pathList.toArray())); 331 | //实现为URLClassLoader,加载所有指定的jar 332 | classLoader = Util.getJarClassLoader(pathList.toArray(new Path[0])); 333 | } 334 | } 335 | haveNewJar.set(pathList.size() != 0); 336 | return classLoader; 337 | } 338 | 339 | private static void beginDiscovery(GIConfig config, 340 | ClassResourceEnumerator classResourceEnumerator, 341 | List pathList) throws Exception { 342 | // Perform the various discovery steps 343 | if (!Files.exists(Paths.get("classes.dat")) || !Files.exists(Paths.get("methods.dat")) 344 | || !Files.exists(Paths.get("inheritanceMap.dat"))) { 345 | LOGGER.info("Running method discovery..."); 346 | MethodDiscovery methodDiscovery = new MethodDiscovery(); 347 | methodDiscovery.discover(classResourceEnumerator); 348 | //保存了类信息、方法信息、继承实现信息 349 | methodDiscovery.save(); 350 | } 351 | 352 | if (!Files.exists(Paths.get("slinks.dat")) && config.getSlinkDiscovery() != null) { 353 | LOGGER.info("Running slink discovery..."); 354 | SlinkDiscovery slinkDiscovery = config.getSlinkDiscovery(); 355 | slinkDiscovery.discover(); 356 | slinkDiscovery.save(); 357 | } 358 | 359 | if (!Files.exists(Paths.get("passthrough.dat")) && ConfigHelper.taintTrack) { 360 | LOGGER.info("Analyzing methods for passthrough dataflow..."); 361 | PassthroughDiscovery passthroughDiscovery = new PassthroughDiscovery(); 362 | //记录参数在方法调用链中的流动关联(如:A、B、C、D四个方法,调用链为A->B B->C C->D,其中参数随着调用关系从A流向B,在B调用C过程中作为入参并随着方法结束返回,最后流向D) 363 | //该方法主要是追踪上面所说的"B调用C过程中作为入参并随着方法结束返回",入参和返回值之间的关联 364 | passthroughDiscovery.discover(classResourceEnumerator, config); 365 | passthroughDiscovery.save(); 366 | } 367 | 368 | if (!Files.exists(Paths.get("callgraph.dat"))) { 369 | LOGGER.info("Analyzing methods in order to build a call graph..."); 370 | CallGraphDiscovery callGraphDiscovery = new CallGraphDiscovery(); 371 | //记录参数在方法调用链中的流动关联(如:A、B、C三个方法,调用链为A->B B->C,其中参数随着调用关系从A流向B,最后流C) 372 | //该方法主要是追踪上面所说的参数流动,即A->B入参和B->C入参的关系,以确定参数可控 373 | callGraphDiscovery.discover(classResourceEnumerator, config); 374 | callGraphDiscovery.save(); 375 | } 376 | 377 | if (!Files.exists(Paths.get("sources.dat"))) { 378 | LOGGER.info("Discovering gadget chain source methods..."); 379 | SourceDiscovery sourceDiscovery = config.getSourceDiscovery(); 380 | //查找利用链的入口(例:java原生反序列化的readObject) 381 | sourceDiscovery.discover(); 382 | sourceDiscovery.save(); 383 | } 384 | 385 | { 386 | LOGGER.info("Searching call graph for gadget chains..."); 387 | GadgetChainDiscovery gadgetChainDiscovery = new GadgetChainDiscovery(config); 388 | //根据上面的数据收集,最终分析利用链 389 | gadgetChainDiscovery.discover(pathList); 390 | } 391 | } 392 | 393 | private static void configureLogging() { 394 | ConsoleAppender console = new ConsoleAppender(); 395 | String PATTERN = "%d %c [%p] %m%n"; 396 | console.setLayout(new PatternLayout(PATTERN)); 397 | console.setThreshold(Level.INFO); 398 | console.activateOptions(); 399 | org.apache.log4j.Logger.getRootLogger().addAppender(console); 400 | } 401 | } 402 | --------------------------------------------------------------------------------