본문으로 건너뛰기
Paul's Dev Notes

ACL 평가 순서와 디버깅 함정 — Phase 3단(table·record·field), Specificity 우선, OR vs AND 결합

ServiceNow ACL 의 3단 phase 평가 (table → record → field), 각 phase 안 specificity 우선, Roles/Condition/Script 의 AND 결합, 같은 phase 다중 ACL OR, phase 간 AND. 권한 누수 함정과 Security Debug 활용 정리.

· note security
검증 인스턴스: OOTB Australia · 검증일 2026-05-22

개요

ServiceNow 의 ACL(Access Control List, 접근 제어 목록) 평가는 3단 phase 구조로 동작한다. Phase 1은 table-level, Phase 2는 record-level, Phase 3은 field-level 이며, 이 순서대로 순차 평가된다. 각 phase 를 통과하지 못하면 그 아래 phase 는 아예 실행되지 않는다.

각 phase 안에서는 specificity 가 높은 ACL(more specific)이 먼저 평가된다. 같은 phase, 같은 specificity 레벨에서 다중 ACL 이 존재하면 OR 결합으로 동작한다 — 하나라도 GRANT 를 반환하면 그 phase 는 통과된 것으로 간주한다. 반면 하나의 ACL 안에서 Roles, Condition, Script 는 AND 결합이다 — 셋 모두 통과해야 그 ACL 이 ALLOW 를 낸다. 그리고 phase 간은 AND 다 — table phase 도 통과하고 field phase 도 통과해야 실제 접근이 허용된다.

가장 자주 마주치는 함정은 phase 간 AND 를 무시한 보안 설계다. Field-level ACL 만 강화했는데 정작 Field ACL 자체가 존재하지 않아 table phase 통과만으로 접근이 허용되거나, 같은 phase 에 여러 ACL 을 추가했는데 기존의 약한 ACL 이 OR 로 묶여 강화 의도가 무력화되는 케이스가 실전에서 반복된다. 디버깅에는 Security Debug 모드와 sys_security_acl 테이블 직접 조회가 핵심 도구다.

OOTB(Out-of-the-Box, 기본 제공) Australia 기준입니다. 인스턴스 버전·플러그인 구성에 따라 동작이 달라질 수 있습니다.


§1 — ACL 구조 (Operation × Table.Field × Type)

모든 ACL 레코드는 sys_security_acl 테이블에 저장된다. 하나의 ACL 레코드는 크게 세 축으로 구성된다 — 어떤 작업(Operation), 어떤 오브젝트(Name, table.field 패턴), 어떤 오브젝트 유형(Type).

Operation 은 ACL 이 적용되는 작업 종류를 지정한다. record 대상 작업(read, write, create, delete)과 비-record 대상 작업(execute, UI Action·Script Include 호출 등)으로 나뉜다. Operation 이 다르면 별개의 ACL 로 평가된다 — incident 테이블에 read ACL 을 추가한다고 해서 write 가 막히는 것은 아니다.

Nametable.field 형태의 패턴이다. incident.priority 는 incident 테이블의 priority 필드에만, incident.* 는 incident 의 모든 필드에, *.* 는 플랫폼 전체에 적용된다. specificity 는 이 패턴의 구체성에 따라 결정된다.

Type 은 ACL 이 보호하는 오브젝트의 종류다. record, ui_page, processor, client_callable_script_include 등의 값을 가진다. 인스턴스 버전과 플러그인 구성에 따라 사용 가능한 Type enum 이 다를 수 있으므로, 현재 인스턴스의 sys_security_acl 테이블에서 실제 값을 확인하는 것이 안전하다.

Operation 분류

record 대상: read / write / create / delete

비-record 대상: execute (UI Action, Script Include 호출 등) — 각 operation 은 독립 ACL 로 평가

Type 분류

record / ui_page / processor / client_callable_script_include 등

인스턴스 버전·플러그인 구성에 따라 enum 차이 — sys_security_acl 직접 확인 권장

Name specificity 패턴

incident.priority (가장 구체) → incident.* (테이블 전체 필드) → . (플랫폼 전체)

specificity 가 높을수록 phase 안에서 먼저 평가됨


§2 — 평가 순서 (3-Phase + Specificity)

