diff --git a/packages/backend/src/controller/scopeDetection.ts b/packages/backend/src/controller/scopeDetection.ts index 9b74dcc..2e6a5c5 100644 --- a/packages/backend/src/controller/scopeDetection.ts +++ b/packages/backend/src/controller/scopeDetection.ts @@ -1,83 +1,57 @@ -import type { SDK } from "caido:plugin"; -import { RequestSpec } from "caido:utils"; +import type { Request, Response } from "caido:utils"; +import type { SDK, DefineAPI } from "caido:plugin"; +import { HttpUtils } from "../utils/http"; -export class ScopeDetection { - async scan( - sdk: SDK, - url: string - ): Promise<{ data: string }> { - sdk.console.log(`들어온 url : ${url}`); // url이 잘 들어왔는지 확인함요 +const httpUtils = new HttpUtils(); + + export class scopeDetection { + // 쿼리 문자열에서 scope 값을 추출하고 값이 없으면 null 반환, 있다면 문자열 반환. + private getScopeFromQuery(query: string | undefined): string | null { + if (!query) return null; + const match = query.match(/(?:^|&)scope=([^&]+)/); + if (match && match[1]) { + return decodeURIComponent(match[1]); + } + return null; + } + // 요청 쿼리 문자열을 받아와서 scope 파라미터 값을 추출하고 값이 all이나 *이면 위험한 scope라고 출력. + async checkScope( + request: Request, + response: Response + ): Promise { - // url이 string이 아니고 , 값이 없거나 그럴 때 유효한 값 넣으라고 출력. - if (!url || typeof url !== "string") { - sdk.console.log("이상한 url 입력함."); - return { data: "알맞은 URL을 입력하세요." }; + const query = request.getQuery() || ""; + const scope = this.getScopeFromQuery(query); + + if (scope && (scope === "all" || scope === "*")) { + return [`요청에서 scope 값 위험 요소 발견: ${scope}`]; } - 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}`); + // 응답 헤더 location에 scope 찾기. + const location = httpUtils.getHeaderValue(response.getHeaders(), "location") || ""; + const locScope = this.getScopeFromQuery(location); - 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) : "없음"}`); + if (locScope && (locScope === "all" || locScope === "*")) { + return [`scope parameter in response location header is insecure: ${locScope}`]; + } - const html = (res as any).body ? (res as any).body.toString() : ""; + return 0; + } - // ]*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}`); + async checkAndReport( + sdk: any, // DefineAPI 등 구체 타입 맞춰야 함 + request: Request, + response: Response + ) { + const result = await this.checkScope(request, response); // scope 문제 있는지 검사 부분. - // 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})` - ); - } + if (result !== 0) { // 문제 있을 경우. + await sdk.findings.create({ + title: "OAuth scope value issue", + description: `${request.getMethod()} ${request.getUrl()}: ${result.join(", ")}`, + request, + reporter: "checker", }); - - 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 + } +}