이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- ABAC는 RBAC로 부족한 어떤 상황을 보완하는가?
- Resource Ownership과 tenant 경계를 서버에서 어떻게 검증해야 하는가?
- 속성 기반 권한 정책에서 클라이언트 입력을 믿으면 어떤 문제가 생기는가?
개요
ABAC는 Attribute Based Access Control이다. 권한 판단에 actor, resource, action, context의 속성을 함께 사용한다.
RBAC가 “이 사용자는 SUPPORT_AGENT role인가”를 묻는다면, ABAC는 더 구체적으로 묻는다.
actor.department == resource.assigned_department
resource.status != CLOSED
context.mfa_verified == true
context.request_time within support_hours
context.ticket_id assigned to actorResource Ownership은 ABAC에서 가장 자주 등장하는 속성이다. 누가 이 resource의 소유자인가, 같은 조직인가, 같은 tenant인가를 서버가 확인해야 한다.
공격 시나리오
고객센터 상담원은 자기에게 배정된 티켓의 회원 정보만 볼 수 있다.
공격자는 상담원 계정으로 로그인한 뒤 다음을 시도한다.
memberId를 유명인 계정으로 바꾼다.ticketId를 아무 값으로 넣는다.assigned=true같은 클라이언트 속성을 추가한다.- 업무 시간이 아닌 시간에 대량 조회한다.
서버가 role만 보고 SUPPORT_AGENT면 조회를 허용하면 모든 회원 정보가 노출된다.
ABAC가 필요한 이유는 role 하나로는 “이 상담원이 이 회원을 지금 볼 수 있는가”를 설명할 수 없기 때문이다.
취약한 요청/응답 예시
GET /admin/support/members/9182?ticketId=T-0000&assigned=true HTTP/1.1
Host: admin.example.com
Authorization: Bearer support-agent-tokenHTTP/1.1 200 OK
Content-Type: application/json
{
"memberId": 9182,
"email": "victim@example.com",
"phone": "010-1234-5678",
"securityNote": "fraud watch"
}서버가 assigned=true를 클라이언트 입력으로 믿거나 ticket 배정을 확인하지 않으면 ABAC는 동작하지 않는다.
주제별 핵심 판단
- ABAC는 actor, resource, action, context 속성을 함께 평가한다.
- Resource ownership은 body의 ownerId가 아니라 서버가 조회한 resource 속성에서 나온다.
개선된 요청/응답 예시
ABAC 정책은 속성 출처를 분명히 해야 한다.
- 개선된 조회는
resource.ownerId == principal.id나 tenant 조건을 서버 조회 결과로 확인한다. - 거부 로그에는 actor, resource, action, context, reason을 분리해 남긴다.
방어 설계
actor:
- account_id: authentication principal
- role: server permission store
- department: HR system or internal DB
resource:
- member_id: path variable
- assigned_ticket: support DB
- security_note: sensitive field
context:
- request_time: server clock
- mfa_verified: session state
- source_ip: trusted request metadata클라이언트가 보낸 속성은 후보값일 뿐이다. 권한 결정 속성은 서버가 조회하거나 계산해야 한다.
고객센터 조회 정책은 다음처럼 쓸 수 있다.
allow SUPPORT_AGENT read member_contact
if ticket is assigned to actor
and ticket.status == OPEN
and reason_code is present
deny member_security_note unless actor has FRAUD_ANALYST permissionSpring/HTTP 예시
@GetMapping("/admin/support/members/{memberId}")
public SupportMemberResponse readForSupport(
@AuthenticationPrincipal AdminPrincipal principal,
@PathVariable long memberId,
@RequestParam String ticketId,
@RequestParam String reasonCode) {
return supportMemberService.readMember(
new SupportReadCommand(principal.adminId(), memberId, ticketId, reasonCode));
}@Transactional(readOnly = true)
public SupportMemberResponse readMember(SupportReadCommand command) {
SupportAssignment assignment = supportAssignmentRepository.findOpenAssignment(
command.ticketId(),
command.memberId(),
command.actorAdminId()
).orElseThrow(() -> denied(command, "ticket_not_assigned"));
SupportPolicyDecision decision = supportPolicy.decide(
assignment.actor(),
assignment.member(),
Action.READ_MEMBER_CONTACT,
command.reasonCode());
audit.supportAccessDecision(command, decision);
if (decision.denied()) {
throw new AccessDeniedException("support access denied");
}
return supportMemberRepository.findMaskedMember(command.memberId());
}DB 조건에도 ownership 또는 assignment를 넣는다.
SELECT m.member_id, m.email_masked, m.phone_masked
FROM member_support_view m
JOIN support_ticket_assignment a ON a.member_id = m.member_id
WHERE m.member_id = ?
AND a.ticket_id = ?
AND a.agent_id = ?
AND a.status = 'OPEN';운영 로그와 감사 지점
level=WARN event=abac_denied result=blocked actor_id=17 actor_role=SUPPORT_AGENT resource_type=member resource_id=9182 action=read_member_contact ticket_id=T-0000 reason=ticket_not_assigned trace_id=9e130bc1level=INFO event=abac_allowed result=allowed actor_id=17 resource_type=member resource_id=7123 action=read_member_contact ticket_id=T-4421 reason_code=customer_call fields=email_masked,phone_masked trace_id=9e130bc2ABAC 로그는 정책에 사용된 주요 속성을 남겨야 한다. 단, 보안 메모 본문이나 전체 개인정보는 남기지 않는다.
실전 판단 기준
- support agent가 배정되지 않은 ticket으로 회원을 조회하면 거부되는가?
- 같은 role 사용자끼리 resource id를 바꿔도 접근할 수 없어야 한다.
- 시간, 조직, 상태 같은 context 조건은 테스트 데이터에 포함되어야 한다.
테스트 포인트
assigned=true,department=fraud같은 클라이언트 속성이 무시되는가?- ticket이 CLOSED 상태면 같은 상담원도 거부되는가?
- 보안 메모 조회에는 별도 permission이나 step-up이 필요한가?
- list 조회와 detail 조회가 같은 tenant/assignment 조건을 쓰는가?
- ABAC 거부 로그에 사용된 속성과 reason이 남는가?
위험 신호!
- role만 보고 모든 resource 접근을 허용한다.
- tenant id, owner id, assigned flag를 클라이언트가 보낸 값으로 사용한다.
- list API에는 tenant 조건이 있지만 detail API에는 없다.
- ABAC 정책이 코드 조건문에 흩어져 테스트하기 어렵다.
- 속성 출처가 문서화되어 있지 않다.
- 고객센터 대리 조회가 일반 조회 로그와 구분되지 않는다.
확인 질문
확인 질문
- ABAC가 필요한 대표 상황은 무엇인가?
- role만으로는 actor와 resource의 관계, 상태, 시간, MFA, 위험도를 설명하기 어려운 상황이다.
- Resource Ownership 검증은 어디에 들어가야 안전한가?
- Service policy와 Repository 조회 조건 모두에 principal context가 들어가는 편이 안전하다.
- 클라이언트가 보낸 속성을 ABAC에 쓰면 안 되는 이유는 무엇인가?
- 공격자가 속성을 조작해 정책 판단을 원하는 방향으로 바꿀 수 있기 때문이다.