From e7de3ee4a71730e8cdd77388baf5b962e9b71837 Mon Sep 17 00:00:00 2001 From: sultanofdisco Date: Mon, 2 Jun 2025 21:09:35 +0900 Subject: [PATCH] =?UTF-8?q?nonceCheck=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/controller/nonceCheck.ts | 68 +++++---- packages/backend/src/index.ts | 3 +- pnpm-lock.yaml | 129 +++++++++++++++++- 3 files changed, 172 insertions(+), 28 deletions(-) diff --git a/packages/backend/src/controller/nonceCheck.ts b/packages/backend/src/controller/nonceCheck.ts index a27a4d6..476dbb5 100644 --- a/packages/backend/src/controller/nonceCheck.ts +++ b/packages/backend/src/controller/nonceCheck.ts @@ -1,29 +1,51 @@ import type { Request, Response } from "caido:utils"; -import { TokenLeakCheck } from "./tokenLeakCheck"; -export class NonceCheckController{ - /** - * 응답이 OIDC(OpenID Connect) 플로우인지 확인하는 메서드 - */ - - public static isOidcFlow(req: Request, res:Response): boolean { - if(TokenLeakCheck.extractIdToken(req, res)) { - return true; - } - return false; +export class NonceCheckController { + /** + * OIDC 플로우 탐지 로직 (OAuth 2.0과 구분) + */ + public static isOidcFlow(req: Request, res: Response): boolean { + const url = req.getUrl(); + const query = req.getQuery(); + const location = res.getHeader("Location"); + const contentType = res.getHeader("Content-Type"); + + // 1️⃣ Authorization 요청: scope=openid 포함 + if (url.includes("/authorize") && /response_type=/.test(query) && /scope=openid/.test(query)) { + return true; } - - public static isNonceCheckRequest(req: Request): boolean { - const id_token = TokenLeakCheck.decodeIdToken(req); - - // 1. nonce 파라미터가 포함된 요청인지 확인 - if (id_token && id_token.includes("nonce=")) { - return true; - } - - return false; + // 2️⃣ Token 응답: id_token 필드 포함 + if (contentType?.includes("application/json")) { + const body = res.getBody(); + const bodyStr = typeof body === "string" ? body : body?.toString?.() ?? ""; + if (bodyStr && /id_token/.test(bodyStr)) { + return true; + } } + + // 3️⃣ Redirect 응답: Location 헤더에 id_token 포함 + if ( + (res.getCode() === 302 || res.getCode() === 303) && + location && + /id_token=/.test(Array.isArray(location) ? location[0] ?? "" : location ?? "") + ) { + return true; + } + + // 4️⃣ Authorization 요청 + nonce 파라미터 포함 + if (url.includes("/authorize") && /nonce=/.test(query)) { + return true; + } + + + + return false; + } + + + + + + } - - diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 0165988..7eb3214 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -26,7 +26,8 @@ export function init(sdk: SDK) { await tokenCheck.testResp(sdk, res, req); await ScopeDetectionController.scan(sdk, req.getUrl()); - if (NonceCheckController.isOidcFlow(req, res)) { + // isOidcFlow는 비동기 메서드로 변경 + if (await NonceCheckController.isOidcFlow(req, res)) { await sdk.findings.create({ title: "OIDC Flow Detected", description: "The request appears to be part of an OIDC flow.", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83609d4..1caa9d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,10 +7,17 @@ settings: importers: .: + dependencies: + '@types/jsonwebtoken': + specifier: ^9.0.9 + version: 9.0.9 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 devDependencies: '@caido-community/dev': specifier: ^0.1.3 - version: 0.1.5(postcss@8.5.3)(typescript@5.5.4) + version: 0.1.5(@types/node@22.15.29)(postcss@8.5.3)(typescript@5.5.4) '@caido/sdk-backend': specifier: ^0.48.1 version: 0.48.1 @@ -328,6 +335,15 @@ packages: '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/jsonwebtoken@9.0.9': + resolution: {integrity: sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@22.15.29': + resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -364,6 +380,9 @@ packages: brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -465,6 +484,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -622,9 +644,19 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -639,6 +671,27 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} @@ -837,6 +890,11 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + send@1.2.0: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} @@ -971,6 +1029,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -1065,7 +1126,7 @@ packages: snapshots: - '@caido-community/dev@0.1.5(postcss@8.5.3)(typescript@5.5.4)': + '@caido-community/dev@0.1.5(@types/node@22.15.29)(postcss@8.5.3)(typescript@5.5.4)': dependencies: '@caido/plugin-manifest': 0.3.0 chalk: 5.4.1 @@ -1076,7 +1137,7 @@ snapshots: jiti: 2.4.2 jszip: 3.10.1 tsup: 8.3.5(jiti@2.4.2)(postcss@8.5.3)(typescript@5.5.4) - vite: 6.0.7(jiti@2.4.2) + vite: 6.0.7(@types/node@22.15.29)(jiti@2.4.2) ws: 8.18.0 zod: 3.24.1 transitivePeerDependencies: @@ -1284,6 +1345,17 @@ snapshots: '@types/estree@1.0.7': {} + '@types/jsonwebtoken@9.0.9': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 22.15.29 + + '@types/ms@2.1.0': {} + + '@types/node@22.15.29': + dependencies: + undici-types: 6.21.0 + accepts@2.0.0: dependencies: mime-types: 3.0.1 @@ -1328,6 +1400,8 @@ snapshots: dependencies: balanced-match: 1.0.2 + buffer-equal-constant-time@1.0.1: {} + bundle-require@5.1.0(esbuild@0.24.2): dependencies: esbuild: 0.24.2 @@ -1401,6 +1475,10 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} emoji-regex@8.0.0: {} @@ -1605,6 +1683,19 @@ snapshots: json-schema-traverse@1.0.0: {} + 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.1.1 + ms: 2.1.3 + semver: 7.7.2 + jszip@3.10.1: dependencies: lie: 3.3.0 @@ -1612,6 +1703,17 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 + jwa@1.4.2: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.2 + safe-buffer: 5.2.1 + lie@3.3.0: dependencies: immediate: 3.0.6 @@ -1622,6 +1724,20 @@ snapshots: load-tsconfig@0.2.5: {} + 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.1.1: {} + lodash.sortby@4.7.0: {} lru-cache@10.4.3: {} @@ -1801,6 +1917,8 @@ snapshots: safer-buffer@2.1.2: {} + semver@7.7.2: {} + send@1.2.0: dependencies: debug: 4.3.6 @@ -1968,6 +2086,8 @@ snapshots: typescript@5.5.4: {} + undici-types@6.21.0: {} + unpipe@1.0.0: {} util-deprecate@1.0.2: {} @@ -1976,12 +2096,13 @@ snapshots: vary@1.1.2: {} - vite@6.0.7(jiti@2.4.2): + vite@6.0.7(@types/node@22.15.29)(jiti@2.4.2): dependencies: esbuild: 0.24.2 postcss: 8.5.3 rollup: 4.41.0 optionalDependencies: + '@types/node': 22.15.29 fsevents: 2.3.3 jiti: 2.4.2