From 2d991770fa0e1c2382285358706c5052c146bb6a Mon Sep 17 00:00:00 2001 From: Ads Dawson <104169244+GangGreenTemperTatum@users.noreply.github.com> Date: Tue, 29 Jul 2025 21:58:10 -0400 Subject: [PATCH 1/3] feat: http method checker workflow --- .../src/http-method-checker/README.md | 16 ++ .../src/http-method-checker/definition.json | 172 ++++++++++++++++++ .../src/http-method-checker/manifest.json | 10 + 3 files changed, 198 insertions(+) create mode 100644 packages/workflows/src/http-method-checker/README.md create mode 100644 packages/workflows/src/http-method-checker/definition.json create mode 100644 packages/workflows/src/http-method-checker/manifest.json diff --git a/packages/workflows/src/http-method-checker/README.md b/packages/workflows/src/http-method-checker/README.md new file mode 100644 index 0000000..b91d035 --- /dev/null +++ b/packages/workflows/src/http-method-checker/README.md @@ -0,0 +1,16 @@ +# HTTP Method Checker + +## Author +- **Name:** [Ads Dawson](https://github.com/GangGreenTemperTatum) + +## Description +This workflow dynamically probes HTTP endpoints by sending an `OPTIONS` request to detect discrepancies between the HTTP methods advertised by the server and the method originally used. It helps identify misconfigured HTTP methods exposed on the target. + +## Features +- Sends `OPTIONS` request to the same host and path as the original request. +- Parses `Allow` and `Access-Control-Allow-Methods` headers. +- Flags requests where the original HTTP method is not listed in the allowed methods. +- Creates findings with detailed metadata for easier triage. + +## Usage +Import this workflow into Caido and run it during your HTTP request analysis. It automatically sends the probe and generates findings if discrepancies are found. diff --git a/packages/workflows/src/http-method-checker/definition.json b/packages/workflows/src/http-method-checker/definition.json new file mode 100644 index 0000000..f000590 --- /dev/null +++ b/packages/workflows/src/http-method-checker/definition.json @@ -0,0 +1,172 @@ +{ + "description": "Passively probes each proxied HTTP request with a subsequent HTTP OPTIONS request and reports when the OPTIONS response includes allowed methods beyond the original request method.", + "edition": 2, + "graph": { + "edges": [ + { + "source": { + "exec_alias": "exec", + "node_id": 2 + }, + "target": { + "exec_alias": "exec", + "node_id": 3 + } + }, + { + "source": { + "exec_alias": "exec", + "node_id": 3 + }, + "target": { + "exec_alias": "exec", + "node_id": 1 + } + }, + { + "source": { + "exec_alias": "exec", + "node_id": 0 + }, + "target": { + "exec_alias": "exec", + "node_id": 5 + } + }, + { + "source": { + "exec_alias": "true", + "node_id": 5 + }, + "target": { + "exec_alias": "exec", + "node_id": 2 + } + } + ], + "nodes": [ + { + "alias": "on_intercept_request", + "definition_id": "caido/on-intercept-request", + "display": { + "x": -10, + "y": -110 + }, + "id": 0, + "inputs": [], + "name": "On intercept request", + "version": "0.1.0" + }, + { + "alias": "passive_end", + "definition_id": "caido/passive-end", + "display": { + "x": -10, + "y": 260 + }, + "id": 1, + "inputs": [], + "name": "Passive End", + "version": "0.1.0" + }, + { + "alias": "options_probe", + "definition_id": "caido/http-code-js", + "display": { + "x": -10, + "y": 80 + }, + "id": 2, + "inputs": [ + { + "alias": "request", + "value": { + "data": "$on_intercept_request.request", + "kind": "ref" + } + }, + { + "alias": "code", + "value": { + "data": "export async function run({ request, response }, sdk) {\n if (!request) return;\n\n const orig = request.getMethod();\n const spec = request.toSpec();\n spec.setMethod(\"OPTIONS\");\n\n // Send the dynamic OPTIONS probe to the same host/path\n const probe = await sdk.requests.send(spec);\n\n if (probe.response) {\n const headers = probe.response.getHeaders();\n const allow = headers[\"allow\"]?.[0] || \"\";\n const cors = headers[\"access-control-allow-methods\"]?.[0] || \"\";\n const methods = (allow || cors).split(/\\s*,\\s*/);\n\n if (methods.length && !methods.includes(orig)) {\n const dedupeKey = ${request.getHost()}|${request.getPath()}|${orig}|${methods.join(\",\")};\n await sdk.findings.create({\n title: \"Extraneous HTTP methods exposed\",\n description: OPTIONS listed methods [${methods.join(\", \")}], original: ${orig},\n request,\n response: probe.response,\n dedupeKey\n });\n }\n }\n}", + "kind": "string" + } + } + ], + "name": "OPTIONS Request Probe", + "version": "0.1.0" + }, + { + "alias": "create_finding", + "definition_id": "caido/finding-create", + "display": { + "x": -10, + "y": 170 + }, + "id": 3, + "inputs": [ + { + "alias": "title", + "value": { + "data": "Create Finding", + "kind": "string" + } + }, + { + "alias": "reporter", + "value": { + "data": "HTTP Method Checker", + "kind": "string" + } + }, + { + "alias": "dedupe_key", + "value": { + "data": "`${host}|${path}|${origMethod}|${allowHeader}`, ", + "kind": "string" + } + }, + { + "alias": "request", + "value": { + "data": "$options_probe.data", + "kind": "ref" + } + }, + { + "alias": "description", + "value": { + "data": "$options_probe.data", + "kind": "ref" + } + } + ], + "name": "Create Finding", + "version": "0.1.0" + }, + { + "alias": "in_scope", + "definition_id": "caido/in-scope", + "display": { + "x": -10, + "y": -10 + }, + "id": 5, + "inputs": [ + { + "alias": "request", + "value": { + "data": "$on_intercept_request.request", + "kind": "ref" + } + } + ], + "name": "In Scope", + "version": "0.1.0" + } + ] + }, + "id": "ce244451-262d-4c01-89d0-358b8fc58a2b", + "kind": "passive", + "name": "HTTP Method Checker" +} \ No newline at end of file diff --git a/packages/workflows/src/http-method-checker/manifest.json b/packages/workflows/src/http-method-checker/manifest.json new file mode 100644 index 0000000..0df4ad2 --- /dev/null +++ b/packages/workflows/src/http-method-checker/manifest.json @@ -0,0 +1,10 @@ +{ + "author": { + "name": "Ads Dawson" + }, + "url": "https://github.com/caido-community/workflows/packages/workflows/http-method-checker/README.md", + "description": "Detects HTTP methods exposed by dynamically probing endpoints with OPTIONS requests.", + "id": "http-method-checker", + "name": "HTTP Method Checker", + "version": "0.1.0" +} From 8b2082bc53b98f5a6e2fde4b59e3445779aec4e8 Mon Sep 17 00:00:00 2001 From: Ads Dawson <104169244+GangGreenTemperTatum@users.noreply.github.com> Date: Wed, 30 Jul 2025 16:55:07 -0400 Subject: [PATCH 2/3] chore: refactor definition to a external javascript.ts file --- .../src/http-method-checker/definition.json | 2 +- .../src/http-method-checker/javascript.ts | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 packages/workflows/src/http-method-checker/javascript.ts diff --git a/packages/workflows/src/http-method-checker/definition.json b/packages/workflows/src/http-method-checker/definition.json index f000590..838b66a 100644 --- a/packages/workflows/src/http-method-checker/definition.json +++ b/packages/workflows/src/http-method-checker/definition.json @@ -88,7 +88,7 @@ { "alias": "code", "value": { - "data": "export async function run({ request, response }, sdk) {\n if (!request) return;\n\n const orig = request.getMethod();\n const spec = request.toSpec();\n spec.setMethod(\"OPTIONS\");\n\n // Send the dynamic OPTIONS probe to the same host/path\n const probe = await sdk.requests.send(spec);\n\n if (probe.response) {\n const headers = probe.response.getHeaders();\n const allow = headers[\"allow\"]?.[0] || \"\";\n const cors = headers[\"access-control-allow-methods\"]?.[0] || \"\";\n const methods = (allow || cors).split(/\\s*,\\s*/);\n\n if (methods.length && !methods.includes(orig)) {\n const dedupeKey = ${request.getHost()}|${request.getPath()}|${orig}|${methods.join(\",\")};\n await sdk.findings.create({\n title: \"Extraneous HTTP methods exposed\",\n description: OPTIONS listed methods [${methods.join(\", \")}], original: ${orig},\n request,\n response: probe.response,\n dedupeKey\n });\n }\n }\n}", + "data": "", "kind": "string" } } diff --git a/packages/workflows/src/http-method-checker/javascript.ts b/packages/workflows/src/http-method-checker/javascript.ts new file mode 100644 index 0000000..734dbc3 --- /dev/null +++ b/packages/workflows/src/http-method-checker/javascript.ts @@ -0,0 +1,33 @@ +/** + * @param {HttpInput} input + * @param {SDK} sdk + * @returns {MaybePromise} + */ +export async function run({ request, response }, sdk) { + if (!request) return; + + const orig = request.getMethod(); + const spec = request.toSpec(); + spec.setMethod("OPTIONS"); + + // Send the dynamic OPTIONS probe to the same host/path + const probe = await sdk.requests.send(spec); + + if (probe.response) { + const headers = probe.response.getHeaders(); + const allow = headers["allow"]?.[0] || ""; + const cors = headers["access-control-allow-methods"]?.[0] || ""; + const methods = (allow || cors).split(/\s*,\s*/); + + if (methods.length && !methods.includes(orig)) { + const dedupeKey = `${request.getHost()}|${request.getPath()}|${orig}|${methods.join(",")}`; + await sdk.findings.create({ + title: "Extraneous HTTP methods exposed", + description: `OPTIONS listed methods [${methods.join(", ")}], original: ${orig}`, + request, + response: probe.response, + dedupeKey + }); + } + } +} \ No newline at end of file From ea6aea0fc184fa08d1240cfdcc7abb49d5b5c9c5 Mon Sep 17 00:00:00 2001 From: bebiksior Date: Wed, 30 Jul 2025 22:05:24 +0100 Subject: [PATCH 3/3] fix validation --- packages/workflows/src/http-method-checker/definition.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/workflows/src/http-method-checker/definition.json b/packages/workflows/src/http-method-checker/definition.json index 838b66a..d41857a 100644 --- a/packages/workflows/src/http-method-checker/definition.json +++ b/packages/workflows/src/http-method-checker/definition.json @@ -70,7 +70,7 @@ "version": "0.1.0" }, { - "alias": "options_probe", + "alias": "javascript", "definition_id": "caido/http-code-js", "display": { "x": -10, @@ -169,4 +169,4 @@ "id": "ce244451-262d-4c01-89d0-358b8fc58a2b", "kind": "passive", "name": "HTTP Method Checker" -} \ No newline at end of file +}