- 위험도
- 높음(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까지 반복. 로그인도, 토큰도 필요 없었어요. 몇 분 만에 전체 사용자 메모가 사라집니다.
같은 구멍이 결제 라우트에 있으면 남의 주문을 취소하고, 등급 변경 라우트에 있으면 자기를 관리자로 올립니다.
최악의 경우
- 전체 데이터 삭제·변조. 백업이 없으면 복구 불가.
- 권한 상승 — 스스로에게 role: "admin"을 부여.
- 개인정보를 돌려주는 라우트라면 회원정보 전량 유출 → PIPA 제29조 안전조치의무 위반.
- 접근 로그가 없으면 "언제 누가 지웠는지"조차 밝히지 못합니다.
나한테 해당되나? (체크)
- app/api/.../route.ts 또는 pages/api/에 파일이 있다.
- 그 파일에 export async function POST(또는 PUT·PATCH·DELETE)가 있다.
- 그 안에서 DB에 쓰거나 지운다.
- 파일 어디에도 세션·사용자를 꺼내 쓰는 줄이 없다.
- "화면에서 버튼을 숨겼으니 괜찮다"고 생각했다.
→ 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).
고친 뒤 확인
- 01
로그아웃 상태로 직접 호출
401이 와야 정상입니다.
curl -i -X POST https://내도메인/api/notes/delete -H "content-type: application/json" -d '{"id":1}' - 02
남의 데이터로 호출
로그인한 다른 계정으로 남의 id를 넣어 호출 → 아무것도 안 바뀌어야 정상. 성공 응답이 와도 DB 행을 직접 확인하세요.
- 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 서버 사이드 문서. 이 카드는 우리가 직접 작성했습니다.
← 문서 목록으로