[Back] basis serialization, serialization and serialization proxy attack

table of Contents

一、what、why、how 序列化
二、JDK 序列化并不简单
三、序列化攻击
四、序列化代理模式
参考
复制代码

A, what, why, how serialization

** What is serialized? ** Simply, it is an object into a byte stream according to the coding sequence of the protocol, the reverse process is called deserialization. For example we have a common JSON serialization:

public class A {
	   private int x = 1;
	   private String y = "2";
}
复制代码

After serialized to JSON:

{
	"x" : 1,
	"y" : "2"
}
复制代码

** Why serialize? ** Simply, it is an object for transmission, compression storage space, and do nothing to language. Communication both sides just to serialize / deserialize serialized in accordance with the agreement, without attention to what the other using a language becomes.

** how serialization? ** many existing serialization protocol, such as xml, json, fastjson, protobuf, protostuff like. In addition, there is regular contact with JDK Java serialization (JDK serialization is unable to cross language). The serialization length of each non Benpian focus, not repeat them, are interested can look at several popular serialization protocol comparison .

Two, JDK is not simple serialization

JDK serialization is as simple as increasing the class declaration implements Serializable, the serialization interface can be realized. But just because a simple, abused can often be seen everywhere. In fact JDK serialization is complex, and in order to serialize the cost of long-term.

why?

First, reduce the flexibility of the class, the evolution of the class is limited . Once the sequence can be achieved, and its serialized byte stream like a part of the API, you must always support serialization / de-serialization, if one of the communicating parties modify the structure of the class and release out, there will be no compatible, which led to the error.

In addition, there is a class in the serial version UID (serial version UID), when deserialized, it will first be confirmed according to the version UID, if inconsistent versions of the deserialization fails, throw InvalidClassException exception. If there is no display provides the UID, will be combined with the class name at runtime, all public and protected members of the name of the computer generation. This is the reason for providing recommendations to achieve UID can be serialized show, because if one of these communication party on the class adds a variable or method-independent, it will also make UID implicitly generated inconsistent, leading to abnormalities not compatible, Secondly, the calculation implicitly generated is not a small overhead.

Second, BUG and increase the likelihood of security breaches occur . Anti-serialization mechanism like an "implicit constructor", if not adopted certain measures to ensure that can easily be exploited by attackers, constructed in violation of the "real constructor," the constraints.

Third, as the realization of a new version of the class can be serialized publication, associated with an increased testing burden .

Third, the sequence of attack

Since the sequence of the object is converted into a byte stream, the byte stream deserialized restore object, whether it be an intermediate byte stream forged? The answer is yes:

For example, our object is Periodto limit the members of the date variable startmust be in endbefore:

public class Period implements Serializable {
    private static final long serialVersionUID = 4647424730390249716L;
    private Date start;
    private Date end;
    public Period(Date start, Date end) {
        if (start.after(end)) {
            throw new IllegalArgumentException();
        }
        this.start = start;
        this.end = end;
    }
    @Override
    public String toString() {
        return "PeriodA{" +
                "start=" + start +
                ", end=" + end +
                '}';
    }
}

复制代码

Now we forged the following byte stream:

