보안 가이드
로그인만 붙이고 안심하고 있지 않은가 — 인증과 인가의 차이
인증(누구인가)과 인가(무엇을 해도 되는가)는 다릅니다. 로그인 기능을 붙여도 데이터를 소유자로 좁히지 않으면 '로그인한 사람은 전부 전체 데이터를 볼 수 있는' 사고가 납니다. 그 원리와 방어법을 공격 절차를 감추고 설명합니다.
"로그인 기능을 붙였으니 이제 지켜지고 있다" —— 이것은 위험한 착각입니다. 실제로는 로그인(인증)과 권한(인가)은 별개이며, 여기를 잘못 다루면 로그인한 사람이 전부, 남의 것까지 포함해 전체 데이터를 볼 수 있는 사고가 납니다. 공격 절차는 감추고, 원리와 방어법을 설명합니다.
인증과 인가는 별개
혼동되기 쉽지만, 역할은 전혀 다릅니다.
인증뿐(흔한 오해)
- "로그인할 수 있다=안전"이라고 생각한다
- 로그인 후의 데이터 조회를 모두 동일하게 취급한다
- 결과: 로그인한 사람은 전부, 전체 데이터에 도달
인증+인가(있어야 할 모습)
- 인증으로 "누구인가"를 확인한다
- 인가로 "그 사람이 접근해도 되는 데이터만"으로 한정한다
- 데이터 조회는 항상 본인의 소유분으로 스코프
인증은 "입구에서의 본인 확인", 인가는 "들어가도 되는 방의 제어". 로그인 기능은 전자밖에 보장하지 않습니다. "누가 어떤 데이터에 접근해도 되는가"는 당신이 따로 작성해야 합니다.
직접 만든 앱에서 '전체 데이터 열람'이 일어나는 원리
현장에서는 독립된 여러 자작 시스템에서 같은 구멍이 반복해서 발견됩니다. 예를 들어—— 개인용 태스크/메모 관리 앱에서도, 소규모 상품·수주 관리 시스템(외부 API 연동 있음)에서도, 원인은 같은 두 가지 결함의 겹침이었습니다.
① 등록이 열려 있다(게다가 이중 경로)
인증 스캐폴드(Laravel의 Breeze/Jetstream에 준하는 구조 등)는 초기 상태에서 신규 등록 페이지가 공개되어 있는 경우가 많다. 게다가 UI 라이브러리 측과 인증 패키지 측에서 등록 라우트가 이중화되어 있으면, 한쪽을 막아도 /register가 남는다. URL을 아는 제3자가 등록할 수 있어 버린다.
② 인증은 있지만 인가가 없다(소유자 스코프의 결락)
데이터 테이블에 소유자 컬럼(user_id)이 없다, 또는 목록·조회·수정·삭제 쿼리를 본인으로 좁히지 않았다. 그러면 "로그인=전체 데이터로의 접근"이 된다. 등록 직후에 자동 로그인하는 구현이라면, 제3자는 등록한 순간에 남의 데이터 목록에 착지한다.
결과: 인증 ≠ 인가의 사고
입구(로그인)는 붙어 있는데 방의 열쇠(인가)가 없는 상태. 제3자는 "정규로 로그인한 이용자"로서 행동할 수 있기 때문에, 부정 접근의 흔적조차 남기 어렵다.
상품·수주 관리 같은 업무 데이터를 다루는 쪽일수록 폭발 반경이 크다(고객 정보·주문·연동처의 API 키 등에 파급될 수 있다)는 점도 놓칠 수 없습니다. 같은 방식으로 만든 다른 시스템에는 같은 구멍이 있을 가능성이 높은 것입니다.
방어법
데이터는 반드시 소유자로 스코프한다
모든 읽기·쓰기를 "로그인 중인 본인의 소유분"으로 한정한다. ORM의 글로벌 스코프나 인가 정책(정책/게이트)으로, 조회·수정·삭제 전부에 소유자 조건을 강제한다. 테이블에 소유자 컬럼이 없다면 우선 거기서부터 다시 설계한다.
불필요한 등록은 즉시 닫고·경로 이중화를 의심한다
개인/내부 앱은 신규 등록을 무효화. /register 등이 실제로 404가 되는지를 HTTP로 실측하고, UI 라이브러리와 인증 패키지의 양쪽 등록 경로를 확인한다.
관리 계열은 다층 방어
내부·관리 용도는 인증만에 의존하지 말고 IP 제한이나 Basic 인증 등을 겹친다. 한 겹의 벽이 뚫려도 다음에서 막는다.
감사 로그와 접근 로그를 '사고 나기 전'에
"누가·언제·어느 IP에서·무엇을"을 남기지 않으면, 사고 후에 '무엇이 보였는가'를 추적할 수 없다. 최소한 로그인 성패의 감사 로그와, 웹 접근 로그의 보관·로테이션을 준비한다.
신규 등록·이상을 탐지한다
이번 교훈의 핵심은 "장기간 알아차리지 못했다"는 것. 신규 사용자 등록의 알림, 정기적인 사용자/데이터의 재고 점검 등, 빨리 알아차리는 구조를 넣는다. 탐지가 없으면 구멍은 조용히 열린 채가 된다.
본 사이트의 관점: 라이브러리는 '누구인가'까지밖에 보장하지 않는다
인증 라이브러리가 돌봐 주는 것은 "누구인가"까지입니다. "그 사람이 무엇을 해도 되는가"는 설계자인 당신이 작성하지 않으면 존재하지 않습니다. 그리고—— 하나의 시스템에서 발견된 구조 결함은 같은 방식으로 만든 다른 시스템에도 있다고 생각하는 것이 안전합니다. 한 건 고치고 끝내지 말고, 같은 종류의 구성을 횡단으로 점검합시다. 읽혔는지 어떤지를 추적할 수 없을 때는, "읽혔다는 전제"로 노출된 비밀을 실효·로테이트하는 것이 올바른 자세입니다.
다음으로 읽기
- 용어집: IDOR(취약한 접근 제어)이란(남의 ID를 지정해 남의 데이터에 닿는 전형)
- 구현: 프레임워크별 보안(입구)(각 프레임워크에서 "인가를 어디에 작성할까"를 구체화)
- 관련: 공개 디렉터리에 비밀 파일을 두지 않기(token/credentials/.env의 노출 점검)
- 입문: 비밀을 지키는 법·초입문 / 토대: 보안 최소한 체크리스트
출처
- OWASP Top 10 (2021) A01: Broken Access Control: owasp.org
- OWASP Authorization Cheat Sheet: cheatsheetseries.owasp.org
FAQ
Q인증(Authentication)과 인가(Authorization)는 어떻게 다른가요?
인증은 '당신이 누구인가'를 확인하는 것(로그인)입니다. 인가는 '그 사람이 무엇을 해도 되는가'를 정하는 것(권한)입니다. 비유하자면 인증은 건물 입구에서 본인 확인을 하는 것, 인가는 '그 사람이 들어가도 되는 방은 어디인가'를 제어하는 것입니다. 로그인 기능을 붙인 것만으로는 인증밖에 구현되지 않으며, 인가(누가 어떤 데이터에 접근해도 되는가)는 따로 작성해야 합니다. 여기를 혼동하면 '로그인한 사람은 전부 전체 데이터를 볼 수 있는' 상태가 됩니다.
Q로그인 기능이 있는데 왜 전체 데이터가 보이나요?
데이터 조회 쿼리를 '로그인 중인 본인 것만'으로 좁히지 않았기 때문입니다. 예를 들어 목록·조회·수정·삭제 중 어느 하나라도 소유자(user_id) 조건을 붙이는 것을 잊으면, 로그인만 통과하면 남의 것까지 포함해 전건이 반환됩니다. 인증의 유무와는 무관하게 일어나는 설계의 구멍이며, OWASP Top 10에서 최상위에 자리매김한 Broken Access Control(취약한 접근 제어)의 전형입니다.
Q개인 도구나 사내 앱이라면 등록을 열어 둬도 괜찮지 않나요?
위험합니다. 많은 인증 스캐폴드는 초기 상태에서 신규 등록(/register 등)이 공개되어 있습니다. URL만 알면 제3자가 등록할 수 있고, 인가가 없으면 그대로 안쪽 데이터에 도달합니다. 게다가 등록 경로가 이중화(UI 라이브러리와 인증 패키지 양쪽)되어 있는 경우가 있어, 한쪽만 막아도 닫히지 않습니다. 불필요한 등록은 즉시 무효화하고, 관리 계열은 IP 제한 등의 다층 방어를 겹치는 것이 안전합니다.