当前位置: 首页 > news >正文

网站做二维码龙岗网站制作公司一般多少钱

网站做二维码,龙岗网站制作公司一般多少钱,湖南手机网站建设公司,wordpress 内容发布作者#xff1a;Longofo知道创宇404实验室时间#xff1a;2019年9月4日起因一开始是听Badcode师傅说的这个工具#xff0c;在Black Hat 2018的一个议题提出来的。这是一个基于字节码静态分析的、利用已知技巧自动查找从source到sink的反序列化利用链工具。看了几遍作者在Bla… 作者Longofo知道创宇404实验室时间2019年9月4日起因一开始是听Badcode师傅说的这个工具在Black Hat 2018的一个议题提出来的。这是一个基于字节码静态分析的、利用已知技巧自动查找从source到sink的反序列化利用链工具。看了几遍作者在Black Hat上的演讲视频[1]与PPT[2]想从作者的演讲与PPT中获取更多关于这个工具的原理性的东西可是有些地方真的很费解。不过作者开源了这个工具[3]但没有给出详细的说明文档对这个工具的分析文章也很少看到一篇平安集团对这个工具的分析从文中描述来看他们对这个工具应该有一定的认识并做了一些改进但是在文章中对某些细节没有做过多的阐释。后面尝试了调试这个工具大致理清了这个工具的工作原理下面是对这个工具的分析过程以及对未来工作与改进的设想。关于这个工具•这个工具不是用来寻找漏洞而是利用已知的source-...-sink链或其相似特征发现分支利用链或新的利用链。•这个工具是在整个应用的classpath中寻找利用链。•这个工具进行了一些合理的预估风险判断(污点判断、污点传递等)。•这个工具会产生误报不是漏报(其实这里还是会漏报这是作者使用的策略决定的在后面的分析中可以看到)。•这个工具是基于字节码分析的对于Java应用来说很多时候我们并没有源码而只有War包、Jar包或class文件。•这个工具不会生成能直接利用的Payload具体的利用构造还需要人工参与。序列化与反序列化序列化(Serialization)是将对象的状态信息转化为可以存储或者传输形式的过程转化后的信息可以存储在磁盘上在网络传输过程中可以是字节、XML、JSON等格式而将字节、XML、JSON等格式的信息还原成对象这个相反的过程称为反序列化。在JAVA中对象的序列化和反序列化被广泛的应用到RMI(远程方法调用)及网络传输中。Java 中的序列化与反序列化库•JDK(ObjectInputStream)•XStream(XML,JSON•Jackson(XML,JSON)•Genson(JSON)•JSON-IO(JSON)•FlexSON(JSON)•Fastjson(JSON)•...不同的反序列化库在反序列化不同的类时有不同的行为、被反序列化类的不同魔术方法会被自动调用这些被自动调用的方法就能够作为反序列化的入口点(source)。如果这些被自动调用的方法又调用了其他子方法那么在调用链中某一个子方法也可以作为source就相当于已知了调用链的前部分从某个子方法开始寻找不同的分支。通过方法的层层调用可能到达某些危险的方法(sink)。•ObjectInputStream例如某个类实现了Serializable接口ObjectInputStream.readobject在反序列化类得到其对象时会自动查找这个类的readObject、readResolve等方法并调用。例如某个类实现了Externalizable接口ObjectInputStream.readobject在反序列化类得到其对象时会自动查找这个类的readExternal等方法并调用。 •Jackson  ObjectMapper.readValue 在反序列化类得到其对象时会自动查找反序列化类的无参构造方法、包含一个基础类型参数的构造方法、属性的setter、属性的getter等方法并调用。   •...在后面的分析中都使用JDK自带的ObjectInputStream作为样例。控制数据类型控制代码作者说在反序列化漏洞中如果控制了数据类型我们就控制了代码。这是什么意思呢按我的理解写了下面的一个例子public class TestDeserialization { interface Animal { public void eat(); } public static class Cat implements Animal,Serializable { Override public void eat() { System.out.println(cat eat fish); } } public static class Dog implements Animal,Serializable { Override public void eat() { try { Runtime.getRuntime().exec(calc); } catch (IOException e) { e.printStackTrace(); } System.out.println(dog eat bone); } } public static class Person implements Serializable { private Animal pet; public Person(Animal pet){ this.pet pet; } private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { pet (Animal) stream.readObject(); pet.eat(); } } public static void GeneratePayload(Object instance, String file) throws Exception { //将构造好的payload序列化后写入文件中 File f new File(file); ObjectOutputStream out new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(instance); out.flush(); out.close(); } public static void payloadTest(String file) throws Exception { //读取写入的payload并进行反序列化 ObjectInputStream in new ObjectInputStream(new FileInputStream(file)); Object obj in.readObject(); System.out.println(obj); in.close(); } public static void main(String[] args) throws Exception { Animal animal new Dog(); Person person new Person(animal); GeneratePayload(person,test.ser); payloadTest(test.ser);// Animal animal new Cat();// Person person new Person(animal);// GeneratePayload(person,test.ser);// payloadTest(test.ser); }}为了方便我把所有类写在一个类中进行测试。在Person类中有一个Animal类的属性pet它是Cat和Dog的接口。在序列化时我们能够控制Person的pet具体是Cat对象或者Dog对象因此在反序列化时在readObject中pet.eat()具体的走向就不一样了。如果是pet是Cat类对象就不会走到执行有害代码Runtime.getRuntime().exec(calc);这一步但是如果pet是Dog类的对象就会走到有害代码。即使有时候类属性在声明时已经为它赋值了某个具体的对象但是在Java中通过反射等方式依然能修改。如下public class TestDeserialization { interface Animal { public void eat(); } public static class Cat implements Animal, Serializable { Override public void eat() { System.out.println(cat eat fish); } } public static class Dog implements Animal, Serializable { Override public void eat() { try { Runtime.getRuntime().exec(calc); } catch (IOException e) { e.printStackTrace(); } System.out.println(dog eat bone); } } public static class Person implements Serializable { private Animal pet new Cat(); private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { pet (Animal) stream.readObject(); pet.eat(); } } public static void GeneratePayload(Object instance, String file) throws Exception { //将构造好的payload序列化后写入文件中 File f new File(file); ObjectOutputStream out new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(instance); out.flush(); out.close(); } public static void payloadTest(String file) throws Exception { //读取写入的payload并进行反序列化 ObjectInputStream in new ObjectInputStream(new FileInputStream(file)); Object obj in.readObject(); System.out.println(obj); in.close(); } public static void main(String[] args) throws Exception { Animal animal new Dog(); Person person new Person(); //通过反射修改私有属性 Field field person.getClass().getDeclaredField(pet); field.setAccessible(true); field.set(person, animal); GeneratePayload(person, test.ser); payloadTest(test.ser); }}在Person类中不能通过构造器或setter方法或其他方式对pet赋值属性在声明时已经被定义为Cat类的对象但是通过反射能将pet修改为Dog类的对象因此在反序列化时依然会走到有害代码处。这只是我自己对作者控制了数据类型就控制了代码的理解在Java反序列化漏洞中很多时候是利用到了Java的多态特性来控制代码走向最后达到恶意执行目的。魔术方法在上面的例子中能看到在反序列化时没有调用Person的readobject方法它是ObjectInputStream在反序列化对象时自动调用的。作者将在反序列化中会自动调用的方法称为魔术方法。使用ObjectInputStream反序列化时几个常见的魔术方法•Object.readObject() •Object.readResolve()•Object.finalize()•...一些可序列化的JDK类实现了上面这些方法并且还自动调用了其他方法(可以作为已知的入口点)•HashMap    •Object.hashCode()        •Object.equals()      •PriorityQueue                    •Comparator.compare()                    •Comparable.CompareTo()      •...一些sink•Runtime.exec()这种最为简单直接即直接在目标环境中执行命令•Method.invoke()这种需要适当地选择方法和参数通过反射执行Java方法•RMI/JNDI/JRMP等通过引用远程对象间接实现任意代码执行的效果•...作者给出了一个从Magic Methods(source)-Gadget Chains-Runtime.exec(sink)的例子上面的HashMap实现了readObject这个魔术方法并且调用了hashCode方法。某些类为了比较对象之间是否相等会实现equals方法(一般是equals和hashCode方法同时实现)。从图中可以看到AbstractTableModel$ff19274a正好实现了hashCode方法其中又调用了f.invoke方法f是IFn对象并且f能通过属性__clojureFnMap获取到。IFn是一个接口上面说到如果控制了数据类型就控制了代码走向。所以如果我们在序列化时在__clojureFnMap放置IFn接口的实现类FnCompose的一个对象那么就能控制f.invoke走FnCompose.invoke方法接着控制FnCompose.invoke中的f1、f2为FnConstant就能到达FnEval.invoke了(关于AbstractTableModel$ff19274a.hashcode中的f.invoke具体选择IFn的哪个实现类根据后面对这个工具的测试以及对决策原理的分析广度优先会选择短的路径也就是选择了FnEval.invoke所以这也是为什么要人为参与在后面的样例分析中也可以看到)。有了这条链只需要找到触发这个链的漏洞点就行了。Payload使用JSON格式表示如下{class:java.util.HashMap,members:[2, {class:AbstractTableModel$ff19274a,__clojureFnMap:{hashcode:{class:FnCompose,f1:{class,FnConstant,value:calc},f2:{class:FnEval} } } } ]}gadgetinspector 工作流程如作者所说正好使用了五个步骤// 枚举全部类以及类的所有方法if (!Files.exists(Paths.get(classes.dat)) || !Files.exists(Paths.get(methods.dat)) || !Files.exists(Paths.get(inheritanceMap.dat))) { LOGGER.info(Running method discovery...); MethodDiscovery methodDiscovery new MethodDiscovery(); methodDiscovery.discover(classResourceEnumerator); methodDiscovery.save(); }//生成passthrough数据流if (!Files.exists(Paths.get(passthrough.dat))) { LOGGER.info(Analyzing methods for passthrough dataflow...); PassthroughDiscovery passthroughDiscovery new PassthroughDiscovery(); passthroughDiscovery.discover(classResourceEnumerator, config); passthroughDiscovery.save(); }//生成passthrough调用图if (!Files.exists(Paths.get(callgraph.dat))) { LOGGER.info(Analyzing methods in order to build a call graph...); CallGraphDiscovery callGraphDiscovery new CallGraphDiscovery(); callGraphDiscovery.discover(classResourceEnumerator, config); callGraphDiscovery.save(); }//搜索可用的sourceif (!Files.exists(Paths.get(sources.dat))) { LOGGER.info(Discovering gadget chain source methods...); SourceDiscovery sourceDiscovery config.getSourceDiscovery(); sourceDiscovery.discover(); sourceDiscovery.save(); }//搜索生成调用链 { LOGGER.info(Searching call graph for gadget chains...); GadgetChainDiscovery gadgetChainDiscovery new GadgetChainDiscovery(config); gadgetChainDiscovery.discover(); }Step1 枚举全部类以及每个类的所有方法要进行调用链的搜索首先得有所有类及所有类方法的相关信息public class MethodDiscovery {private static final Logger LOGGER LoggerFactory.getLogger(MethodDiscovery.class);private final List discoveredClasses new ArrayList();//保存所有类信息private final List discoveredMethods new ArrayList();//保存所有方法信息 ... ...public void discover(final ClassResourceEnumerator classResourceEnumerator) throws Exception {//classResourceEnumerator.getAllClasses()获取了运行时的所有类(JDK rt.jar)以及要搜索应用中的所有类for (ClassResourceEnumerator.ClassResource classResource : classResourceEnumerator.getAllClasses()) {try (InputStream in classResource.getInputStream()) { ClassReader cr new ClassReader(in);try { cr.accept(new MethodDiscoveryClassVisitor(), ClassReader.EXPAND_FRAMES);//通过ASM框架操作字节码并将类信息保存到this.discoveredClasses将方法信息保存到discoveredMethods } catch (Exception e) { LOGGER.error(Exception analyzing: classResource.getName(), e); } } } } ... ...public void save() throws IOException { DataLoader.saveData(Paths.get(classes.dat), new ClassReference.Factory(), discoveredClasses);//将类信息保存到classes.dat DataLoader.saveData(Paths.get(methods.dat), new MethodReference.Factory(), discoveredMethods);//将方法信息保存到methods.dat Map classMap new HashMap();for (ClassReference clazz : discoveredClasses) { classMap.put(clazz.getHandle(), clazz); } InheritanceDeriver.derive(classMap).save();//查找所有继承关系并保存 }}来看下classes.dat、methods.dat分别长什么样子•classes.dat找了两个比较有特征的类名父类名所有接口是否是接口成员com/sun/deploy/jardiff/JarDiffPatcherjava/lang/Objectcom/sun/deploy/jardiff/JarDiffConstants,com/sun/deploy/jardiff/PatcherfalsenewBytes!2![Bcom/sun/corba/se/impl/presentation/rmi/InvocationHandlerFactoryImpl$CustomCompositeInvocationHandlerImplcom/sun/corba/se/spi/orbutil/proxy/CompositeInvocationHandlerImplcom/sun/corba/se/spi/orbutil/proxy/LinkedInvocationHandler,java/io/Serializablefalsestub!130!com/sun/corba/se/spi/presentation/rmi/DynamicStub!this$0!4112!com/sun/corba/se/impl/presentation/rmi/InvocationHandlerFactoryImpl第一个类com/sun/deploy/jardiff/JarDiffPatcher和上面的表格信息对应一下是吻合的•类名com/sun/deploy/jardiff/JarDiffPatcher•父类java/lang/Object如果一类没有显式继承其他类默认隐式继承java/lang/Object并且java中不允许多继承所以每个类只有一个父类•所有接口com/sun/deploy/jardiff/JarDiffConstants、com/sun/deploy/jardiff/Patcher•是否是接口false•成员newBytes!2![BnewBytes成员Byte类型。为什么没有将static/final类型的成员加进去呢这里还没有研究如何操作字节码所以作者这里的判断实现部分暂且跳过。不过猜测应该是这种类型的变量并不能成为污点所以忽略了第二个类com/sun/corba/se/impl/presentation/rmi/InvocationHandlerFactoryImpl$CustomCompositeInvocationHandlerImpl和上面的表格信息对应一下也是吻合的•类名com/sun/corba/se/impl/presentation/rmi/InvocationHandlerFactoryImpl$CustomCompositeInvocationHandlerImpl是一个内部类•父类com/sun/corba/se/spi/orbutil/proxy/CompositeInvocationHandlerImpl•所有接口com/sun/corba/se/spi/orbutil/proxy/LinkedInvocationHandler,java/io/Serializable•是否是接口false•成员stub!130!com/sun/corba/se/spi/presentation/rmi/DynamicStub!this$0!4112!com/sun/corba/se/impl/presentation/rmi/InvocationHandlerFactoryImpl!*!这里可以暂时理解为分割符有一个成员stub类型com/sun/corba/se/spi/presentation/rmi/DynamicStub。因为是内部类所以多了个this成员这个this指向的是外部类      •methods.dat同样找几个比较有特征的类名方法名方法描述信息是否是静态方法sun/nio/cs/ext/Big5newEncoder()Ljava/nio/charset/CharsetEncoder;falsesun/nio/cs/ext/Big5_HKSCS$Decoder(Ljava/nio/charset/Charset;Lsun/nio/cs/ext/Big5_HKSCS$1;)Vfalsesun/nio/cs/ext/Big5#newEncoder      •类名sun/nio/cs/ext/Big5            •方法名newEncoder           •方法描述信息()Ljava/nio/charset/CharsetEncoder; 无参返回java/nio/charset/CharsetEncoder对象            •是否是静态方法falsesun/nio/cs/ext/Big5_HKSCS$Decoder#•类名sun/nio/cs/ext/Big5_HKSCS$Decoder•方法名•方法描述信息(Ljava/nio/charset/Charset;Lsun/nio/cs/ext/Big5_HKSCS$1;)V 参数1是java/nio/charset/Charset类型参数2是sun/nio/cs/ext/Big5_HKSCS$1类型返回值void•是否是静态方法false继承关系的生成继承关系在后面用来判断一个类是否能被某个库序列化、以及搜索子类方法实现等会用到。public class InheritanceDeriver { private static final Logger LOGGER LoggerFactory.getLogger(InheritanceDeriver.class); public static InheritanceMap derive(Map classMap) { LOGGER.debug(Calculating inheritance for (classMap.size()) classes...); Map implicitInheritance new HashMap(); for (ClassReference classReference : classMap.values()) { if (implicitInheritance.containsKey(classReference.getHandle())) { throw new IllegalStateException(Already derived implicit classes for classReference.getName()); } Set allParents new HashSet(); getAllParents(classReference, classMap, allParents);//获取当前类的所有父类 implicitInheritance.put(classReference.getHandle(), allParents); } return new InheritanceMap(implicitInheritance); } ... ... private static void getAllParents(ClassReference classReference, Map classMap, Set allParents) { Set parents new HashSet(); if (classReference.getSuperClass() ! null) { parents.add(new ClassReference.Handle(classReference.getSuperClass()));//父类 } for (String iface : classReference.getInterfaces()) { parents.add(new ClassReference.Handle(iface));//接口类 } for (ClassReference.Handle immediateParent : parents) { //获取间接父类以及递归获取间接父类的父类 ClassReference parentClassReference classMap.get(immediateParent); if (parentClassReference null) { LOGGER.debug(No class id for immediateParent.getName()); continue; } allParents.add(parentClassReference.getHandle()); getAllParents(parentClassReference, classMap, allParents); } } ... ...}这一步的结果保存到了inheritanceMap.dat类直接父类间接父类com/sun/javaws/OperaPreferences$PreferenceSection$PreferenceEntryIteratorjava/lang/Object、java/util/Iteratorcom/sun/java/swing/plaf/windows/WindowsLookAndFeel$XPValuejava/lang/Object、javax/swing/UIDefaults$ActiveValueStep2 生成passthrough 数据流这里的passthrough数据流指的是每个方法的返回结果与方法参数的关系这一步生成的数据会在生成passthrough调用图时用到。以作者给出的demo为例先从宏观层面判断下FnConstant.invoke返回值与参数this(参数0因为序列化时类的所有成员我们都能控制所以所有成员变量都视为0参)、arg(参数1)的关系•与this的关系返回了this.value即与0参有关系•与arg的关系返回值与arg没有任何关系即与1参没有关系•结论就是FnConstant.invoke与参数0有关表示为FnConstant.invoke()-0Fndefault.invoke返回值与参数this(参数0)、arg(参数1)的关系•与this的关系返回条件的第二个分支与this.f有关系即与0参有关系•与arg的关系返回条件的第一个分支与arg有关系即与1参有关系•结论就是FnConstant.invoke与0参1参都有关系表示为Fndefault.invoke()-0、Fndefault.invoke()-1在这一步中gadgetinspector是利用ASM来进行方法字节码的分析主要逻辑是在类PassthroughDiscovery和TaintTrackingMethodVisitor中。特别是TaintTrackingMethodVisitor它通过标记追踪JVM虚拟机在执行方法时的stack和localvar并最终得到返回结果是否可以被参数标记污染。核心实现代码(TaintTrackingMethodVisitor涉及到字节码分析暂时先不看)public class PassthroughDiscovery { private static final Logger LOGGER LoggerFactory.getLogger(PassthroughDiscovery.class); private final Map methodCalls new HashMap(); private Map passthroughDataflow; public void discover(final ClassResourceEnumerator classResourceEnumerator, final GIConfig config) throws IOException { Map methodMap DataLoader.loadMethods();//load之前保存的methods.dat Map classMap DataLoader.loadClasses();//load之前保存的classes.dat InheritanceMap inheritanceMap InheritanceMap.load();//load之前保存的inheritanceMap.dat Map classResourceByName discoverMethodCalls(classResourceEnumerator);//查找一个方法中包含的子方法 List sortedMethods topologicallySortMethodCalls();//对所有方法构成的图执行逆拓扑排序 passthroughDataflow calculatePassthroughDataflow(classResourceByName, classMap, inheritanceMap, sortedMethods, config.getSerializableDecider(methodMap, inheritanceMap));//计算生成passthrough数据流涉及到字节码分析 } ... ... private List topologicallySortMethodCalls() { Map outgoingReferences new HashMap(); for (Map.Entry entry : methodCalls.entrySet()) { MethodReference.Handle method entry.getKey(); outgoingReferences.put(method, new HashSet(entry.getValue())); } // 对所有方法构成的图执行逆拓扑排序 LOGGER.debug(Performing topological sort...); Set dfsStack new HashSet(); Set visitedNodes new HashSet(); List sortedMethods new ArrayList(outgoingReferences.size()); for (MethodReference.Handle root : outgoingReferences.keySet()) { dfsTsort(outgoingReferences, sortedMethods, visitedNodes, dfsStack, root); } LOGGER.debug(String.format(Outgoing references %d, sortedMethods %d, outgoingReferences.size(), sortedMethods.size())); return sortedMethods; } ... ... private static void dfsTsort(Map outgoingReferences, List sortedMethods, Set visitedNodes, Set stack, MethodReference.Handle node) { if (stack.contains(node)) {//防止在dfs一条方法调用链中进入循环 return; } if (visitedNodes.contains(node)) {//防止对某个方法及子方法重复排序 return; } Set outgoingRefs outgoingReferences.get(node); if (outgoingRefs null) { return; } stack.add(node); for (MethodReference.Handle child : outgoingRefs) { dfsTsort(outgoingReferences, sortedMethods, visitedNodes, stack, child); } stack.remove(node); visitedNodes.add(node); sortedMethods.add(node); }}拓扑排序有向无环图(DAG)才有拓扑排序非 DAG 图没有拓扑排序。当有向无环图满足以下条件时•每一个顶点出现且只出现一次      •若A在序列中排在B的前面则在图中不存在从B到A的路径这样的图是一个拓扑排序的图。树结构其实可以转化为拓扑排序而拓扑排序 不一定能够转化为树。以上面的拓扑排序图为例用一个字典表示图结构 graph { a: [b,d], b: [c], d: [e,c], e: [c], c: [], }代码实现graph { a: [b,d], b: [c], d: [e,c], e: [c], c: [],}def TopologicalSort(graph): degrees dict((u, 0) for u in graph) for u in graph: for v in graph[u]: degrees[v] 1 #入度为0的插入队列 queue [u for u in graph if degrees[u] 0] res [] while queue: u queue.pop() res.append(u) for v in graph[u]: # 移除边即将当前元素相关元素的入度-1 degrees[v] - 1 if degrees[v] 0: queue.append(v) return resprint(TopologicalSort(graph)) # [a, d, e, b, c]但是在方法的调用中我们希望最后的结果是c、b、e、d、a这一步需要逆拓扑排序正向排序使用的BFS那么得到相反结果可以使用DFS。为什么在方法调用中需要使用逆拓扑排序呢这与生成passthrough数据流有关。看下面一个例子... public String parentMethod(String arg){ String vul Obj.childMethod(arg); return vul; }...那么这里arg与返回值到底有没有关系呢假设Obj.childMethod为... public String childMethod(String carg){ return carg.toString(); }...由于childMethod的返回值carg与有关那么可以判定parentMethod的返回值与参数arg是有关系的。所以如果存在子方法调用并传递了父方法参数给子方法时需要先判断子方法返回值与子方法参数的关系。因此需要让子方法的判断在前面这就是为什么要进行逆拓扑排序。从下图可以看出outgoingReferences的数据结构为{ method1:(method2,method3,method4), method5:(method1,method6), ...}而这个结构正好适合逆拓扑排序但是上面说拓扑排序时不能形成环但是在方法调用中肯定是会存在环的。作者是如何避免的呢在上面的dfsTsort实现代码中可以看到使用了stack和visitedNodesstack保证了在进行逆拓扑排序时不会形成环visitedNodes避免了重复排序。使用如下一个调用图来演示过程从图中可以看到有环med1-med2-med6-med1并且有重复的调用med3严格来说并不能进行逆拓扑排序但是通过stack、visited记录访问过的方法就能实现逆拓扑排序。为了方便解释把上面的图用一个树来表示对上图进行逆拓扑排序(DFS方式)从med1开始先将med1加入stack中此时stack、visited、sortedmethods状态如下med1还有子方法有继续深度遍历。将med2放入stack此时的状态med2有子方法吗有继续深度遍历。将med3放入stack此时的状态med3有子方法吗有继续深度遍历。将med7放入stack此时的状态med7有子方法吗没有从stack中弹出med7并加入visited和sortedmethods此时的状态回溯到上一层med3还有其他子方法吗有med8将med8放入stack此时的状态med8还有子方法吗没有弹出stack加入visited与sortedmethods此时的状态回溯到上一层med3还有其他子方法吗没有了弹出stack加入visited与sortedmethods此时的状态回溯到上一层med2还有其他子方法吗有med6将med6加入stack此时的状态med6还有子方法吗有med1med1在stack中不加入抛弃。此时状态和上一步一样回溯到上一层med6还有其他子方法吗没有了弹出stack加入visited和sortedmethods此时的状态回溯到上一层med2还有其他子方法吗没有了弹出stack加入visited和sortedmethods此时的状态回溯到上一层med1还有其他子方法吗有med3med3在visited中在抛弃。回溯到上一层med1还有其他子方法吗有med4将med4加入stack此时的状态med4还有其他子方法吗没有弹出stack加入visited和sortedmethods中此时的状态回溯到上一层med1还有其他子方法吗没有了弹出stack加入visited和sortedmethods中此时的状态(即最终状态)所以最后的逆拓扑排序结果为med7、med8、med3、med6、med2、med4、med1。生成passthrough数据流在calculatePassthroughDataflow中遍历了sortedmethods并通过字节码分析生成了方法返回值与参数关系的passthrough数据流。注意到下面的序列化决定器作者内置了三种JDK、Jackson、Xstream会根据具体的序列化决定器判定决策过程中的类是否符合对应库的反序列化要求不符合的就跳过•对于JDK(ObjectInputStream)类是否继承了Serializable接口•对于Jackson类是否存在0参构造器•对于Xstream类名能否作为有效的XML标签生成passthrough数据流代码... private static Map calculatePassthroughDataflow(Map classResourceByName, Map classMap, InheritanceMap inheritanceMap, List sortedMethods, SerializableDecider serializableDecider) throws IOException { final Map passthroughDataflow new HashMap(); for (MethodReference.Handle method : sortedMethods) {//依次遍历sortedmethods并且每个方法的子方法判定总在这个方法之前这是通过的上面的逆拓扑排序实现的。 if (method.getName().equals()) { continue; } ClassResourceEnumerator.ClassResource classResource classResourceByName.get(method.getClassReference().getName()); try (InputStream inputStream classResource.getInputStream()) { ClassReader cr new ClassReader(inputStream); try { PassthroughDataflowClassVisitor cv new PassthroughDataflowClassVisitor(classMap, inheritanceMap, passthroughDataflow, serializableDecider, Opcodes.ASM6, method); cr.accept(cv, ClassReader.EXPAND_FRAMES);//通过结合classMap、inheritanceMap、已判定出的passthroughDataflow结果、序列化决定器信息来判定当前method的返回值与参数的关系 passthroughDataflow.put(method, cv.getReturnTaint());//将判定后的method与有关系的污染点加入passthroughDataflow } catch (Exception e) { LOGGER.error(Exception analyzing method.getClassReference().getName(), e); } } catch (IOException e) { LOGGER.error(Unable to analyze method.getClassReference().getName(), e); } } return passthroughDataflow; }...最后生成了passthrough.dat类名方法名方法描述污点java/util/Collections$CheckedNavigableSettailSet(Ljava/lang/Object;)Ljava/util/NavigableSet;0,1java/awt/RenderingHintsput(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;0,1,2Step3 枚举 passthrough 调用图这一步和上一步类似gadgetinspector 会再次扫描全部的Java方法但检查的不再是参数与返回结果的关系而是方法的参数与其所调用的子方法的关系即子方法的参数是否可以被父方法的参数所影响。那么为什么要进行上一步的生成passthrough数据流呢由于这一步的判断也是在字节码分析中所以这里只能先进行一些猜测如下面这个例子... private MyObject obj; public void parentMethod(Object arg){ ... TestObject obj1 new TestObject(); Object obj2 obj1.childMethod1(arg); this.obj.childMethod(obj2); ... }...如果不进行生成passthrough数据流操作就无法判断TestObject.childMethod1的返回值是否会受到参数1的影响也就无法继续判断parentMethod的arg参数与子方法MyObject.childmethod的参数传递关系。作者给出的例子AbstractTableModel$ff19274a.hashcode与子方法IFn.invoke•AbstractTableModel$ff19274a.hashcode的this(0参)传递给了IFn.invoke的1参表示为0-IFn.invoke()1• 由于f是通过this.__clojureFnMap(0参)获取的而f又为IFn.invoke()的this(0参)即AbstractTableModel$ff19274a.hashcode的0参传递给了IFn.invoke的0参表示为0-IFn.invoke()0FnCompose.invoke与子方法IFn.invoke•FnCompose.invoked的arg(1参)传递给了IFn.invoke的1参表示为1-IFn.invoke()1•f1为FnCompose的属性(this0参)被做为了IFn.invoke的this(0参数)传递表示为0-IFn.invoke()1•f1.invoke(arg)做为一个整体被当作1参传递给了IFn.invoke由于f1在序列化时我们可以控制具体是IFn的哪个实现类所以具体调用哪个实现类的invoke也相当于能够控制即f1.invoke(arg)这个整体可以视为0参数传递给了IFn.invoke的1参(这里只是进行的简单猜测具体实现在字节码分析中可能也体现了作者说的合理的风险判断吧)表示为0-IFn.invoke()1在这一步中gadgetinspector也是利用ASM来进行字节码的分析主要逻辑是在类CallGraphDiscovery和ModelGeneratorClassVisitor中。在ModelGeneratorClassVisitor中通过标记追踪JVM虚拟机在执行方法时的stack和localvar最终得到方法的参数与其所调用的子方法的参数传递关系。生成passthrough调用图代码(暂时省略ModelGeneratorClassVisitor的实现涉及到字节码分析)public class CallGraphDiscovery { private static final Logger LOGGER LoggerFactory.getLogger(CallGraphDiscovery.class); private final Set discoveredCalls new HashSet(); public void discover(final ClassResourceEnumerator classResourceEnumerator, GIConfig config) throws IOException { Map methodMap DataLoader.loadMethods();//加载所有方法 Map classMap DataLoader.loadClasses();//加载所有类 InheritanceMap inheritanceMap InheritanceMap.load();//加载继承图 Map passthroughDataflow PassthroughDiscovery.load();//加载passthrough数据流 SerializableDecider serializableDecider config.getSerializableDecider(methodMap, inheritanceMap);//序列化决定器 for (ClassResourceEnumerator.ClassResource classResource : classResourceEnumerator.getAllClasses()) { try (InputStream in classResource.getInputStream()) { ClassReader cr new ClassReader(in); try { cr.accept(new ModelGeneratorClassVisitor(classMap, inheritanceMap, passthroughDataflow, serializableDecider, Opcodes.ASM6), ClassReader.EXPAND_FRAMES);//通过结合classMap、inheritanceMap、passthroughDataflow结果、序列化决定器信息来判定当前method参数与子方法传递调用关系 } catch (Exception e) { LOGGER.error(Error analyzing: classResource.getName(), e); } } } }最后生成了passthrough.datStep4 搜索可用的 source这一步会根据已知的反序列化漏洞的入口检查所有可以被触发的方法。例如在利用链中使用代理时任何可序列化并且是java/lang/reflect/InvocationHandler子类的invoke方法都可以视为source。这里还会根据具体的反序列化库决定类是否能被序列化。搜索可用的sourcepublic class SimpleSourceDiscovery extends SourceDiscovery { Override public void discover(Map classMap, Map methodMap, InheritanceMap inheritanceMap) { final SerializableDecider serializableDecider new SimpleSerializableDecider(inheritanceMap); for (MethodReference.Handle method : methodMap.keySet()) { if (Boolean.TRUE.equals(serializableDecider.apply(method.getClassReference()))) { if (method.getName().equals(finalize) method.getDesc().equals(()V)) { addDiscoveredSource(new Source(method, 0)); } } } // 如果类实现了readObject则传入的ObjectInputStream被认为是污染的 for (MethodReference.Handle method : methodMap.keySet()) { if (Boolean.TRUE.equals(serializableDecider.apply(method.getClassReference()))) { if (method.getName().equals(readObject) method.getDesc().equals((Ljava/io/ObjectInputStream;)V)) { addDiscoveredSource(new Source(method, 1)); } } } // 使用代理技巧时任何扩展了serializable and InvocationHandler的类会受到污染。 for (ClassReference.Handle clazz : classMap.keySet()) { if (Boolean.TRUE.equals(serializableDecider.apply(clazz)) inheritanceMap.isSubclassOf(clazz, new ClassReference.Handle(java/lang/reflect/InvocationHandler))) { MethodReference.Handle method new MethodReference.Handle( clazz, invoke, (Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;); addDiscoveredSource(new Source(method, 0)); } } // hashCode()或equals()是将对象放入HashMap的标准技巧的可访问入口点 for (MethodReference.Handle method : methodMap.keySet()) { if (Boolean.TRUE.equals(serializableDecider.apply(method.getClassReference()))) { if (method.getName().equals(hashCode) method.getDesc().equals(()I)) { addDiscoveredSource(new Source(method, 0)); } if (method.getName().equals(equals) method.getDesc().equals((Ljava/lang/Object;)Z)) { addDiscoveredSource(new Source(method, 0)); addDiscoveredSource(new Source(method, 1)); } } } // 使用比较器代理可以跳转到任何groovy Closure的call()/doCall()方法所有的args都被污染 // https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/Groovy1.java for (MethodReference.Handle method : methodMap.keySet()) { if (Boolean.TRUE.equals(serializableDecider.apply(method.getClassReference())) inheritanceMap.isSubclassOf(method.getClassReference(), new ClassReference.Handle(groovy/lang/Closure)) (method.getName().equals(call) || method.getName().equals(doCall))) { addDiscoveredSource(new Source(method, 0)); Type[] methodArgs Type.getArgumentTypes(method.getDesc()); for (int i 0; i methodArgs.length; i) { addDiscoveredSource(new Source(method, i 1)); } } } }...这一步的结果会保存在文件sources.dat中:类方法方法描述污染参数java/awt/color/ICC_Profilefinalize()V0java/lang/EnumreadObject(Ljava/io/ObjectInputStream;)V1Step5 搜索生成调用链这一步会遍历全部的source并在callgraph.dat中递归查找所有可以继续传递污点参数的子方法调用直至遇到sink中的方法。搜索生成调用链public class GadgetChainDiscovery { private static final Logger LOGGER LoggerFactory.getLogger(GadgetChainDiscovery.class); private final GIConfig config; public GadgetChainDiscovery(GIConfig config) { this.config config; } public void discover() throws Exception { Map methodMap DataLoader.loadMethods(); InheritanceMap inheritanceMap InheritanceMap.load(); Map methodImplMap InheritanceDeriver.getAllMethodImplementations( inheritanceMap, methodMap);//得到方法的所有子类方法实现(被子类重写的方法) final ImplementationFinder implementationFinder config.getImplementationFinder( methodMap, methodImplMap, inheritanceMap); //将方法的所有子类方法实现保存到methodimpl.dat try (Writer writer Files.newBufferedWriter(Paths.get(methodimpl.dat))) { for (Map.Entry entry : methodImplMap.entrySet()) { writer.write(entry.getKey().getClassReference().getName()); writer.write(\t); writer.write(entry.getKey().getName()); writer.write(\t); writer.write(entry.getKey().getDesc()); writer.write(\n); for (MethodReference.Handle method : entry.getValue()) { writer.write(\t); writer.write(method.getClassReference().getName()); writer.write(\t); writer.write(method.getName()); writer.write(\t); writer.write(method.getDesc()); writer.write(\n); } } } //方法调用mapkey为父方法value为子方法与父方法参数传递关系 Map graphCallMap new HashMap(); for (GraphCall graphCall : DataLoader.loadData(Paths.get(callgraph.dat), new GraphCall.Factory())) { MethodReference.Handle caller graphCall.getCallerMethod(); if (!graphCallMap.containsKey(caller)) { Set graphCalls new HashSet(); graphCalls.add(graphCall); graphCallMap.put(caller, graphCalls); } else { graphCallMap.get(caller).add(graphCall); } } //exploredMethods保存在调用链从查找过程中已经访问过的方法节点methodsToExplore保存调用链 Set exploredMethods new HashSet(); LinkedList methodsToExplore new LinkedList(); //加载所有sources并将每个source作为每条链的第一个节点 for (Source source : DataLoader.loadData(Paths.get(sources.dat), new Source.Factory())) { GadgetChainLink srcLink new GadgetChainLink(source.getSourceMethod(), source.getTaintedArgIndex()); if (exploredMethods.contains(srcLink)) { continue; } methodsToExplore.add(new GadgetChain(Arrays.asList(srcLink))); exploredMethods.add(srcLink); } long iteration 0; Set discoveredGadgets new HashSet(); //使用广度优先搜索所有从source到sink的调用链 while (methodsToExplore.size() 0) { if ((iteration % 1000) 0) { LOGGER.info(Iteration iteration , Search space: methodsToExplore.size()); } iteration 1; GadgetChain chain methodsToExplore.pop();//从队首弹出一条链 GadgetChainLink lastLink chain.links.get(chain.links.size()-1);//取这条链最后一个节点 Set methodCalls graphCallMap.get(lastLink.method);//获取当前节点方法所有子方法与当前节点方法参数传递关系 if (methodCalls ! null) { for (GraphCall graphCall : methodCalls) { if (graphCall.getCallerArgIndex() ! lastLink.taintedArgIndex) { //如果当前节点方法的污染参数与当前子方法受父方法参数影响的Index不一致则跳过 continue; } Set allImpls implementationFinder.getImplementations(graphCall.getTargetMethod());//获取子方法所在类的所有子类重写方法 for (MethodReference.Handle methodImpl : allImpls) { GadgetChainLink newLink new GadgetChainLink(methodImpl, graphCall.getTargetArgIndex());//新方法节点 if (exploredMethods.contains(newLink)) { //如果新方法已近被访问过了则跳过,这里能减少开销。但是这一步跳过会使其他链/分支链经过此节点时由于已经此节点被访问过了链会在这里断掉。那么如果这个条件去掉就能实现找到所有链了吗这里去掉会遇到环状问题造成路径无限增加... continue; } GadgetChain newChain new GadgetChain(chain, newLink);//新节点与之前的链组成新链 if (isSink(methodImpl, graphCall.getTargetArgIndex(), inheritanceMap)) {//如果到达了sink则加入discoveredGadgets discoveredGadgets.add(newChain); } else { //新链加入队列 methodsToExplore.add(newChain); //新节点加入已访问集合 exploredMethods.add(newLink); } } } } } //保存搜索到的利用链到gadget-chains.txt try (OutputStream outputStream Files.newOutputStream(Paths.get(gadget-chains.txt)); Writer writer new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { for (GadgetChain chain : discoveredGadgets) { printGadgetChain(writer, chain); } } LOGGER.info(Found {} gadget chains., discoveredGadgets.size()); }...作者给出的sink方法private boolean isSink(MethodReference.Handle method, int argIndex, InheritanceMap inheritanceMap) {if (method.getClassReference().getName().equals(java/io/FileInputStream) method.getName().equals()) {return true; }if (method.getClassReference().getName().equals(java/io/FileOutputStream) method.getName().equals()) {return true; }if (method.getClassReference().getName().equals(java/nio/file/Files) (method.getName().equals(newInputStream) || method.getName().equals(newOutputStream) || method.getName().equals(newBufferedReader) || method.getName().equals(newBufferedWriter))) {return true; }if (method.getClassReference().getName().equals(java/lang/Runtime) method.getName().equals(exec)) {return true; }/* if (method.getClassReference().getName().equals(java/lang/Class) method.getName().equals(forName)) { return true; } if (method.getClassReference().getName().equals(java/lang/Class) method.getName().equals(getMethod)) { return true; } */// If we can invoke an arbitrary method, thats probably interesting (though this doesnt assert that we// can control its arguments). Conversely, if we can control the arguments to an invocation but not what// method is being invoked, we dont mark that as interesting.if (method.getClassReference().getName().equals(java/lang/reflect/Method) method.getName().equals(invoke) argIndex 0) {return true; }if (method.getClassReference().getName().equals(java/net/URLClassLoader) method.getName().equals(newInstance)) {return true; }if (method.getClassReference().getName().equals(java/lang/System) method.getName().equals(exit)) {return true; }if (method.getClassReference().getName().equals(java/lang/Shutdown) method.getName().equals(exit)) {return true; }if (method.getClassReference().getName().equals(java/lang/Runtime) method.getName().equals(exit)) {return true; }if (method.getClassReference().getName().equals(java/nio/file/Files) method.getName().equals(newOutputStream)) {return true; }if (method.getClassReference().getName().equals(java/lang/ProcessBuilder) method.getName().equals() argIndex 0) {return true; }if (inheritanceMap.isSubclassOf(method.getClassReference(), new ClassReference.Handle(java/lang/ClassLoader)) method.getName().equals()) {return true; }if (method.getClassReference().getName().equals(java/net/URL) method.getName().equals(openStream)) {return true; }// Some groovy-specific sinksif (method.getClassReference().getName().equals(org/codehaus/groovy/runtime/InvokerHelper) method.getName().equals(invokeMethod) argIndex 1) {return true; }if (inheritanceMap.isSubclassOf(method.getClassReference(), new ClassReference.Handle(groovy/lang/MetaClass)) Arrays.asList(invokeMethod, invokeConstructor, invokeStaticMethod).contains(method.getName())) {return true; }return false; }对于每个入口节点来说其全部子方法调用、孙子方法调用等等递归下去就构成了一棵树。之前的步骤所做的就相当于生成了这颗树而这一步所做的就是从根节点出发找到一条通往叶子节点的道路使得这个叶子节点正好是我们所期望的sink方法。gadgetinspector对树的遍历采用的是广度优先(BFS)而且对于已经检查过的节点会直接跳过这样减少了运行开销避免了环路但是丢掉了很多其他链。这个过程看起来就像下面这样通过污点的传递最终找到从source-sink的利用链注targ表示污染参数的index0-1这样的表示父方法的0参传递给了子方法的1参参考•https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf•https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains-wp.pdf•https://www.youtube.com/watch?vwPbW6zQ52w8•https://mp.weixin.qq.com/s/RD90-78I7wRogdYdsB-UOg 往 期 热 门(点击图片跳转)  觉得不错点个“在看”哦
http://www.zqtcl.cn/news/486715/

