2828import java .util .List ;
2929import java .util .Map ;
3030import java .util .Set ;
31- import java .util .function .Predicate ;
31+ import java .util .function .Consumer ;
3232
33- import org .apache .commons .lang3 .exception .ExceptionUtils ;
3433import org .junit .jupiter .api .Assertions ;
3534import org .junit .jupiter .api .Test ;
3635import org .springframework .security .core .authority .SimpleGrantedAuthority ;
4847import org .springframework .security .oauth2 .core .oidc .user .OidcUserAuthority ;
4948import org .springframework .security .web .authentication .WebAuthenticationDetails ;
5049
50+ import tools .jackson .core .StreamReadFeature ;
51+ import tools .jackson .databind .exc .InvalidDefinitionException ;
52+ import tools .jackson .databind .exc .InvalidTypeIdException ;
53+ import tools .jackson .databind .json .JsonMapper ;
5154
55+
56+ // NOTE: Originally designed for Jackson v2
57+ // Was improved in v3 so that for example Object objects are no longer deserialized into
58+ // potentially dangerous classes.
59+ // Please note that Jackson v3 does not cover all possible options (as it depends on the available classes)
60+ // and therefore this test is still present
5261class DefaultOAuth2CookieRememberMeAuthSerializerTest
5362{
5463 private static final String ACCESS_TOKEN = "dummy" ;
@@ -70,70 +79,63 @@ class DefaultOAuth2CookieRememberMeAuthSerializerTest
7079 private static final String NAME = "A B" ;
7180 private static final String EMAIL = "a.b@xdev-software.de" ;
7281
82+ private static final Consumer <JsonMapper .Builder > SERIALIZER_JSON_MAPPER_CUSTOMIZER =
83+ b -> b .enable (StreamReadFeature .INCLUDE_SOURCE_IN_LOCATION );
84+
85+ static DefaultOAuth2CookieRememberMeAuthSerializer createSerializer (final boolean safe )
86+ {
87+ return new DefaultOAuth2CookieRememberMeAuthSerializer (safe , SERIALIZER_JSON_MAPPER_CUSTOMIZER );
88+ }
89+
7390 @ Test
7491 void defaultSerializationWorks ()
7592 {
7693 Assertions .assertDoesNotThrow (() -> this .serializeAndDeserialize (
77- new DefaultOAuth2CookieRememberMeAuthSerializer ( ),
94+ createSerializer ( true ),
7895 Map .of ()));
7996 }
8097
8198 @ SuppressWarnings ("checkstyle:VisibilityModifier" )
8299 static Set <String > attackSuccessIds = new HashSet <>();
83100
84101 @ Test
85- void performAttackWithoutProtectionSuccess ()
102+ void performAttackWithoutDedicatedProtection ()
86103 {
87- final String id = "success" ;
88- this .performAttack (
89- new DefaultOAuth2CookieRememberMeAuthSerializer (false ),
90- id ,
91- List .of (
92- "Unable to deserialize" ::equals ,
93- s -> s .contains (AttackPerformer .SUCCESS_INDICATOR ),
94- AttackPerformer .SUCCESS_INDICATOR ::equals )
104+ final String id = "default" ;
105+ final var serializer = createSerializer (false );
106+ final InvalidDefinitionException ex = Assertions .assertThrows (
107+ InvalidDefinitionException .class ,
108+ () -> this .performAttack (serializer , id )
95109 );
96- Assertions .assertTrue (attackSuccessIds .contains (id ));
110+ Assertions .assertTrue (ex .getMessage ().startsWith ("Configured `PolymorphicTypeValidator`" )
111+ && ex .getMessage ().contains ("denies resolution of all subtypes of base type "
112+ + "`java.lang.Object` as using too generic base type "
113+ + "can open a security hole without checks on subtype: "
114+ + "please configure a custom `PolymorphicTypeValidator` for this use case" ));
115+ Assertions .assertFalse (attackSuccessIds .contains (id ));
97116 }
98117
99118 @ Test
100119 void performAttackFails ()
101120 {
102121 final String id = "fail" ;
103- this . performAttack (
104- new DefaultOAuth2CookieRememberMeAuthSerializer (),
105- id ,
106- List . of (
107- "Unable to deserialize" :: equals ,
108- s -> s .startsWith ("Could not resolve type id" )
109- && s . contains ( "$AttackPerformer' as a subtype of `java.lang.Object`: "
110- + " Configured `PolymorphicTypeValidator`") ));
122+ final var serializer = createSerializer ( true );
123+ final InvalidTypeIdException ex = Assertions . assertThrows (
124+ InvalidTypeIdException . class ,
125+ () -> this . performAttack ( serializer , id )
126+ );
127+ Assertions . assertTrue ( ex . getMessage () .startsWith ("Could not resolve type id" )
128+ && ex . getMessage (). contains (
129+ "$AttackPerformer' as a subtype of `java.lang.Object`: Configured `PolymorphicTypeValidator`" ));
111130 Assertions .assertFalse (attackSuccessIds .contains (id ));
112131 }
113132
114133 void performAttack (
115134 final DefaultOAuth2CookieRememberMeAuthSerializer serializer ,
116- final String id ,
117- final List <Predicate <String >> exceptionCauseMessageChecks )
135+ final String id )
118136 {
119137 final Map <String , Object > data = Map .of ("test" , new AttackPerformer (id ));
120- final IllegalStateException ex = Assertions .assertThrows (
121- IllegalStateException .class ,
122- () -> this .serializeAndDeserialize (serializer , data ));
123- Throwable current = ex ;
124-
125- int i = 0 ;
126- while (current != null )
127- {
128- Assertions .assertTrue (
129- i < exceptionCauseMessageChecks .size ()
130- && exceptionCauseMessageChecks .get (i ).test (current .getMessage ()),
131- "Invalid exception message at nested=" + i + ": " + current .getMessage ()
132- + "\n SOURCE EXCEPTION:\n "
133- + ExceptionUtils .getStackTrace (ex ));
134- current = current .getCause ();
135- i ++;
136- }
138+ this .serializeAndDeserialize (serializer , data );
137139 }
138140
139141 public static class AttackPerformer
0 commit comments