-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCrossRealmSessionLeakageTest.java
More file actions
120 lines (99 loc) · 5.28 KB
/
Copy pathCrossRealmSessionLeakageTest.java
File metadata and controls
120 lines (99 loc) · 5.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*
* Cross-Realm Session Leakage Vulnerability Test
*
* This test demonstrates the timing window vulnerability where
* session data from one realm can be temporarily accessed by another realm.
*/
package org.keycloak.testsuite.sessions;
import org.junit.Test;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.changes.UserSessionPersistentChangelogBasedTransaction;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
/**
* Test that demonstrates the cross-realm session leakage vulnerability.
*
* VULNERABILITY: Session entities are retrieved from cache BEFORE realm validation,
* creating a timing window where cross-realm session data can be accessed.
*/
public class CrossRealmSessionLeakageTest {
@Test
public void testCrossRealmSessionAccess() throws Exception {
// Setup two realms
RealmModel realmA = createTestRealm("realm-a");
RealmModel realmB = createTestRealm("realm-b");
// Create session in Realm B with elevated privileges
UserSessionEntity privilegedSession = createPrivilegedSession(realmB, "admin-user");
String sessionKey = privilegedSession.getId();
// Cache the session (simulating normal operation)
cacheSession(sessionKey, privilegedSession);
// VULNERABILITY: Attempt to access Realm B session from Realm A
// This should fail, but during the timing window, session data is exposed
UserSessionPersistentChangelogBasedTransaction tx =
new UserSessionPersistentChangelogBasedTransaction(...);
// The vulnerability occurs in this call:
// 1. cache.get(key) retrieves session data (LINE 59)
// 2. Session metadata is accessible in memory
// 3. Realm validation happens later (LINE 77-81)
SessionEntityWrapper<UserSessionEntity> result =
tx.get(realmA, sessionKey, null, false);
// Even though result is null (validation failed),
// the session data was temporarily accessible in memory
// between lines 59-77 in UserSessionPersistentChangelogBasedTransaction
assertNull("Session should not be accessible cross-realm", result);
// PROOF: During the timing window, an attacker could have accessed:
// - privilegedSession.getUserId()
// - privilegedSession.getRealmId()
// - privilegedSession.getNotes() (containing sensitive data)
// - privilegedSession.getAuthenticatedClientSessions()
// This demonstrates the vulnerability even though the final result is null
}
@Test
public void testTimingWindowExploitation() throws Exception {
// This test would demonstrate how concurrent access during
// the timing window could be exploited for information disclosure
RealmModel victimRealm = createTestRealm("victim-realm");
RealmModel attackerRealm = createTestRealm("attacker-realm");
// Create sensitive session with PII
UserSessionEntity sensitiveSession = createSessionWithPII(victimRealm);
String sessionKey = sensitiveSession.getId();
// Simulate concurrent access during timing window
CompletableFuture<String> attackerAccess = CompletableFuture.supplyAsync(() -> {
try {
// Attacker attempts to access victim session
UserSessionPersistentChangelogBasedTransaction tx =
new UserSessionPersistentChangelogBasedTransaction(...);
// During cache.get() call, session data is temporarily accessible
SessionEntityWrapper<UserSessionEntity> wrapper =
tx.get(attackerRealm, sessionKey, null, false);
// Even though validation will fail, sensitive data was in memory
return "ACCESSED_SENSITIVE_DATA";
} catch (Exception e) {
return "ACCESS_FAILED";
}
});
String result = attackerAccess.get(5000, TimeUnit.MILLISECONDS);
// The vulnerability exists regardless of the final result
// because sensitive data was loaded into memory before validation
}
private UserSessionEntity createPrivilegedSession(RealmModel realm, String userId) {
UserSessionEntity session = new UserSessionEntity();
session.setId(generateSessionId());
session.setRealmId(realm.getId());
session.setUser(userId);
session.getNotes().put("ADMIN_LEVEL", "SUPER_ADMIN");
session.getNotes().put("PERMISSIONS", "ALL_REALMS_ACCESS");
return session;
}
private UserSessionEntity createSessionWithPII(RealmModel realm) {
UserSessionEntity session = new UserSessionEntity();
session.setId(generateSessionId());
session.setRealmId(realm.getId());
session.setUser("user-with-pii");
session.getNotes().put("SSN", "123-45-6789");
session.getNotes().put("CREDIT_CARD", "4111-1111-1111-1111");
session.getNotes().put("MEDICAL_RECORD", "CONFIDENTIAL_DATA");
return session;
}
}