// packages/backend/src/controller/PKCECheck.ts import { RequestSpec } from "caido:utils"; var PKCECheck = class { async test(sdk, req) { const method = req.getMethod(); if (method !== "GET") { sdk.console.log("[PKCEDowngradeCheck] Not a GET request. Skipping."); return false; } const query = req.getQuery(); const searchParams = new URLSearchParams(query); const requiredKeys = ["client_id", "response_type", "code_challenge", "code_challenge_method"]; if (!requiredKeys.every((key) => searchParams.has(key))) { sdk.console.log("[PKCEDowngradeCheck] Required PKCE parameters missing. Skipping."); return false; } const url = req.getUrl(); const isOpenID = searchParams.get("scope")?.includes("openid") || url.includes("id_token"); const methodVal = searchParams.get("code_challenge_method"); const challengeVal = searchParams.get("code_challenge"); if (!methodVal || !challengeVal) { sdk.console.log("[PKCEDowngradeCheck] code_challenge or method missing. Skipping."); await sdk.findings.create({ title: isOpenID ? "[WARN] OpenID Flow PKCE Parameters Missing" : "[WARN] OAuth2 Flow PKCE Parameters Missing", description: `PKCE parameters are missing or incomplete for ${url}. This may indicate a misconfiguration.`, request: req, reporter: "PKCE Checker" }); return false; } if (methodVal === "plain") { sdk.console.log("[PKCEDowngradeCheck] code_challenge_method is 'plain'. Skipping."); await sdk.findings.create({ title: isOpenID ? "[WARN] OpenID Flow PKCE Method is 'plain'" : "[WARN] OAuth2 Flow PKCE Method is 'plain'", description: `PKCE method is set to 'plain' for ${url}. This may indicate a downgrade vulnerability.`, request: req, reporter: "PKCE Checker" }); return false; } searchParams.delete("code_challenge"); searchParams.delete("code_challenge_method"); const downgradedQuery = searchParams.toString(); const scheme = req.getUrl().startsWith("https") ? "https" : "http"; const downgradedUrl = `${scheme}://${req.getHost()}:${req.getPort()}${req.getPath()}?${downgradedQuery}`; sdk.console.log(`${req.getHost()} Original URL: ` + url); sdk.console.log(`${req.getHost()} Downgraded URL: ` + downgradedUrl); try { const spec = new RequestSpec(downgradedUrl); spec.setBody(req.getBody()); for (const [key, value] of Object.entries(req.getHeaders())) { if (Array.isArray(value)) { spec.setHeader(key, value.join(", ")); } else { spec.setHeader(key, value); } } spec.setHost(req.getHost()); spec.setMethod(req.getMethod()); spec.setPath(req.getPath()); spec.setQuery(downgradedQuery); spec.setTls(req.getTls()); spec.setPort(req.getPort()); let sendDowngradedRequest = await sdk.requests.send(spec); if (sendDowngradedRequest.response) { let domain = spec.getHost(); let port = spec.getPort(); let path2 = spec.getPath(); let query2 = spec.getQuery(); let id = sendDowngradedRequest.response.getId(); let code = sendDowngradedRequest.response.getCode(); sdk.console.log(`REQ ${id}: ${domain}:${port}${path2}${query2} received a status code of ${code}`); } if (sendDowngradedRequest.response?.getCode() === 302) { await sdk.findings.create({ title: isOpenID ? "[CRITICAL] OpenID Flow PKCE Downgrade Vulnerability" : "[CRITICAL] OAuth2 Flow PKCE Downgrade Vulnerability", description: `The request to ${url} is vulnerable to a PKCE downgrade attack. This may indicate a configuration error.`, request: req, reporter: "PKCE Checker" }); } } catch (err) { sdk.console.error(`PKCE downgrade check failed for ${url}: ${String(err)}`); } sdk.console.log("[PKCEDowngradeCheck] No PKCE downgrade detected."); return false; } }; // packages/backend/src/controller/redirectUriCheck.ts import { promises as fs } from "fs"; import * as path from "path"; import os from "os"; var redirectUriCheck = class { requestMap = /* @__PURE__ */ new Map(); // constructor(private sdk: SDK) {} async onRequest(sdk, req) { try { const urlString = req.getUrl(); const url = new URL(urlString); sdk.console.log(`[OAuthPlugin] Intercepted request: ${urlString}`); if (!url.pathname.includes("/authorize") && !url.pathname.includes("/auth")) return; const params = new URLSearchParams(url.search); const redirectUri = params.get("redirect_uri"); if (!redirectUri) return; const reqId = req.getId(); this.requestMap.set(reqId, redirectUri); const clientId = params.get("client_id") ?? "(missing)"; const responseType = params.get("response_type") ?? "(missing)"; const isScan = params.has("scan"); if (isScan) return; const output = { original_url: urlString, client_id: clientId, redirect_uri: redirectUri, response_type: responseType }; try { const filePath = path.join(os.tmpdir(), "oauth-fuzz-input.json"); await fs.writeFile(filePath, JSON.stringify(output, null, 2)); } catch (err) { await sdk.findings.create({ title: "[fs] Write Failed", description: `Could not write to file: ${err}`, request: req, reporter: "oauth-open-redirect-detector" }); } await sdk.findings.create({ title: "[ ] OAuth2 Authorization Request Collected", description: `client_id: ${clientId} redirect_uri: ${redirectUri} response_type: ${responseType}`, request: req, reporter: "oauth-open-redirect-detector" }); } catch (err) { sdk.console.error(`Error in onRequest: ${err}`); } } async onResponse(sdk, req, resp) { try { const reqId = req.getId(); const url = new URL(req.getUrl()); const status = resp.getCode(); const location = resp.getHeader("location")?.[0]; const params = new URLSearchParams(url.search); const isScan = params.has("scan"); if (!isScan) { this.requestMap.delete(reqId); return; } if (status >= 300 && status < 400 && location) { const redirectUri = this.requestMap.get(reqId) ?? "(unknown)"; await sdk.findings.create({ title: "[+] Redirect URI Misconfiguration Detected", description: `Status: ${status} Location: ${location} Request URL: ${url.href} Redirect URI: ${redirectUri}`, request: req, reporter: "oauth-open-redirect-detector" }); } this.requestMap.delete(reqId); } catch (err) { sdk.console.error(`Error in onResponse: ${err}`); } } }; // packages/backend/src/index.ts var pkceCheckController = new PKCECheck(); var redirectUriCheckController = new redirectUriCheck(); function init(sdk) { sdk.events.onInterceptRequest( async (sdk2, req) => { await redirectUriCheckController.onRequest(sdk2, req); } ); sdk.events.onInterceptResponse( async (sdk2, req, resp) => { await pkceCheckController.test(sdk2, req); await redirectUriCheckController.onResponse(sdk2, req, resp); } ); } export { init };