본문 바로가기
문서로그인 확인 없는 API 라우트

로그인 확인 없는 API 라우트

데이터를 바꾸는 API 주소(POST/PUT/PATCH/DELETE)에 "너 로그인했니?"를 묻는 줄이 없어서, 주소만 알면 누구나 그 기능을 실행할 수 있어요.

한눈에
위험도
높음(High)
CWE
CWE-862 · 권한 확인 누락
OWASP
A01:2021
vg-nextjs-route-no-auth

이게 무슨 말이냐 (쉽게)

화면(버튼)과 API(주소)는 다른 겁니다. 로그인 안 한 사람에게 "삭제" 버튼을 안 보여줘도, 그 버튼이 부르는 주소(/api/notes/delete)는 인터넷에 그대로 열려 있어요. 버튼은 예의고, 잠금은 API 안에 있어야 합니다.

AI에게 "글 삭제 API 만들어줘"라고 하면 대개 동작하는 코드를 줍니다. 동작이 목표였으니까요. 로그인 확인은 따로 시키지 않으면 안 들어갑니다.

우리 검사기는 그 파일에 POST·PUT·PATCH·DELETE를 내보내는 함수가 있는데 getServerSession · supabase.auth · getUser · requireAuth 같은 확인 코드가 한 줄도 없을 때만 표시합니다.

실제로 어떻게 터지나 (시나리오)

메모 앱을 만들었어요. 삭제는 app/api/notes/delete/route.ts가 처리하고, 화면에서는 본인 메모에만 휴지통 아이콘이 뜹니다. 안전해 보이죠.

누군가 개발자도구 Network 탭에서 본인 메모를 한 번 지워봅니다 → 요청 주소와 형식을 확인합니다 → 터미널에서 id만 바꿔 1부터 5000까지 반복. 로그인도, 토큰도 필요 없었어요. 몇 분 만에 전체 사용자 메모가 사라집니다.

같은 구멍이 결제 라우트에 있으면 남의 주문을 취소하고, 등급 변경 라우트에 있으면 자기를 관리자로 올립니다.

최악의 경우

나한테 해당되나? (체크)

→ 3·4번이 같이 체크되면 해당됩니다. 이 룰은 휴리스틱이라 리포트에는 신뢰도 점검 필요로 떠요 — 미들웨어에서 이미 막고 있다면 오탐일 수 있으니 아래 확인 절차까지 같이 보세요.

어떻게 고치나 (복붙)

① 핸들러 첫 줄에서 확인하고, 없으면 401

// app/api/notes/delete/route.ts
import { createServerClient } from "@/lib/supabase/server";

export async function POST(req: Request) {
  const supabase = createServerClient();
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) return Response.json({ error: "unauthorized" }, { status: 401 }); // 먼저 막고 시작
  // ... 여기부터 실제 작업
}

② 로그인만으로는 부족 — "이게 네 것이냐"까지. 로그인한 사람이 남의 id를 넣는 게 더 흔한 사고입니다(IDOR, 룰 vg-idor-param-direct-query).

const { id } = await req.json();
await supabase
  .from("notes")
  .delete()
  .eq("id", id)
  .eq("user_id", user.id);   // 소유자 조건을 쿼리에 박는다

③ 미들웨어는 보조 수단으로만 — middleware.ts의 matcher는 적어둔 경로만 지킵니다. 새 라우트를 추가하면 조용히 빠져요(룰 vg-nextjs-middleware-auth-bypass). 핸들러 안의 확인이 정본, 미들웨어는 덤입니다.

④ Server Action도 같은 규칙 — "use server" 함수는 폼 밖에서도 호출됩니다(룰 vg-nextjs-server-action-no-auth).

고친 뒤 확인

  1. 01

    로그아웃 상태로 직접 호출

    401이 와야 정상입니다.
    curl -i -X POST https://내도메인/api/notes/delete -H "content-type: application/json" -d '{"id":1}'

  2. 02

    남의 데이터로 호출

    로그인한 다른 계정으로 남의 id를 넣어 호출 → 아무것도 안 바뀌어야 정상. 성공 응답이 와도 DB 행을 직접 확인하세요.

  3. 03

    쓰기 라우트 전수 확인

    git grep -lE "export (async )?function (POST|PUT|PATCH|DELETE)" app/api → 나온 파일마다 인증 줄이 있는지.

규제 연결

접근통제 실패라 ISMS-P 2.5(인증·권한)·2.6(접근통제), PIPA 제29조 안전조치의무에 직접 걸립니다. 조항 매핑은 신뢰 센터의 참고 대조표에 있습니다. 리포트가 발견마다 자동으로 붙여 주지는 않습니다.

출처/더 읽기: OWASP A01:2021(Broken Access Control), CWE-862, Next.js Route Handlers 공식 문서, Supabase Auth 서버 사이드 문서. 이 카드는 우리가 직접 작성했습니다.

문서 목록으로