I’m using Spring Security Keycloak adapter 12.0.1 and Memcached for session replication. When the session is loaded from Memcached, classes from the Keyclaok adapter are deserialized. The read method of the class KeycloakSecurityContext
contains
DelegatingSerializationFilter.builder()
.addAllowedClass(KeycloakSecurityContext.class)
.setFilter(in);
…which sets an ObjectFilter
for the current ObjectInputStream
.
I found out that I have to set the system property jdk.serialSetFilterAfterRead
to true, otherwise the exception filter can not be set after an object has been read
is thrown and DelegatingSerializationFilter
complains that it is not possible to set the object filter. The result is that no object filter is applied at all and the log is spammed with warnings.
After applying jdk.serialSetFilterAfterRead
, I encountered that the ObjectInputStream
with the memcached attributes contains further classes that are not set as allowed classes from the DelegatingSerializationFilter
, e.g.:
org.springframework.security.web.savedrequest.DefaultSavedRequest
The result is that these classes are rejected during the serialization process.
So my question is this: Does anybody know how to configure the object filter so that serialization is working correctly?
3
Answers
What is happening
Your problem is that memcached session handler is deserializing a bunch of different classes at the same time. That is, in the same
ObjectInputStream
. This happens because your (Tomcat? or whatever application server, it doesn’t matter) session is composed by a number of different objects; so memcached serializes them one-by-one into its storage and then, the same way, they are deserialized back into your application. This works great until you deserialize an object of a bitchy class,KeycloakSecurityContext
, that introduces an evil filter 😱 in yourObjectInputStream
. Link to the evil class.What happens after that is that the keycloak class is allowed so it keeps being deserialized correctly, but now magically all other classes are not allowed because keycloak excludes all of them by adding "!*" at the end of its filter. Link to the filter.
Now you have a strong headache 😵 and you’re thinking "Enough bullshit, how do I fix this?". I understand and I’ve got a bad headache as well, keep reading.
Solution
There are multiple solutions to this issue (good news! 😀) depending on how deep you dare (😣) go and change things in your application.
The correct™ solution is to make sure that
KeycloakSecurityContext
objects (and any other class that introduces such filters) are deserialized in their own streams rather than in the same, common, stream. Now, I’m not an expert in this memcached session handler thing so I don’t know how much control you actually have of the whole session deserialization process; but I’m quite confident that it should be possible to hack into it.If this, for some reason, is not possible, you need to override the filter in
KeycloakSecurityContext
by extending the class. If you’re choosing this path (fly, you fools 😈) you have to play with the filter and decide what to do. The approach that I would think it’s most correct™ in this case is to completely remove the filter thing fromKeycloakSecurityContext
and then add a filter at the application level where you define all the classes and the packages that you allow for deserialization in your application.Link on how to do it.
Another approach, a bit hacky but more immediate, is to add all the relevant classes in the filter in the class extending
KeycloakSecurityContext
(seriously, don’t do this).Bonus good-citizen point
Some might even argue that this is a bug, or let’s say a missing functionality, in
memcached-session-manager
. Might be worth opening an issue there and see what they think about it.https://github.com/magro/memcached-session-manager
Now the hardcore stuff
In-depth practical explanation of the problem for nerds who have nothing to do.
Think about the following scenario: you have two types of classes,
A
andB
, and both classes areSerializable
.Class
A
is a simple, a bit naïve, class and doesn’t add any custom filters in itsreadObject
method. In the sameObjectInputStream
you deserialize the objects:a1, a2, a3, a4
. All instances of classA
.All objects are deserialized correctly, fuck yeah! 🐒
Class
B
, instead, has a very mean filter in itsreadObject
that allows only objects of the same classB
to be deserialized in the currentObjectInputStream
.Now we read the following objects in a stream:
a1, b1, a2, b2
.a1
is deserialized correctly, there is no filter right now in ourObjectInputStream
, great!b1
is deserialized correctly, cool; butb1
is also a little bit of a bitch (being an instance of ClassB
) so it sets a new filter for our beloved stream and class B is, from now on, the only class allowed for deserializationa2
and it is not deserialized (whaaaat 😩), becauseb1
, in the previous step, set a filter that doesn’t include class A and this is all happening in the sameObjectInputStream
instanceThe code implemented in
KeycloakSecurityContext
and other related classes was included in order to mitigate a bug associated with the CVECVE-2020-1714
:The solution tries to address the above-mentioned vulnerability by implementing a custom
ObjectInputFilter
.As indicated in the bug description, the idea behind the Java serialization filtering mechanism is to prevent deserialization vulnerabilities that can lead to remote code execution that can cause a security issue to the application.
In many situations, like when dealing with sessions, several objects, those stored in the session following the example, are serialized and will be later deserialized together, in other words, they will be written to the same
ObjectOutputStream
and then they will be read from the sameObjectInputStream
.When a
ObjectInputFilter
is applied, which can be done at several levels – process, application and specificObjectInputStream
– only the objects that satisfy the configured filter pattern will be deserialized; depending on the pattern itself, the rest will be either rejected or the decision will be delegated to a process-wide filter if one exists.Please, consider the following example, where
A
,B
,C
andD
are classes, and a filterA;D;!*
is applied over an hypothetical object input stream, represented by the top marble diagram line, being the bottom one the deseriaization result after the filter is applied:This pattern can be defined in terms of modules, packages and/or individual classes, and can be set as the
jdk.serialFilter
system property, or by editing thejava.security
properties file.You can create custom filters as well. They are implemented using the API provided by
ObjectInputFilter
, and allow for a more granular serialization control because they can be specific to a specificObjectInputStream
.Please, see the relevant Oracle Serialization documentation for more information.
The
Keycloak
serialization filters use the utility classDelegatingSerializationFilter
.In the implementation provided, the filter is applied inside the
KeycloakSecurityContext
readObject
method:As a consequence, in order to work it properly, as you pointed out, it is necessary to define the
jdk.serialSetFilterAfterRead
system property astrue
when running your program.This filter will be always applied unless a previous, non process-wide filter (see the section labeled Setting a Process-Wide Custom Filter in the Oracle documentation), has been already applied to the
ObjectInputStream
; it will be guaranteed by thesetObjectInputFilter
method and it is checked by theDelegatingSerializationFilter
class as well:In other words, the only way to avoid a custom filter to be applied is to provide first an initial filter over the target
ObjectInputStream
, the one that contains your session data this time. As indicated in the docs:The way in which the creation of this initial filter should be accomplished is highly dependent on the code that is actually dealing with the deserialization functionality.
In your use case, you probably are using
memcached-session-manager
, either the original version or some more updated projects in Github.In a normal use case, the sessions in
memcached-session-manager
are mainly handled by the code defined inMemcachedSessionService
.This class uses
TranscoderService
for handling the Java serialization stuff.TranscoderService
in turn delegates that responsability to a proper implementation ofTranscoderFactory
andSessionAttributesTranscoder
.JavaSerializationTranscoderFactory
and the associated classJavaSerializationTranscoder
are the default implementations of these interfaces.Please, pay attention to the
deserializeAttributes
method ofJavaSerializationTranscoder
, it defines the logic for session deserialization:As you can see, the problem is that the session information, represented by the input byte array, can contain several attributes, and all of them are deserialized from the same
ObjectInputStream
. Once theKeycloak
ObjectInputFilter
is applied on thisObjectInputStream
, as you indicated, it will reject the rest of classes that are not allowed by the filter. The reason is thatDelegatingSerializationFilter
append a final!*
to the filter pattern that is being constructed, excluding everything but the explicitly provided class and text based patterns (well, and the classes ofjava.util.*
to allow collections).In order to avoid this problem, try providing your own implementation of
SessionAttributesTranscoder
, and include a method something similar todeserializeAttributes
but defining an initial filter over the constructedObjectInputStream
.For example (please, forgive to define the whole class, you can probably reuse the code of
JavaSerializationTranscoder
in a certain way):Now, define a custom
TranscoderFactory
. Let’s reuse the code of the classJavaSerializationTranscoderFactory
this time:Place this classes on your classpath, with the rest of libraries from
memcached-session-manager
, and provide a convenient value for thetranscoderFactoryClass
memcached-session-manager
configuration property, as indicated in the docs:I have no the ability to test the solution, although a simple test seems to work properly:
Person
andAddress
two simple POJOs. Pay attention to the definitionPerson
readObject
method:Please, be aware that the filter is there in order to prevent security flaws: as also suggested in the code comments, it would be advisable to improve the logic implemented in the
fixDeserialization
to restrict in some way the possible classes that your session is supposed to contain instead of use the wildcard*
as in the example.In fact, this functionality can be included in the
memcached-session-manager
library, probably by defining some kind of configuration property,serialFilter
, for instance, which value, a valid filter pattern, should be provided to the indicated underlying Java deserialization mechanisms.I created a fork of the project: it is still a WIP, but please, see this commit, I hope you get the idea. I will try to pull a request to the forked repo once finished.
I know this question is regarding Memcached, but I came here with the exact same error using Spring Session w/ Hazelcast. I will post what I found in case it helps others.
Basically, Spring Session Hazelcast 2.4.2 provides
HazelcastSessionSerializer
. I was previously using 2.3.2 which does not have this class, and defaults to Java serialization, resulting in the "rejected" error when deserializingDefaultSavedRequest
.The configuration example includes a few new lines to set this serializer:
This resolved the issue for me.