ACL 평가는 Phase 1 table-level → Phase 2 record-level → Phase 3 field-level 의 순서로 진행된다. 각 phase 를 통과해야 다음 phase 로 넘어간다. field-level ACL 이 아무리 엄격해도 table-level ACL 이 먼저 DENY 를 내면 field phase 자체가 실행되지 않는다.

각 phase 안에서는 가장 구체적인 ACL(most specific)이 먼저 평가된다. incident.priorityincident.* 보다 specificity 가 높고, incident.**.* 보다 높다. 평가 중 첫 번째 GRANT(true 반환) 가 나오면 그 phase 의 평가는 종료되고 통과 처리된다.

phase 안 다중 ACL 은 OR 결합이다 — 같은 specificity 레벨에서 여러 ACL 이 존재할 때 “at least 1 rule” 이 통과하면 그 phase 는 통과된다. 이 OR 동작이 §4에서 다루는 권한 강화 무력화의 원인이다.

phase 간은 AND 다 — 최종 접근 허용을 위해서는 table phase AND field phase 가 모두 통과돼야 한다. record-level phase 는 table 과 field 사이에서 추가 필터 역할을 한다.

ACL 평가 시작Phase 1 — Table-level ACLmost specific → least specific 순 평가, 첫 GRANT 로 phase 종료모두 DENY?DENYPASSPhase 2 — Record-level ACLrecord ACL 없으면 자동 통과 (인스턴스 정책 따라 다름)모두 DENY?DENYPASSPhase 3 — Field-level ACLfield ACL 없으면 table 통과 기준 적용 (인스턴스 정책 따라 다름)PASSDENY접근 거부접근 허용

record-level phase 는 레코드 단위의 조건 — 예컨대 “자신이 할당된 레코드만 read 가능” 같은 동적 조건 — 을 적용하는 층이다. record ACL 이 없는 경우의 동작은 인스턴스 보안 정책에 따라 다를 수 있으므로 단정하기 어렵다.


§3 — Roles + Condition + Script 결합 (한 ACL 안 → AND)

하나의 ACL 레코드 안에는 세 가지 컴포넌트가 있다 — Roles, Condition, Script. 이 셋은 AND 결합이다. 셋 중 하나라도 실패하면 그 ACL 은 DENY 로 간주된다.

Roles 는 이 ACL 이 적용되는 역할 목록이다. 여러 Role 이 나열되면 그것들은 OR 결합이다 — 나열된 Role 중 하나라도 사용자가 보유하면 Roles 단계는 통과된다. 빈 Roles 의 의미는 일반적으로 “모든 사용자(role 무관)“로 해석되지만, 인스턴스 보안 정책에 따라 다르게 설정될 수 있으므로 단정하기 어렵다. 빈 Roles ACL 이 의도치 않게 모든 사용자에게 접근을 허용하는 케이스가 실전에서 발생하므로 주의가 필요하다.

Condition 은 플랫폼 Condition Builder 로 작성하는 필터 조건이다. 현재 레코드·컨텍스트를 기준으로 동적 조건을 걸 수 있다.

Script 는 가장 유연한 컴포넌트다. answer 변수에 true 또는 false 를 할당하는 방식으로 결과를 반환한다.

// ACL Script 컴포넌트 예시 — answer 에 true/false 할당
(function executeRule(/*GlideRecord*/ current, /*GlideRecord*/ advanced) {

    // 예: 할당자가 현재 사용자이거나 assignment_group 멤버일 때만 write 허용
    var userId = gs.getUserID();

    if (current.assigned_to == userId) {
        answer = true;
        return;
    }

    // group membership 확인
    var grp = new GlideRecord('sys_user_grmember');
    grp.addQuery('group', current.assignment_group);
    grp.addQuery('user', userId);
    grp.setLimit(1);
    grp.query();
    answer = grp.hasNext();

})(current, null);

Script ACL 에서 answer 를 명시적으로 할당하지 않으면 기본값이 false 로 처리되는 것이 일반적인 동작이나, 인스턴스 구성에 따라 달라질 수 있으므로 명시적 할당을 항상 권장한다. 또한 Script ACL 안에서 무거운 GlideRecord 쿼리를 실행하면 record 조회·리스트 뷰 로드 성능에 직접 영향을 미친다.

