caido-plugin-test/packages/backend/dist/index.js
2025-05-31 15:22:25 +09:00

190 lines
7.2 KiB
JavaScript

// 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<any>) {}
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
};