[주요정보통신기반시설 웹 애플리케이션 취약점 분석·평가 21일 완성 코스] 11강 — [Day 11] 불충분한 권한 검증
[Day 11] 불충분한 권한 검증 (Insufficient Access Control)
안녕하세요! Hackerstory 공부방의 IT/보안 튜터 정다은입니다. 👋
어느덧 21일 코스의 반환점을 돌았습니다! 지난시간 [Day 10]에서는 사용자가 '누구인지' 확인하는 인증(Authentication) 절차의 허점에 대해 공부했었죠?
오늘 학습할 [Day 11] 주제는 **불충분한 권한 검증(Insufficient Access Control)**입니다. 인증을 통과해 시스템에 로그인한 사용자라 할지라도, 그 사용자가 '어떤 행동까지 할 수 있는지' 제어하는 인가/권한 부여(Authorization) 과정에서 구멍이 생기면 발생하는 심각한 취약점입니다.
개발 현장에서 생각보다 빈번하게 발생하는 이 취약점의 원리와 실제 사례, 그리고 실무적인 방어 기법까지 차근차근 파헤쳐 보겠습니다!
1. 개념 설명: 권한 검증이 왜 불충분해질까?
**'불충분한 권한 검증'**이란, 애플리케이션이 요청을 처리할 때 요청자가 해당 자원(데이터)이나 기능에 접근할 수 있는 적절한 권한을 가지고 있는지 서버 측에서 올바르게 검증하지 않아 발생하는 취약점입니다.
공격 패턴에 따라 크게 수평적 권한 상승과 수직적 권한 상승 두 가지로 나뉩니다.
[권한 상승의 두 가지 유형]
1. 수평적 권한 상승 (Horizontal Privilege Escalation / IDOR)
[일반 사용자 A] <====== (타인의 영역 침범) ======> [일반 사용자 B]
* 동등한 권한을 가진 다른 사람의 개인정보, 작성 글 등을 조회/수정/삭제
2. 수직적 권한 상승 (Vertical Privilege Escalation)
[관리자 (Admin)]
▲
│ (상위 권한 기능 직접 호출)
[일반 사용자 (User)]
* 일반 사용자가 관리자 전용 메뉴, 회원 강퇴, 시스템 설정 변경 등 실행
① 수평적 권한 상승 (Horizontal Privilege Escalation)
- 개념: 동일한 권한 등급을 가진 다른 사용자의 데이터에 접근하는 행위입니다.
- IDOR (Insecure Direct Object References, 취약한 직렬 객체 참조): 수평적 권한 상승의 대표적인 형태입니다. URL 파라미터, Form 데이터, Cookie에 포함된 사용자 식별자(예:
userId=1001)를 다른 값(userId=1002)으로 단순 변경했을 때, 서버가 세션 사용자와 요청 파라미터의 일치 여부를 검증하지 않고 그대로 데이터를 보여줄 때 발생합니다.
② 수직적 권한 상승 (Vertical Privilege Escalation)
- 개념: 하위 권한을 가진 사용자(예: 일반 회원)가 상위 권한(예: 관리자)을 가진 사용자의 기능이나 데이터에 접근하는 행위입니다.
- 원인: 웹 화면상에서 관리자 버튼을 숨겨 놓기만 하고, 백엔드 API URL(
POST /api/admin/deleteUser)에 직접 접근할 때 **"이 요청을 보낸 사람이 진짜 관리자인가?"**를 체크하는 서버 측 RBAC(Role-Based Access Control) 로직이 빠져있기 때문에 발생합니다.
2. 취약점 예시 및 시나리오 분석
실제 코드 수준에서 어떻게 취약점이 발생하는지, 그리고 이를 어떻게 수정해야 하는지 비교해 보겠습니다.
예시 1: 수평적 권한 상승 (IDOR) - 마이페이지 조회 API
❌ 취약한 코드 (Node.js / Express 예시)
// [취약] 요청 파라미터의 id를 그대로 DB 조회에 사용
app.get('/api/user/profile', async (req, res) => {
// Client가 보낸 query parameter: /api/user/profile?id=2004
const requestedUserId = req.query.id;
// 로그인 여부(인증)만 확인하고, 세션 주입 사용자와 requestedUserId의 일치 여부는 확인 안 함!
if (!req.session.isLoggedIn) {
return res.status(401).send("로그인이 필요합니다.");
}
// 서버는 세션 소유자 구분 없이 파라미터 id로 사용자를 조회하여 반환
const userProfile = await db.query('SELECT * FROM users WHERE id = ?', [requestedUserId]);
res.json(userProfile);
});
공격 시나리오:
로그인한 공격자(ID: 1001)가 본인 프로필 조회 요청(/api/user/profile?id=1001)의 파라미터를id=1002로 변경하여 전송합니다. 서버는 세션 사용자가 1001임에도 불구하고 1002번 회원의 민감한 개인정보(주소, 전화번호, 결제정보 등)를 그대로 응답합니다.
⭕ 안전한 코드 (Node.js / Express 예시)
// [안전] 클라이언트 파라미터를 신뢰하지 않고, '서버 세션'의 사용자 식별자를 이용
app.get('/api/user/profile', async (req, res) => {
// 1. 로그인 여부 확인
if (!req.session.isLoggedIn || !req.session.user) {
return res.status(401).send("로그인이 필요합니다.");
}
// 2. 파라미터 대신 검증된 '세션 내의 사용자 ID'를 직접 사용
const loggedInUserId = req.session.user.id;
// DB에서 본인의 정보만 조회하도록 강제
const userProfile = await db.query('SELECT * FROM users WHERE id = ?', [loggedInUserId]);
res.json(userProfile);
});
개선점:
요청 URL의 파라미터를 완전히 제거하거나, 요청 파라미터가 오더라도req.session.user.id === requestedUserId인지 검증 로직을 추가하여 본인의 데이터에만 접근할 수 있도록 통제했습니다.
예시 2: 수직적 권한 상승 - 회원 탈퇴/삭제 기능
❌ 취약한 코드
// [취약] 인증(로그인 여부)만 확인하고 권한(관리자 여부)을 검증하지 않음
app.post('/api/admin/delete-user', checkAuthentication, async (req, res) => {
const targetUserId = req.body.userId;
// 바로 삭제 로직 실행!
await db.query('DELETE FROM users WHERE id = ?', [targetUserId]);
res.send("회원이 성공적으로 삭제되었습니다.");
});
⭕ 안전한 코드 (서버 측 RBAC 적용)
// [안전] 인증(checkAuthentication) 및 관리자 권한(checkAdminRole) 미들웨어 동시 적용
app.post('/api/admin/delete-user', checkAuthentication, checkAdminRole, async (req, res) => {
const targetUserId = req.body.userId;
await db.query('DELETE FROM users WHERE id = ?', [targetUserId]);
res.send("회원이 성공적으로 삭제되었습니다.");
});
// 권한 검증 미들웨어 구현 예시
function checkAdminRole(req, res, next) {
// 세션에 저장된 사용자의 역할(Role) 확인
if (req.session.user && req.session.user.role === 'ADMIN') {
next(); // 관리자 맞음 -> 다음 로직 진행
} else {
// 권한 없음 (403 Forbidden)
res.status(403).json({ error: "해당 기능에 대한 접근 권한이 없습니다." });
}
}
3. 실무 적용 가능한 보안 대책 (Countermeasures)
개발 및 보안 진단 실무에서 불충분한 권한 검증을 방지하기 위해 준수해야 할 핵심 원칙은 다음과 같습니다.
① "Never Trust Client Input" (클라이언트 입력값 불신)
- 화면상에서 UI 요소(버튼, 메뉴 등)를 숨기는 것은 보안 대책이 아닙니다. (시각적 은닉일 뿐입니다.)
- 공격자는 프록시 툴(Burp Suite 등)을 사용해 요청 파라미터, 쿠키, 헤더를 자유자재로 조작하므로 반드시 모든 권한 검증은 백엔드 서버에서 처리해야 합니다.
② 세션 기반의 식별자 매핑 검증
- 조회/수정/삭제 요청 시 전달되는 대상 객체의 식별자(예:
boardId,fileId,accountNo)가 현재 요청을 보낸 세션 소유자의 소유가 맞는지 DB 조회가 동반된 매핑 검증을 수행해야 합니다.
③ 중앙집중식 접근 제어 목록(ACL / RBAC) 구축
- API 엔드포인트마다 개별적으로 권한 검증 코드를 작성하면 누락될 확률이 매우 높습니다.
- 프레임워크 수준의 인터셉터(Interceptor), 미들웨어(Middleware), Spring Security, Annotation 등을 활용해 URL 패턴별 접근 권한(Role)을 중앙에서 통제하는 구조를 설계해야 합니다.
④ 간접 객체 참조 (Indirect Object Reference) 및 Random Token 사용
- 데이터베이스의 Sequence PK(1, 2, 3...)가 URL에 직접 노출되면 무차별 대입(Enumeration) 공격에 취약해집니다.
- PK를 직접 노출하는 대신 예측 불가능한 UUID/GUID를 사용하거나, 세션 내에 매핑 테이블을 두고 임시 토큰을 사용하는 방식을 검토합니다.
⑤ 기본 거부 (Deny by Default) 원칙
- 접근 제어 정책을 설정할 때는 모든 접근을 기본적으로 차단(Deny)하고, 명시적으로 허용된 권한과 역할에 대해서만 접근(Allow)을 허용하는 **최소 권한의 원칙(Principle of Least Privilege)**을 적용합니다.
4. 핵심 요약 (Summary)
| 구분 | 주요 내용 |
|---|---|
| 개념 | 인증된 사용자라 하더라도 본인 범위를 벗어난 자원/기능에 접근할 때 권한 체크가 누락되어 발생하는 취약점 |
| 수평적 상승 (IDOR) | 타인의 식별자(ID, 계좌번호 등) 조작을 통해 타인의 정보 조회/수정 |
| 수직적 상승 | 일반 사용자가 관리자 URL 직접 호출 등을 통해 관리자 기능 수행 |
| 핵심 원인 | 클라이언트 전달값 신뢰, 서버 측 Access Control (RBAC/ACL) 누락 |
| 보안 대책 | ① 서버 측에서 세션 정보와 요청 파라미터 간 소유권 검증<br>② 프레임워크 미들웨어를 통한 공통 권한 제어<br>③ Default Deny 정책 및 UUID 활용 |
오늘 다룬 **'불충분한 권한 검증'**은 OWASP Top 10에서도 수년간 1위(Broken Access Control)를 차지할 만큼 광범위하고 치명적인 취약점입니다. 개발 과정에서 "로그인했으니 안전하겠지?"라는 방심이 큰 보안 사고로 이어질 수 있다는 점을 꼭 기억해 주세요!
오늘 내용 중 궁금한 점이 있다면 언제든 댓글로 남겨주세요. 😉
🔜 다음 챕터 예고
내일 [Day 12]에서는 **취약한 비밀번호 복구 절차 (Insecure Password Recovery)**에 대해 공부합니다. 비밀번호 찾기/재설정 기능에서 흔히 발생하는 본인 인증 우회, 인증 번호 무차별 대입, 비밀번호 재설정 링크 오용 문제와 그 방어 기법을 자세히 알아보겠습니다.
열공하시느라 고생 많으셨습니다. 내일 또 만나요! 🔥
댓글 0
로그인하면 댓글을 작성할 수 있습니다.