ACL 의 setWorkflow(false) 우회는 별도 영역이다 — Business Rule 무한 루프와 setWorkflow(false) 우회 에서 다룬 setWorkflow 는 자동화(BR/Flow/Notification)를 차단할 뿐 ACL 평가는 우회하지 못한다. 보안 경계와 자동화 차단은 명확히 다른 메커니즘임을 기억해야 한다.

RolesRole 목록 내 OR하나라도 보유하면 통과ANDConditionCondition Builder동적 레코드 조건ANDScriptanswer = true/false가장 유연한 커스텀GRANT셋 중 하나라도 실패 → 이 ACL 은 DENY (다음 ACL 로 OR 이동)

§4 — 다중 ACL OR vs Phase 간 AND (권한 누수 핵심)

같은 phase 다중 ACL → OR

같은 phase, 같은 specificity 레벨에서 여러 ACL 이 존재할 때, 그것들은 OR 결합으로 평가된다. 이 동작이 실전에서 가장 많이 오해되는 지점이다.

예를 들어 incident 테이블의 read operation 에 대해 table-level ACL 이 두 개 존재한다고 하자. ACL-A 는 itil role 이 있는 사용자에게만 read 허용, ACL-B 는 admin role 에게 허용한다. itil role 이 없는 일반 사용자가 ACL-B 의 기준을 만족하면 table phase 를 통과한다. 여기까지는 의도된 설계일 수 있다. 문제는 기존에 범위가 넓은 ACL-B 가 이미 존재하는 상황에서 “강화 목적으로” ACL-A 를 추가해도 OR 결합 때문에 ACL-B 가 여전히 통과 경로가 된다는 점이다. 강화 목적의 ACL 추가가 효과 없는 이유가 이 OR 동작이다.

Phase 간 → AND

Phase 1(table) 과 Phase 3(field) 는 AND 결합이다. table phase 를 통과하고 field phase 도 통과해야 최종 접근이 허용된다. 이 AND 구조 자체가 문제가 아니라, field phase 가 평가될 ACL 자체가 없을 때 가 함정이다.

권한 누수 시나리오 1 — OR 강화 무력화

Field-level 에 엄격한 신규 ACL 을 추가했다. 그런데 같은 specificity 에 기존의 약한 ACL 이 이미 존재한다. OR 결합 때문에 약한 ACL 이 통과 경로로 남는다. 신규 ACL 이 DENY 를 내도 기존 ACL 이 GRANT 를 내면 field phase 는 통과된다. “조이려고 추가했는데 아무 효과가 없다” 는 현상의 원인이다.

올바른 접근은 기존 약한 ACL 을 수정(또는 제거)하거나, Condition/Script 로 기존 ACL 자체의 통과 조건을 좁히는 것이다. 새 ACL 추가만으로는 OR 구조에서 강화가 작동하지 않는다.

권한 누수 시나리오 2 — Field ACL 부재

Field-level deny 를 의도했지만 해당 field 에 대한 field-level ACL 이 아예 존재하지 않는다. Field ACL 이 없으면 field phase 는 일반적으로 table phase 결과를 따라간다 — table phase 를 통과한 사용자는 field 에도 접근된다. Field ACL 을 실제로 생성해야 비로소 field phase 가 독립적으로 평가된다. “필드에 보안을 걸었는데 안 막힌다” 의 절반은 Field ACL 이 없거나 잘못된 specificity 에 걸어둔 경우다.

Table Phase (같은 specificity 다중 ACL → OR)ACL-A (넓음)약한 조건, 기존 보유ACL-B (좁음)강화 목적 신규 추가ORTable Phase 통과Field PhaseField ACL 없음→ table phase 결과 따라감Field Phase 통과AND접근 허용Field ACL 부재로 table 결과 상속 — 의도치 않은 허용

§5 — Security Debug 도구 활용

ServiceNow 는 ACL 평가 흐름을 실시간으로 확인할 수 있는 Security Debug 모드를 제공한다. 일반적으로 System Diagnostics 메뉴 아래에서 접근할 수 있으나, 인스턴스 버전·구성에 따라 메뉴 경로가 다를 수 있으므로 정확한 경로는 본인 인스턴스에서 확인이 필요하다.