相关文章:

  • 做jsp网站用哪些软件下载如何利用网站赚钱
  • 注册网站域名需要什么湘潭公司做网站
  • 一个网站如何优化企业资质查询平台
  • 模板网站为什么做不了优化山西网络网站建设销售公司
  • 建设什么网站可以赚钱设计本网站是用什么做的
  • 荆州市网站建设策划师
  • 苏州中国建设银行招聘信息网站中国企业登记网
  • 网站服务器的重要性新闻软文范例大全
  • 茶叶网站建设一般的风格加大志愿服务网站建设
  • 湖州医院网站建设方案网页游戏知乎
  • 以网站建设为开题报告临海门户网站住房和城乡建设规划局
  • 河南省大型项目建设办公室网站wordpress置顶功能
  • 奉化网站建设三合一网站建设多少钱
  • wordpress文章页怎么调用网站图片wordpress菜单锚点定位
  • 网站建设运营合作合同网站建设英文合同
  • wordpress chrome插件开发图片式网站利于做优化吗
  • 如何做好品牌网站建设策划app要有网站做基础
  • 横沥网站建设公司wordpress运行php
  • 南皮网站建设价格网络推广这个工作好做吗
  • 长安大学门户网站是谁给做的网站排名logo怎么做
  • 襄樊做网站做网站做网站
  • 百度做网站续费费用网站开发的可行性
  • 电子商务网站建设效益分析如何才能做好品牌网站建设策划
  • 能打开各种网站的浏览器app文章目录wordpress
  • 网站注册页面html中国建设招标网网站
  • 云南网站设计海外直购网站建设方案书范文
  • 网站视频小程序商城多少钱
  • 美耐皿 技术支持 东莞网站建设如何将网站指向404
  • 如何做网站的维护和推广wordpress首页在哪里修改
  • 网站建设公司在哪里宣传网站群系统建设的目的