
AI로 만든 앱에서 가장 위험한 허점: 타인의 데이터가 보이는 "인가 누락" 이야기
요약
AI 생성 코드가 흔히 범하는 보안 취약점인 '인가 누락(IDOR)'의 위험성을 다룹니다. 인증과 인가의 차이를 설명하고, AI가 비즈니스 로직에 기반한 권한 확인을 누락하는 이유와 이를 검증하는 방법을 제시합니다.
핵심 포인트
- 인증(Authentication)과 인가(Authorization)의 개념적 차이 이해
- AI는 작동하는 코드를 만드는 데 집중하여 권한 검증을 누락하기 쉬움
- IDOR 취약점은 타인의 데이터 열람 및 삭제로 이어지는 심각한 보안 허점
- 두 개의 계정을 활용한 교차 테스트를 통해 인가 로직 검증 필요
서론
AI 도구로 앱을 만들 수 있게 되면서, 개인 개발의 문턱이 단번에 낮아졌습니다. 로그인 기능도 AI에게 부탁하면 몇 분 만에 작동하는 결과물이 나옵니다.
하지만 그 "작동하는 로그인"에는 간과하기 쉬운 허점이 하나 있습니다. 로그인은 올바르게 구현되어 있지만, 로그인한 후에 "그 데이터를 봐도 되는 사람인지"를 확인하지 않는 허점입니다.
이는 IDOR (Insecure Direct Object Reference, 안전하지 않은 직접 객체 참조)라고 불리며, Web 앱의 취약점 중에서도 실질적인 피해가 큰 것입니다. 게다가 AI 생성 코드에서 특히 빈번하게 발견됩니다. 이 기사에서는 왜 발생하는지, 어떻게 확인하는지, 어떻게 고치는지에 대해 하나의 주제에 집중하여 깊이 있게 다룹니다.
(AI 생성 코드의 전형적인 버그 전체는 별도 기사 1, 리뷰 진행 방법은 별도 기사 2에 정리해 두었습니다.)
인증과 인가는 별개의 것이다
먼저 용어 정리부터 하겠습니다. 자주 혼동되지만, 이 둘은 다릅니다.
인증 (Authentication): 당신이 "누구인지"를 확인한다. 로그인을 의미한다. -
인가 (Authorization): 그 사람이 "그것을 해도 되는지"를 확인한다. 권한을 의미한다.
AI는 전자, 즉 로그인 구현은 잘합니다. 이메일·패스워드 인증이나 OAuth는 학습 데이터에 대량으로 존재하기 때문에 어느 정도 올바르게 작성할 수 있습니다.
문제는 후자입니다. "로그인한 사용자가 정말로 그 데이터의 소유자인가"를 확인하는 작업은 앱의 비즈니스 로직에 의존하기 때문에, AI는 지시하지 않으면 작성하지 않습니다. 그리고 많은 사람은 거기까지 지시하지 않습니다.
구체적인 예시: 타인의 주문이 보여버리는 경우
이런 API를 가정해 봅시다. 주문 상세 정보를 반환하는 엔드포인트입니다.
// 로그인을 필수 사항으로 설정함. 여기까지는 올바름
app.get("/api/orders/:id", requireLogin, async (req, res) => {
const order = await db.orders.findById(req.params.id)
...
언뜻 보기에는 문제가 없어 보입니다. requireLogin으로 로그인을 필수화했기 때문입니다.
하지만 여기에는 "그 주문이 로그인한 본인의 것인가"에 대한 확인이 없습니다. 즉, 로그인만 되어 있다면,
GET /api/orders/1001 ← 자신의 주문
GET /api/orders/1002 ← 타인의 주문 (하지만 보여버림)
GET /api/orders/1003 ← 이것도 타인의 주문 (보여버림)
URL의 숫자를 다른 값으로 바꾸는 것만으로 타인의 주문을 통째로 열람할 수 있게 되는 것입니다. 주문 정보에는 성명·주소·구매 이력이 포함되는 경우가 많으며, 이는 명백한 개인정보 유출입니다.
더 나쁜 것은 이것이 DELETE /api/orders/:id인 경우입니다. 타인의 데이터를 삭제할 수 있게 됩니다.
왜 AI는 이것을 작성하는 것을 잊어버리는가
이유는 간단합니다. "작동하게 만드는 것"만 목적이라면 인가 체크는 필요 없기 때문입니다.
자신의 계정으로 자신의 데이터를 다루고 있는 한, 인가가 없어도 정상적으로 작동합니다. 개발 중에 "타인의 ID를 넣어서 테스트"하는 일은 보통 없으므로, 테스트 단계에서도 알아차리지 못합니다. AI는 주어진 사양을 만족하는 "작동하는 코드"를 작성하지만, "악의적인 방식으로 사용된다면 어떻게 될까"까지는 명시적으로 요청하지 않으면 고려하지 않습니다.
즉, 이것은 AI가 나쁘다기보다, 인간이 "작동한다"는 것만 확인하고 만족해버림으로써 통과되어 버리는 허점입니다.
스스로 확인하는 방법: 2개의 계정으로 호출하기
코드를 읽는 것보다 확실한 것은 실제로 시도해 보는 것입니다. 절차는 다음과 같습니다.
- 테스트용으로 계정을 2개 만든다 (사용자 A · 사용자 B)
- 사용자 A로 로그인하여, A의 데이터를 1건 생성한다 (주문이든 게시물이든 무엇이든)
- 해당 데이터의 ID (URL의 숫자)를 기록해 둔다
- 사용자 B로 다시 로그인하여, 방금 기록해 둔 A의 ID를 호출한다
이때 B가 A의 데이터를 볼 수 있다면 인가 누락이 있는 것입니다. 브라우저의 개발자 도구나 curl을 사용하여 URL의 숫자를 교체하는 것만으로 확인할 수 있습니다.
# B의 세션으로, A의 주문 ID를 호출해 본다
curl -H "Cookie: session=B의 세션" https://example.com/api/orders/1001
# 여기서 A의 주문이 반환된다면 실패(Out)
「연번 ID」를 사용하는 앱은 특히 위험합니다. 1001이 보였다면 1002도 1003도 시도해 볼 수 있기 때문입니다.
어떻게 수정하는가
수정 방법은 명쾌합니다. 취득한 데이터가 정말로 로그인한 사용자의 것인지 검증하는 한 줄을 추가하기만 하면 됩니다.
app.get("/api/orders/:id", requireLogin, async (req, res) => {
const order = await db.orders.findById(req.params.id)
// 이것을 추가: 소유자가 아니라면 보여주지 않음
...
두 가지 포인트가 있습니다.
1. 권한이 없을 때는 403보다 404를 반환한다
"권한이 없습니다 (403)"라고 반환하면, "해당 ID의 데이터가 존재한다"는 사실을 알려주게 됩니다. "찾을 수 없습니다 (404)"로 설정하면 데이터의 존재 여부조차 숨길 수 있습니다. 공격자에게 정보를 주지 않기 위한 작은 기교입니다.
2. 가능하다면 "취득 시점"에서 필터링한다
위의 예시는 취득 후에 체크하고 있지만, 더 안전한 방법은 쿼리 (Query) 단계에서 사용자 ID를 조건에 포함하는 것입니다.
// 처음부터 "내 것만" 가져오기
const order = await db.orders.findOne({
id: req.params.id,
...
이렇게 하면 애초에 타인의 데이터는 히트(Hit)되지 않습니다. 체크를 누락하는 실수도 줄어듭니다.
프레임워크별 주의사항
- Next.js (App Router / Route Handlers):
route.ts안에서 세션의 사용자 ID와 리소스의 소유자를 매번 대조해야 합니다. 미들웨어 (Middleware)는 로그인 확인까지는 할 수 있어도, 리소스 단위의 인가 (Authorization)까지는 대신 처리해주지 않는 경우가 많습니다. - Firebase / Supabase: 클라이언트에서 직접 DB를 호출하는 구성인 경우, **보안 규칙 (RLS, Row Level Security)**을 통해 행 레벨의 인가를 반드시 설정해야 합니다. 이 부분이 비어 있으면 프론트엔드에서 숨겨두더라도 API를 통해 전체 데이터를 가져올 수 있습니다.
- ORM의 일괄 취득:
findAll()계열을 사용자가 필터링하지 않고 사용하는 곳은 주의가 필요합니다. 목록 API에서도 동일한 취약점이 발생할 수 있습니다.
요약
- 로그인 (인증, Authentication)이 구현되어 있더라도, **해당 데이터의 소유자인지 확인하는 것 (인가, Authorization)**은 별개의 문제입니다.
- AI 생성 코드는 "작동하는 것"을 우선시하므로, 인가 체크가 누락되기 쉽습니다.
- 확인 방법은 간단합니다: 2개의 계정으로 타인의 ID를 호출해 보기
- 수정 방법도 간단합니다: 취득한 데이터의 소유자를 검증한다 (가능하면 쿼리에서 필터링한다), 권한 외의 요청은 404를 반환한다.
"타인의 ID를 호출해 보기"는 오늘이라도 당장 자신의 앱에서 시도해 볼 수 있습니다. 만약 타인의 데이터가 보인다면, 공개하기 전에 반드시 수정해야 할 우선순위가 높은 허점입니다.
직접 작성했거나 (혹은 AI에게 작성하게 한) 코드는 이러한 "악의적인 방식으로 사용된다면"이라는 관점이 빠지기 쉽습니다. 공개 전에 제삼자의 눈으로 확인하고 싶을 때를 위해, AI로 작성한 코드의 리뷰 및 버그 수정을 현역 엔지니어에게 스팟으로 의뢰할 수 있는 창구 "HeyGeek"를 시험 운영 중입니다. 리포지토리 (Repository) URL과 궁금한 점을 보내는 것만으로, 이 글과 같은 관점에서 사람이 직접 체크합니다 👉 HeyGeek (공개 전 체크)
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기