Security Debug 모드를 활성화하면 페이지 하단에 ACL 평가 로그가 표시된다. 어떤 ACL 이 평가됐는지, 각 ACL 의 Roles/Condition/Script 컴포넌트가 어떤 결과를 냈는지, 최종적으로 어느 phase 에서 GRANT/DENY 가 결정됐는지를 추적할 수 있다. 권한 문제가 발생했을 때 “어떤 ACL 이 왜 통과됐는가” 를 역추적하는 데 가장 직접적인 도구다.

sys_security_acl 테이블에서 직접 검색하는 것도 디버깅에 유효하다. Name 필드에 incident.*incident 패턴, *.* 패턴이 혼재할 수 있으므로 쿼리 시 세 가지 specificity 레벨을 모두 확인하는 것이 안전하다. Operation 과 Type 조합으로 필터링하면 특정 시나리오에 적용되는 ACL 목록을 좁힐 수 있다.

레코드 폼에서 우클릭 컨텍스트 메뉴 또는 Personalize 옵션으로 “Security Rules” 또는 “Show ACLs” 와 유사한 메뉴가 노출되는 경우가 있다. 이 기능의 명칭과 가용 여부는 인스턴스 버전과 구성에 따라 다를 수 있다. sys_log_security 테이블이 활성화된 인스턴스에서는 ACL 평가 이력을 쿼리로 검색할 수 있으나, 활성화 여부와 보관 기간은 인스턴스 보안 정책에 따라 다르다.

List view 와 Form view 에서 평가되는 ACL 이 다를 수 있다 — 동일한 read operation 이어도 List view 에서는 테이블 전체를 스캔하는 맥락이고, Form 에서는 단일 레코드 컨텍스트이기 때문이다. 특정 뷰에서만 권한 문제가 재현된다면 이 차이를 의심해볼 수 있으나, 정확한 동작은 인스턴스 버전·구성에 따라 다를 수 있으므로 Security Debug 로 직접 확인하는 것이 가장 신뢰할 수 있는 방법이다.


§6 — 흔한 함정 체크리스트

빈 ACL (ACL 자체가 없는 테이블)

ACL 레코드가 없는 테이블의 접근 동작은 admin override 또는 인스턴스 보안 정책에 따라 결정된다. OOTB 기본 동작이 “접근 허용”인지 “거부”인지는 인스턴스 구성에 따라 다르므로, 신규 커스텀 테이블에는 명시적 ACL 을 생성하는 것이 안전하다.

OR 강화 무력화

같은 phase·specificity 에 약한 ACL 이 있으면 새 엄격한 ACL 은 OR 로 무력화된다 (§4 참조). 강화 목적이면 기존 ACL 의 조건을 직접 좁혀야 한다.

Field ACL 부재 → phase 간 AND 누수

특정 field 를 숨기거나 read 전용으로 만들려면 field-level ACL 이 실제로 존재해야 한다. Field ACL 이 없으면 field phase 가 독립 평가되지 않고 table phase 결과가 그대로 적용된다. sys_security_acl 에서 해당 테이블·필드 조합으로 ACL 레코드를 확인하는 것이 첫 번째 확인 단계다.

admin / security_admin 우회

admin role 이 ACL 을 우회하는 동작, 그리고 security_admin elevation 메커니즘은 인스턴스 보안 정책에 따라 다르게 구성될 수 있다. “admin 이니까 무조건 통과” 라는 전제는 인스턴스 구성에 따라 다를 수 있으므로 단정하지 말고 직접 검증해야 한다.

빈 Roles ACL 의 범위

Roles 가 비어있는 ACL 은 일반적으로 “role 무관, 모든 사용자” 로 해석된다. 의도치 않게 광범위한 접근을 허용하는 경우가 있으므로, 빈 Roles ACL 의 존재 여부와 그 Condition/Script 조건을 반드시 확인한다.

List view vs Record view ACL 차이

동일한 read operation 이어도 List view 와 Record(Form) view 에서 ACL 평가 맥락이 다를 수 있다. 한쪽에서만 재현되는 권한 문제는 이 차이를 의심하고, Security Debug 모드로 각 뷰에서 독립적으로 확인하는 것이 권장된다.


참조