A widespread vulnerability in Java environments leaves thousands of businesses seriously exposed. Despite lacking a clever name — ala Heartbleed, Shellshock, and POODLE — this Java security issue is poised to allow hackers to do damage across the Internet. Any application that accepts serialized Java objects is likely vulnerable, even if a framework or library is responsible and not your custom code. And there’s no easy way to protect applications from this Java security issue en-masse. That means it will take organizations a long time to find and fix all the different variants of this vulnerability.
Serialization is a way that developers turn their data structures into a stream of bytes for transport or storage. Deserialization is the reverse process that happens when the data is received. There have been Java security issues with serialization for a long time - going back to 2010 and before. See http://www.ibm.com/developerworks/library/se-lookahead for the full history. Just recently, exploits were published for a bunch of different Java environments -- which unfortunately appears to be the only way to make something happen in security.
These flaws in Java security are serious and can be used to effect a complete remote command execution -- total host takeover on any application that accepts serialized objects.
In Java, reading a Data object from a serialized stream is as simple as:
ObjectInputStream in = new ObjectInputStream( inputStream );
return (Data)in.readObject();
The security issue is that there’s no way to know what you’re deserializing before you’ve decoded it. So an attacker can serialize a bunch of malicious objects and send them to your application. Once you call readObject(), it’s too late. The attackers malicious objects have already been instantiated, and have taken over your entire server. In some ways it’s like an XXE problem, where an attacker can use a malicious DOCTYPE to generate attacks during XML parsing. But in this case, there’s no easy way to turn off DOCTYPE processing.
What's needed to address this java security issue is a way to allow deserialization, but make it impossible for attackers to create instances of arbitrary classes. Something like this:
// read in the serialized object SAFELY
List<Class<?>> safeClasses = Arrays.asList( BitSet.class, ArrayList.class );
Data data = safeReadObject( Data.class, safeClasses, 10, 50, inputStream );
This allows the developer to specify the return type and a list of the classes that they expect to show up in serialized objects. The prevents the attacker from loading classes that allow him to execute attacks. This also empowers the developer to limit the input to a maximum of 10 embedded objects and 50 bytes of input. This way, the attacker can't send an arbitrarily large serialized object to blow up your JVM by exhausting memory.
When something unauthorized shows up, we throw a SecurityException and block the attempt. Turns out that it’s not too difficult to implement all these defenses. We just need to overload a bit of the ObjectInputStream implementation.
Here’s a method that you can use to replace calls to readObject:
/**
* A method to replace the unsafe ObjectInputStream.readObject() method built into Java. This method
* checks to be sure the classes referenced are safe, the number of objects is limited to something sane,
* and the number of bytes is limited to a reasonable number. The returned Object is also cast to the
* specified type.
*
* @param type Class representing the object type expected to be returned
* @param safeClasses List of Classes allowed in serialized object being read
* @param maxObjects long representing the maximum number of objects allowed inside the serialized object being read
* @param maxBytes long representing the maximum number of bytes allowed to be read from the InputStream
* @param in InputStream containing an untrusted serialized object
* @return Object read from the stream (cast to the Class of the type parameter)
* @throws IOException
* @throws ClassNotFoundException
*/
@SuppressWarnings("unchecked")
public static T safeReadObject(Class<?> type, List<Class<?>> safeClasses, long maxObjects, long maxBytes, InputStream in ) throws IOException, ClassNotFoundException {
// create an input stream limited to a certain number of bytes
InputStream lis = new FilterInputStream( in ) {
private long len = 0;
public int read() throws IOException {
int val = super.read();
if (val != -1) {
len++;
checkLength();
}
return val;
}
public int read(byte[] b, int off, int len) throws IOException {
int val = super.read(b, off, len);
if (val > 0) {
len += val;
checkLength();
}
return val;
}
private void checkLength() throws IOException {
if (len > maxBytes) {
throw new SecurityException("Security violation: attempt to deserialize too many bytes from stream. Limit is " + maxBytes);
}
}
};
// create an object input stream that checks classes and limits the number of objects to read
ObjectInputStream ois = new ObjectInputStream( lis ) {
private int objCount = 0;
boolean b = enableResolveObject(true);
protected Object resolveObject(Object obj) throws IOException {
if ( objCount++ > maxObjects ) throw new SecurityException( "Security violation: attempt to deserialize too many objects from stream. Limit is " + maxObjects );
Object object = super.resolveObject(obj);
return object;
}
protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
Class<?> clazz = super.resolveClass(osc);
if (
clazz.isArray() ||
clazz.equals(type) ||
clazz.equals(String.class) ||
Number.class.isAssignableFrom(clazz) ||
safeClasses.contains(clazz)
) return clazz;
throw new SecurityException("Security violation: attempt to deserialize unauthorized " + clazz);
}
};
// use the protected ObjectInputStream to read object safely and cast to T
return (T)ois.readObject();
}
This method overrides the resolveClass() method in ObjectInputStream and adds checks to make sure that any class loaded as part of the deserialization process is either not exploitable or contained in the whitelist of safe classes. As an alternative, you could also blacklist the classes you don’t know — the ones known to be used in exploits — but this approach is doomed to failure. There are simply too many so-called “gadgets” on the classpath of a modern application to effectively list all the possible exploits.
This simple check protects your applications without radical changes to your code. The first step is to search your code to find all the places that you’re vulnerable and update them with calls to safeReadObject(). However, this approach doesn't protect you if the unsafe calls to readObject() are in your libraries or frameworks. The best way to implement full protection is with instrumentation, and tomorrow, all Contrast users will receive an update that does just that. You’ll be notified all the places across your application portfolio where you’re exposed due to deserializing untrusted data!
How to protect your applications from the Java Serialization Vulnerability with Contrast Security
Contrast Security has a solution that uses our patented, powerful application security instrumentation platform to find and fix this Java security issue both quickly and accurately. Contrast Security can identify this problem during development using Contrast Assess, our IAST (Interactive Application Security Testing) approach. And Contrast can also protect applications in production using Contrast Protect, RASP (Runtime Application Self Protection), features to patch the problem immediately or generate security alerts, with no re-coding necessary. One Contrast agent protects all applications on a server, so it's easy to protect your entire portfolio against serialization attacks as well as a broad array of other vulnerabilities and attacks.
FIGURE 1. The Contrast Dashboard shows your application security
environment in real time. This application is under attack and it has
some critical vulnerabilities as well.
See How our Java Serialization Vulnerability Solution can Help Protect your Applications in Real-Time