From dfeab629d771a8b3f7802b5f8dca7696606fe661 Mon Sep 17 00:00:00 2001 From: seungyeoncherry Date: Sat, 31 May 2025 11:49:11 +0900 Subject: [PATCH 1/2] [Add] Scope Detection --- .../backend/src/controller/scopeDetection.ts | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 packages/backend/src/controller/scopeDetection.ts diff --git a/packages/backend/src/controller/scopeDetection.ts b/packages/backend/src/controller/scopeDetection.ts new file mode 100644 index 0000000..9c8610e --- /dev/null +++ b/packages/backend/src/controller/scopeDetection.ts @@ -0,0 +1,89 @@ +import type { DefineAPI, SDK } from "caido:plugin"; +import { RequestSpec } from "caido:utils"; + +const scan = async ( // scan 함수 정의 + sdk: SDK, + url: string +): Promise<{ data: string }> => { + sdk.console.log(`들어온 url : ${url}`); // url이 잘 들어왔는지 확인함요 + + // url이 string이 아니고 , 값이 없거나 그럴 때 유효한 값 넣으라고 출력. + if (!url || typeof url !== "string") { + sdk.console.log("이상한 url 입력함."); + return { data: "알맞은 URL을 입력하세요." }; + } + + try { + const spec = new RequestSpec(url); // url에 GET 요청 보낼거긔. + spec.setMethod("GET"); + spec.setHeader("User-Agent", "Caido Scanner"); + spec.setHeader("Accept", "*/*"); + sdk.console.log(`요청 URL: ${url}`); + + const res = await sdk.requests.send(spec); // 요청 보내고 응답 받음. + sdk.console.log('[SCAN] 응답 :', res); + sdk.console.log(`[SCAN] 요청 성공:${(res as any).status}`); + sdk.console.log(`[SCAN] body: ${(res as any).body ? (res as any).body.toString().substring(0, 100) : "없음"}`); + + const html = (res as any).body ? (res as any).body.toString() : ""; + + // ]*href="([^"]+)"[^>]*>/gi; + const anchors: string[] = []; + let match; + while ((match = anchorRegex.exec(html)) !== null) { // html에서 a href 찾아 배열에 저장함. + if (typeof match[1] === "string") { + anchors.push(match[1]); + } + } + sdk.console.log(`찾아진 a href 개수: ${anchors.length}`); + + // 5. scope 탐지 + const results: string[] = []; + anchors.forEach((href) => { // 추출한 a href 링크 하나씩 검사드감. + try { + const absHref = new URL(href, url).href; // 상대경로라면 url 기준으로 절대 URL 바꿔줌줌 + sdk.console.log(`[SCAN] 절대 URL 변환: ${href} -> ${absHref}`); // + + if (/oauth|authorize|login|accounts|auth/i.test(absHref)) { // url에 이런 OAuth 키워드가 있는지 필터링. + let u: URL; + try { + u = new URL(absHref); // 필터링된 url을 url 객체로 파싱. 정식 url인 경우 변수 u에 저장. + } catch (err) { // 파싱 실패하면 + sdk.console.log( + `URL 파싱 실패 : ${absHref} (${err instanceof Error ? err.message : err})` + ); + return; + } + + try { + const scope = u.searchParams.get("scope"); // url에 scope있긔?scope값 가져와. + if (scope && /all|\*/i.test(scope)) { // scope가 존재하고 all, *있다면. + results.push(`위험한 scope 발견: ${scope}\n -> ${absHref}`); // results에 경고 메시지 전달. + } + } catch (err) { + sdk.console.log(`searchParams.get 실패`); + } + } + } catch (e) { + sdk.console.log( + `URL 파싱 실패 (absHref 단계): ${href} (${e instanceof Error ? e.message : e})` + ); + } + }); + + const resultStr = results.join("\n") || "위험한 scope가 발견되지 않았습니다."; + return { data: resultStr }; // 성공했는지 실패했는지 App.vue한테 전달할 메시지. + } catch (e) { + sdk.console.log(`백엔드 에러: ${e instanceof Error ? e.message : e}`); + return { data: "백엔드 에러: " + (e instanceof Error ? e.message : String(e)) }; // App.vue에 전달할 메시지. + } +}; + +export type API = DefineAPI<{ + scan: typeof scan; +}>; + +export function init(sdk: SDK) { + sdk.api.register("scan", scan); +} \ No newline at end of file From b1f3534e1cd6d3f0f33d6a3b563d965784929454 Mon Sep 17 00:00:00 2001 From: imnyang Date: Sat, 31 May 2025 11:55:15 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=ED=8F=AC=ED=8C=85=EC=9D=80=20=ED=96=88?= =?UTF-8?q?=EB=8A=94=EB=8D=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=8A=94=20?= =?UTF-8?q?=EC=95=88=ED=95=B4=EB=B3=B4=EA=B8=B4=20=ED=96=88=EC=96=B4?= =?UTF-8?q?=EC=9A=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=A2=80=20=ED=95=B4?= =?UTF-8?q?=EC=A3=BC=EC=84=B8=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bun.lock | 52 ++++-- package.json | 4 + .../backend/src/controller/scopeDetection.ts | 148 +++++++++--------- packages/backend/src/index.ts | 3 + 4 files changed, 119 insertions(+), 88 deletions(-) diff --git a/bun.lock b/bun.lock index 289e8aa..6fa7969 100644 --- a/bun.lock +++ b/bun.lock @@ -3,6 +3,10 @@ "workspaces": { "": { "name": "caido-oauth", + "dependencies": { + "@types/jsonwebtoken": "^9.0.9", + "jsonwebtoken": "^9.0.2", + }, "devDependencies": { "@caido-community/dev": "^0.1.3", "@caido/sdk-backend": "^0.48.1", @@ -127,6 +131,12 @@ "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + "@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.9", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@22.15.29", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ=="], + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], @@ -143,6 +153,8 @@ "brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -185,6 +197,8 @@ "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], @@ -261,8 +275,14 @@ "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="], + "jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="], + "jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="], + + "jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="], + "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], @@ -271,6 +291,20 @@ "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="], + + "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], "lru-cache": ["lru-cache@11.1.0", "", {}, "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A=="], @@ -291,7 +325,7 @@ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - "ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], @@ -357,6 +391,8 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="], "serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="], @@ -419,6 +455,8 @@ "typescript": ["typescript@5.5.4", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q=="], + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], @@ -449,14 +487,14 @@ "body-parser/qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + "debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "finalhandler/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "router/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], - "send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -477,12 +515,6 @@ "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "body-parser/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "finalhandler/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "router/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "sucrase/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], @@ -491,8 +523,6 @@ "sucrase/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "tsup/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], diff --git a/package.json b/package.json index 7d2ba1b..a1e0324 100644 --- a/package.json +++ b/package.json @@ -11,5 +11,9 @@ "@caido-community/dev": "^0.1.3", "@caido/sdk-backend": "^0.48.1", "typescript": "5.5.4" + }, + "dependencies": { + "@types/jsonwebtoken": "^9.0.9", + "jsonwebtoken": "^9.0.2" } } diff --git a/packages/backend/src/controller/scopeDetection.ts b/packages/backend/src/controller/scopeDetection.ts index 9c8610e..9b74dcc 100644 --- a/packages/backend/src/controller/scopeDetection.ts +++ b/packages/backend/src/controller/scopeDetection.ts @@ -1,89 +1,83 @@ -import type { DefineAPI, SDK } from "caido:plugin"; +import type { SDK } from "caido:plugin"; import { RequestSpec } from "caido:utils"; -const scan = async ( // scan 함수 정의 - sdk: SDK, - url: string -): Promise<{ data: string }> => { - sdk.console.log(`들어온 url : ${url}`); // url이 잘 들어왔는지 확인함요 - - // url이 string이 아니고 , 값이 없거나 그럴 때 유효한 값 넣으라고 출력. - if (!url || typeof url !== "string") { - sdk.console.log("이상한 url 입력함."); - return { data: "알맞은 URL을 입력하세요." }; - } - - try { - const spec = new RequestSpec(url); // url에 GET 요청 보낼거긔. - spec.setMethod("GET"); - spec.setHeader("User-Agent", "Caido Scanner"); - spec.setHeader("Accept", "*/*"); - sdk.console.log(`요청 URL: ${url}`); - - const res = await sdk.requests.send(spec); // 요청 보내고 응답 받음. - sdk.console.log('[SCAN] 응답 :', res); - sdk.console.log(`[SCAN] 요청 성공:${(res as any).status}`); - sdk.console.log(`[SCAN] body: ${(res as any).body ? (res as any).body.toString().substring(0, 100) : "없음"}`); - - const html = (res as any).body ? (res as any).body.toString() : ""; - - // ]*href="([^"]+)"[^>]*>/gi; - const anchors: string[] = []; - let match; - while ((match = anchorRegex.exec(html)) !== null) { // html에서 a href 찾아 배열에 저장함. - if (typeof match[1] === "string") { - anchors.push(match[1]); - } +export class ScopeDetection { + async scan( + sdk: SDK, + url: string + ): Promise<{ data: string }> { + sdk.console.log(`들어온 url : ${url}`); // url이 잘 들어왔는지 확인함요 + + // url이 string이 아니고 , 값이 없거나 그럴 때 유효한 값 넣으라고 출력. + if (!url || typeof url !== "string") { + sdk.console.log("이상한 url 입력함."); + return { data: "알맞은 URL을 입력하세요." }; } - sdk.console.log(`찾아진 a href 개수: ${anchors.length}`); - // 5. scope 탐지 - const results: string[] = []; - anchors.forEach((href) => { // 추출한 a href 링크 하나씩 검사드감. - try { - const absHref = new URL(href, url).href; // 상대경로라면 url 기준으로 절대 URL 바꿔줌줌 - sdk.console.log(`[SCAN] 절대 URL 변환: ${href} -> ${absHref}`); // + try { + const spec = new RequestSpec(url); // url에 GET 요청 보낼거긔. + spec.setMethod("GET"); + spec.setHeader("User-Agent", "Caido Scanner"); + spec.setHeader("Accept", "*/*"); + sdk.console.log(`요청 URL: ${url}`); - if (/oauth|authorize|login|accounts|auth/i.test(absHref)) { // url에 이런 OAuth 키워드가 있는지 필터링. - let u: URL; - try { - u = new URL(absHref); // 필터링된 url을 url 객체로 파싱. 정식 url인 경우 변수 u에 저장. - } catch (err) { // 파싱 실패하면 - sdk.console.log( - `URL 파싱 실패 : ${absHref} (${err instanceof Error ? err.message : err})` - ); - return; - } + const res = await sdk.requests.send(spec); // 요청 보내고 응답 받음. + sdk.console.log('[SCAN] 응답 :', res); + sdk.console.log(`[SCAN] 요청 성공:${(res as any).status}`); + sdk.console.log(`[SCAN] body: ${(res as any).body ? (res as any).body.toString().substring(0, 100) : "없음"}`); - try { - const scope = u.searchParams.get("scope"); // url에 scope있긔?scope값 가져와. - if (scope && /all|\*/i.test(scope)) { // scope가 존재하고 all, *있다면. - results.push(`위험한 scope 발견: ${scope}\n -> ${absHref}`); // results에 경고 메시지 전달. - } - } catch (err) { - sdk.console.log(`searchParams.get 실패`); - } + const html = (res as any).body ? (res as any).body.toString() : ""; + + // ]*href="([^"]+)"[^>]*>/gi; + const anchors: string[] = []; + let match; + while ((match = anchorRegex.exec(html)) !== null) { // html에서 a href 찾아 배열에 저장함. + if (typeof match[1] === "string") { + anchors.push(match[1]); } - } catch (e) { - sdk.console.log( - `URL 파싱 실패 (absHref 단계): ${href} (${e instanceof Error ? e.message : e})` - ); } - }); + sdk.console.log(`찾아진 a href 개수: ${anchors.length}`); - const resultStr = results.join("\n") || "위험한 scope가 발견되지 않았습니다."; - return { data: resultStr }; // 성공했는지 실패했는지 App.vue한테 전달할 메시지. - } catch (e) { - sdk.console.log(`백엔드 에러: ${e instanceof Error ? e.message : e}`); - return { data: "백엔드 에러: " + (e instanceof Error ? e.message : String(e)) }; // App.vue에 전달할 메시지. - } -}; + // 5. scope 탐지 + const results: string[] = []; + anchors.forEach((href) => { // 추출한 a href 링크 하나씩 검사드감. + try { + const absHref = new URL(href, url).href; // 상대경로라면 url 기준으로 절대 URL 바꿔줌줌 + sdk.console.log(`[SCAN] 절대 URL 변환: ${href} -> ${absHref}`); // -export type API = DefineAPI<{ - scan: typeof scan; -}>; + if (/oauth|authorize|login|accounts|auth/i.test(absHref)) { // url에 이런 OAuth 키워드가 있는지 필터링. + let u: URL; + try { + u = new URL(absHref); // 필터링된 url을 url 객체로 파싱. 정식 url인 경우 변수 u에 저장. + } catch (err) { // 파싱 실패하면 + sdk.console.log( + `URL 파싱 실패 : ${absHref} (${err instanceof Error ? err.message : err})` + ); + return; + } -export function init(sdk: SDK) { - sdk.api.register("scan", scan); + try { + const scope = u.searchParams.get("scope"); // url에 scope있긔?scope값 가져와. + if (scope && /all|\*/i.test(scope)) { // scope가 존재하고 all, *있다면. + results.push(`위험한 scope 발견: ${scope}\n -> ${absHref}`); // results에 경고 메시지 전달. + } + } catch (err) { + sdk.console.log(`searchParams.get 실패`); + } + } + } catch (e) { + sdk.console.log( + `URL 파싱 실패 (absHref 단계): ${href} (${e instanceof Error ? e.message : e})` + ); + } + }); + + const resultStr = results.join("\n") || "위험한 scope가 발견되지 않았습니다."; + return { data: resultStr }; // 성공했는지 실패했는지 App.vue한테 전달할 메시지. + } catch (e) { + sdk.console.log(`백엔드 에러: ${e instanceof Error ? e.message : e}`); + return { data: "백엔드 에러: " + (e instanceof Error ? e.message : String(e)) }; // App.vue에 전달할 메시지. + } + }; } \ No newline at end of file diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 2eccd6d..244a3e2 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -3,12 +3,14 @@ import type { Request } from "caido:utils"; import { ImplicitGrantController } from "./controller/implictGrant"; import { AuthZCodeGrantController } from "./controller/authZCodeGrant"; import { PKCECheck } from "./controller/PKCECheck"; +import { ScopeDetection } from "./controller/scopeDetection"; export type API = DefineAPI<{}>; const implicitGrantController = new ImplicitGrantController(); const authZCodeGrantController = new AuthZCodeGrantController(); const pkceCheckController = new PKCECheck(); +const ScopeDetectionController = new ScopeDetection(); // function matchSSORequest(req: Request): boolean { // const raw = req.getRaw().toString(); @@ -36,6 +38,7 @@ export function init(sdk: SDK) { if (result) { await pkceCheckController.test(sdk, req); + await ScopeDetectionController.scan(sdk, req.getUrl()); await sdk.findings.create({ title: "Possible SSO Request Detected",