Ok, so here's where I discuss the how I've addressed serialization's
security issues:
From the source code:
// These two settings are to prevent DOS attacks.
private static final int MAX_ARRAY_LEN = 32768;
private static final int MAX_OBJECT_CACHE = 65664;
The output stream implementation automatically resets the cach, when it
exceeds a certain size, while the ObjectInputStream throws a
StreamCorruptedException if OBJECT_CACHE is exceeded.
@Override
public void writeObjectOverride(Object obj) throws IOException {
d.writeObject(obj);
// This is where we check the number of Object's cached and
// reset if we're getting towards half our limit.
// One day this limit will become too small.
if (d.numObjectsCached > 32768) reset();
}
A new annotation
@AtomicSerial is provided for classes that implement Serializable and
want to validate their invariants atomically. That is if invariants
aren't satisfied, the object is not created and doesn't exist so cannot
be used as an attack vector.
An annotation was chosen since it is not inherited by subclasses.
Classes that implement Serializable, still continue to do so as usual,
the serial form doesn't change.
Use of the new streams is determined by MethodConstraints.
Child classes in the stream that don't implement @AtomicSerial (apart
from instances of Throwable, immutable Object versions of primitives and
MarshalledObject) require DeSerializationPermission.
Because circular links are prohibited, when a conventional Serializable
object is constructed, it is not published until after it's readObject,
readObjectNoData or readResolve method has been called, so an attacker
is prevented from obtaining a reference to it through the stream. These
object's are still vulnerable to finalizer attacks, however that
requires downloaded code and the intent is for this to be used to
establish proxy trust prior to granting DownloadPermission.
@AtomicSerial objects are constructed using a constructor:
SomeObject(GetArg arg) throws IOException{
super(check(arg));
Object somefield = arg.get("somefield", null);
}
Where GetArg extends GetFields, but provides caller sensitive methods,
to ensure different classes can't see each others field namespaces. To
construct a GetArg instance requires
SerializablePermission("enableSubclassImplementation").
The lowest extension class in the inheritance heirarchy is called first,
it checks its invariants using a static method, each class in the
inheritance heirarchy checks it's invariants before Object's default
constructor is called, if any invariant checks fail the object is not
constructed.
This is also generic parameter and final field friendly.
@ReadInput (used to annotate a static method that returns ReadObject (an
interface) are both provided to allow classes to gain direct access to
the stream, as they would in readObject(ObjectInputStream in), but
without requiring their own object instance.
Conventional classes are deserialized using a best effort approach, by
trying each constructor on the lowest extension class, using default
values. Not all Serializable object's can be constructed.
The intent is for services, proxy's and discovery to use DOS safe code
to serialize their state while establishing trust.
Collection, List, Set, SortedSet, Map and SortedMap are replaced in the
ObjectOutputStream implementation with DOS attack safe immutable
versions backed by arrays, these are functional package private
implementations, intended as parameters, so implementations of
@AtomicSerial are required to pass them as parameters to their preferred
collection implementation. @AtomicSerial implementations are encouraged
to use checked collections, see java.util.Collections.
Conventional serialization can be used for trusted connections.
@AtomicSerial is very easy to implement, and much easier to evolve than
default serialization, some users may wish to use it anyway (it's also
final field friendly), but it is definitely intended to be optional.
Regards,
Peter.
On 8/02/2015 6:11 PM, Peter Firmstone wrote:
Thanks Dan, hopefully I don't dissapoint.
... So continuing on, another benefit of secure Serialization, if
you're a lookup service, you don't need to authenticate your clients,
you can deal with anyone and your not subject to DOS attacks, other
than more conventional attacks unrelated to java.
I've been investigating Serialization security, identifying issues and
considering how to deal with them. I think everyone is aware
Serialization has security pitfalls, if I fail to mention an issue
you're aware of, please let me know.
At first I thought it was just an issue of limiting the classes that
could be deserialized and that's relatively easily done, for example,
ArrayList reads an integer from the stream, then uses it to create an
array, without sanity checking it first. Well that's easy, just
prevent ArrayList and a bunch of others from deserializing...
Not so fast, ObjectInputStream also creates arrays, without sanity
checking, blockdata also has similar issues during byte array creation.
So you only need send an Integer.MAX_VALUE to bring the jvm to it's
knees, and if that doesn't do it, send a multi dimension array with a
few more. It requires very little data from the sender, just a few
bytes and a couple of integers.
In addition ObjectInputStream caches objects, so they don't have to be
re-serialized, but if ObjectOutputStream doesn't perform a reset,
well you can figure that out without my help.
But wait there's more...
During deserialization, Serializable objects are instantiated by
calling a zero arg constructor of the first non Serializable super
class, this partially constructed Object, an instance of a
Serializable child class, without any invariant checks or validation,
is then allocated an integer handle and published, an attacker is now
free to obtain a reference to the unconstructed object simply by
inserting a reference handle into the stream.
At this time the ProtectionDomain's of the classes in the object's
heirarchy are not present in the AccessControlContext, which is why
attackers in the past have been able to creat ClassLoader instances
and download code, when someone has deserialized into privileged
context (that renders DownloadPermission useless, because the attacker
can work around it).
After unsafe publication, the fields are read in from the stream from
super class to child class and set.
Now I don't blame the developers back in the day, they had deadlines
and targets to achieve, but the market has changed significantly
since then and the issue needs addressing.
The good news is, the Serialization stream protocol has all the
components necessary to create a secure ObjectInputStream.
For example, the stream cache can be reset periodically, this also
means we can place a limit on the number of objects cached, and
require ObjectOuputStream to call reset. If it doesn't and the object
cache exceeds our limit, StreamCorruptedException.
For array length, again we can impose limits, and throw
StreamCorruptedException if the limit is exceeded.
The collections themselves aren't hard to solve, simply replace all
collection types with a safe replacement in ObjectOutputStream and
require DeSerializationPermission for known insecure objects.
Circular links, can't be supported using existing deserialization
mechanisms.
Does this last point matter?
My implementation of ObjectInputStream doesn't support circular links
and passes all lookup service tests. In this case circular links are
replaced with null. To support circular links safely would require
some cooperation from the classes participating in deserialization.
Construction during deserialization is the last challenge, many
existing Serializable classes don't have public constructors or zero
arg constructors, even though implementing Serializable is equivalent
to a public constructor.
... to be continued, until next time.
Cheers,
Peter.
On 5/02/2015 2:38 AM, Dan Rollo wrote:
Very interesting. Looking forward to the next episode.
On Feb 4, 2015, at 9:11 AM, dev-digest-h...@river.apache.org wrote:
to be continued...