public class SerializeTest {
    private static final byte[] serializedForm = new byte[] {
            (byte)0xac, (byte)0xed, 0x00, 0x05, 0x73, 0x72, 0x00, 0x06,
            0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x40, 0x7e, (byte)0xf8,
            0x2b, 0x4f, 0x46, (byte)0xc0, (byte)0xf4, 0x02, 0x00, 0x02,
            0x4c, 0x00, 0x03, 0x65, 0x6e, 0x64, 0x74, 0x00, 0x10, 0x4c,
            0x6a, 0x61, 0x76, 0x61, 0x2f, 0x75, 0x74, 0x69, 0x6c, 0x2f,
            0x44, 0x61, 0x74, 0x65, 0x3b, 0x4c, 0x00, 0x05, 0x73, 0x74,
            0x61, 0x72, 0x74, 0x71, 0x00, 0x7e, 0x00, 0x01, 0x78, 0x70,
            0x73, 0x72, 0x00, 0x0e, 0x6a, 0x61, 0x76, 0x61, 0x2e, 0x75,
            0x74, 0x69, 0x6c, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x68, 0x6a,
            (byte)0x81, 0x01, 0x4b, 0x59, 0x74, 0x19, 0x03, 0x00, 0x00,
            0x78, 0x70, 0x77, 0x08, 0x00, 0x00, 0x00, 0x66, (byte)0xdf,
            0x6e, 0x1e, 0x00, 0x78, 0x73, 0x71, 0x00, 0x7e, 0x00, 0x03,
            0x77, 0x08, 0x00, 0x00, 0x00, (byte)0xd5, 0x17, 0x69, 0x22,
            0x00, 0x78
    };
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Period p = (Period) deserialize(serializedForm);
        System.out.println(p);
    }
    public static Object deserialize(byte[] sf) {
        try {
            InputStream is = new ByteArrayInputStream(sf);
            ObjectInputStream ois = new ObjectInputStream(is);
            return ois.readObject();
        } catch (Exception e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}
复制代码

By deserialization results:

PeriodA{start=Sat Jan 02 04:00:00 CST 1999, end=Mon Jan 02 04:00:00 CST 1984}
复制代码

Already appeared deserialization said earlier this "implicit constructor" build an object that violates our constitution constraint relationship startlater than endthis can be very dangerous for the program. As for how the byte stream forgery, you can take a look at "Java Object Serialization Specification", including a description of the serialization format.

Therefore, effetive java repeatedly stressed, to achieve serializable class must write readObjectmethods, and to ensure that constraints.

private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException{
   stream.defaultReadObject();
   if (start.after(end)) {
     throw new IllegalArgumentException();
   }
}
复制代码

However, although you can still do that through forged byte stream to break the constraints, in addition to providing a stream of bytes is valid Period objects, plus two additional references, references to these two variables are examples of two members, so that after instantiation by two arbitrary reference operation target. The following presentation:

public class MutablePeriod {
    // 有效period对象
    public final Period period;
    // 两个额外的引用
    public final Date start;
    public final Date end;

    public MutablePeriod() {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bos);
            out.writeObject(new Period(new Date(), new Date()));
            // 附上额外引用
            byte[] ref = { 0x71, 0, 0x7e, 0, 5 };
            bos.write(ref);
            ref[4] = 4;
            bos.write(ref);

            ObjectInputStream in = new ObjectInputStream(
                    new ByteArrayInputStream(bos.toByteArray()));
            period = (Period) in.readObject();
            start = (Date) in.readObject();
            end = (Date) in.readObject();
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

    public static void main(String[] args) {
        MutablePeriod mp = new MutablePeriod();
        Period p = mp.period;
        Date pEnd = mp.end;
      
        pEnd.setYear(78);
        System.out.println(p);
        pEnd.setYear(69);
        System.out.println(p);
    }
}
复制代码

The results are:

PeriodA{start=Fri Aug 23 12:26:53 CST 2019, end=Wed Aug 23 12:26:53 CST 1978}
PeriodA{start=Fri Aug 23 12:26:53 CST 2019, end=Sat Aug 23 12:26:53 CST 1969}
复制代码

The above problem occurs that the root cause readObject copy protection method is not carried out, i.e. configured, new member variables of the object, and to deserialize objects out of the copy protection member variables to the new object, which, two additional reference attacker modification is not instantiated object variables:

 private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException {
   stream.defaultReadObject();
   // 保护性拷贝
   start = new Date(start.getTime());
   end = new Date(end.getTime());
   if (start.after(end)) {
     throw new IllegalArgumentException();
   }
}
复制代码

It should be noted that the copy protection prior to testing constraints, and so on instead of using a shallow copy clone mode.

In addition, there are more commonly used method, that is serialized proxy mode, see below.

Fourth, the serialization proxy mode

Sequence of very simple agents, i.e. put on a serializable class private static shell, the shell is called serialization agent, which has a constructor, i.e. parameters of the proxy class constructor, when copying is configured parameters of the proxy class. By providing a sequence of time writeReplace, in fact, the sequence of the proxy class, and readObjectthe interface direct rejection of sequences, only proxy deserialization. By providing the proxy class readResolvedeserialize the proxy class. See in particular the code:

public class Period implements Serializable {

    private static final long serialVersionUID = 4647424730390249716L;
    private Date start;
    private Date end;

    public Period(Date start, Date end) {
        if (start.after(end)) {
            throw new IllegalArgumentException();
        }
        this.start = start;
        this.end = end;
    }

    @Override
    public String toString() {
        return "PeriodA{" +
                "start=" + start +
                ", end=" + end +
                '}';
    }
    public Date getStart() {
        return start;
    }
    public Date getEnd() {
        return end;
    }

    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
        // 不允许直接反序列化,只能通过反序列化代理实例化
        throw new InvalidObjectException("只允许通过代理反序列化");
    }
    private Object writeReplace() {
        // 序列化代理
        return new SerializeProxy(this);
    }
		
    // 序列化代理类
    private class SerializeProxy implements Serializable {
        private final Date start;
        private final Date end;
        // 通过构造复制代理类变量
        public SerializeProxy(Period period) {
            this.start = period.getStart();
            this.end = period.getEnd();
        }
        // 反序列化为被代理类
        private Object readResolve() {
            return new Period(start, end);
        }
    }

}
复制代码

reference

[1] Effective Java Chapter XI

Guess you like

Origin juejin.im/post/5d5fb302f265da03d871c746