특권
고친 사람 github-actions[bot]
특권은 보통의 사용자나 프로그램에게는 막혀 있는 일을 해도 된다는 허락입니다. 시스템 시계를 바꾸거나 남의 프로그램을 멈추는 일이 그런 일입니다. 운영체제는 특권을 가진 쪽이 보낸 요청만 이런 일에 통과시킵니다. 힘이 큰 만큼 꼭 필요한 곳에만 짧게 줍니다.
쉽고 빠른 이해
특권은 기계 전체에 영향을 주는 일을 해도 된다는 허락입니다. 서버의 시계를 바꾸는 일이 대표입니다. 시계는 모든 프로그램이 함께 보는 값이라 아무 프로그램이나 바꾸면 안 됩니다.
이런 허락을 따로 묶지 않으면 프로그램 하나의 실수가 기계 전체를 흔듭니다. 그래서 위험한 일은 특권을 가진 쪽만 하게 막아 둡니다.
어떻게 도나:
- 프로그램이 위험한 일을 운영체제에 부탁합니다
- 운영체제가 그 프로그램이 알맞은 특권을 가졌는지 봅니다
- 가졌으면 들어줍니다. 없으면 거절합니다
대가도 있습니다. 특권을 가진 프로그램이 뚫리면 공격자도 그 특권을 얻습니다. 그래서 특권을 쥔 코드는 작게, 쥐는 시간은 짧게 짜야 합니다. 그만큼 설계가 번거로워집니다.
상세
아파트 관리실에는 모든 집 문을 여는 마스터키가 있습니다. 주민의 열쇠는 자기 집 문만 엽니다. 마스터키는 관리소장처럼 정해진 사람만 들고 다닙니다.
특권(privilege)은 보통은 막혀 있는 일을 해도 된다고 주체에 붙여 둔 허락입니다. 주체는 요청을 보내는 쪽입니다. 사용자 계정이나 실행 중인 프로그램이 주체입니다. 마스터키를 관리소장이 들고 다니듯, 특권도 주체가 들고 다닙니다.
시스템 시계를 바꾸는 일이 대표 예입니다. 시계를 특권으로 묶는 까닭은 모두가 같이 보는 값이기 때문입니다. 로그에 찍히는 시각도, 인증서가 만료됐는지 따지는 판단도 이 시계를 봅니다. 아무 프로그램이나 시계를 돌려 놓으면 이 판단들이 한꺼번에 어긋납니다.
특권이 지키는 일에는 공통점이 있습니다. 한 사용자의 몫을 넘어 기계 전체나 다른 사용자에게 영향을 줍니다. 남의 파일 주인을 바꾸는 일, 다른 사용자의 프로그램을 멈추는 일, 네트워크 설정을 바꾸는 일이 그렇습니다.
시스템은 요청이 오면 먼저 보낸 쪽이 누구인지 확인합니다. 이 확인을 인증이라고 합니다. 누구인지 안 뒤에 그 주체가 무엇을 해도 되는지 정하는 일은 인가입니다.
특권은 인가가 들여다보는 것 가운데 하나입니다. 로그인에 성공했다고 특권이 따라오지는 않습니다.
권한과 특권
이 소절은 특권을 가장 헷갈리는 이웃인 권한과 견줍니다. 두 낱말은 섞여 쓰입니다. 허락이 어디에 적혀 있는지로 가르면 차이가 보입니다.
권한(permission)은 흔히 대상에 붙습니다. 「이 파일은 주인만 고칠 수 있다」처럼 파일이나 테이블 쪽에 적어 둡니다. 요청이 오면 시스템은 그 대상에 적힌 내용을 봅니다.
특권은 주체에 붙습니다. 「이 프로그램은 시스템 시계를 바꿔도 된다」처럼 요청하는 쪽이 들고 다닙니다. 시계에는 주인을 따질 파일이 없습니다. 대상 쪽에 적을 데가 없으니 주체 쪽에 적습니다.
| 권한 | 특권 | |
|---|---|---|
| 어디에 적나 | 대상 · 파일이나 테이블 | 주체 · 계정이나 프로그램 |
| 묻는 질문 | 이 대상에 이 일을 해도 되나 | 이 주체가 이런 종류의 일을 해도 되나 |
| 예 | 파일의 읽기·쓰기 표시 | 시계 바꾸기 · 남의 파일 주인 바꾸기 |
표의 구분은 느슨하게 쓰입니다. 데이터베이스는 테이블을 읽어도 되는지 같은 대상 쪽 허락도 특권이라고 부릅니다. 영어 문서에서 privilege 와 permission 을 만나면 허락이 대상 쪽에 적혔는지, 주체 쪽에 적혔는지를 한 번 더 따져 보면 됩니다.
특권 프로세스와 비특권 프로세스
이 소절은 유닉스 계열 운영체제가 특권을 다뤄 온 전통적인 방식을 봅니다. 구조가 단순해서 특권이 무엇인지 감을 잡기 쉽습니다.
프로세스는 실행 중인 프로그램 하나입니다. 운영체제는 프로세스마다 누구의 이름으로 도는지를 기억합니다.
유닉스 계열은 이 「누구」를 사용자 번호로 적습니다. 이 번호를 UID(User ID, 사용자 ID)라고 부릅니다.
전통적인 유닉스는 프로세스를 두 부류로만 나눕니다. UID 가 0 인 프로세스는 특권 프로세스입니다. 나머지는 비특권 프로세스입니다. UID 0 인 계정을 root 또는 슈퍼유저라고 부릅니다.
요청을 처리하는 쪽은 커널입니다. 커널은 운영체제의 중심부입니다. 파일을 열거나 시계를 바꾸는 요청을 받아 해냅니다. 특권 프로세스가 보낸 요청이면 커널은 권한 검사를 건너뜁니다.
아래 그림은 프로세스가 파일 하나를 고치려 할 때 커널이 어떤 순서로 가르는지 보입니다. 특권을 먼저 봅니다. 특권이 없을 때만 파일의 권한을 봅니다.
flowchart TD
R["프로세스가 파일을 고치려 한다"] --> Q{"UID 가 0 인가"}
Q -->|"예 · 특권 프로세스"| OK["허락한다"]
Q -->|"아니오"| P{"파일의 권한이 이 UID 에게 쓰기를 허락하나"}
P -->|"예"| OK
P -->|"아니오"| NO["거절한다"]
이 방식은 전부 아니면 전무입니다. 시계 하나만 바꾸려는 프로그램도 root 로 돌아야 합니다. root 로 돌면 시계뿐 아니라 기계 안의 모든 파일까지 손에 넣습니다.
특권을 잘게 쪼개기
이 소절은 root 하나에 몰린 특권을 조각으로 나누는 방법을 봅니다. 리눅스의 케이퍼빌리티를 예로 듭니다.
리눅스는 root 의 특권을 일의 종류별로 쪼개 두었습니다. 쪼갠 조각 하나하나를 케이퍼빌리티(capability)라고 부릅니다. 프로세스에는 필요한 조각만 골라 붙일 수 있습니다.
조각 하나를 보려면 포트 이야기가 먼저 필요합니다. 포트는 한 기계 안에서 네트워크 요청을 어느 프로그램이 받을지 가르는 번호입니다.
프로그램이 포트 번호 하나를 맡는 일을 바인드라고 합니다. 바인드를 해야 그 번호로 오는 요청이 이 프로그램에 닿습니다.
1024보다 작은 번호는 특권 포트라고 부릅니다. 웹의 443번처럼 널리 약속된 서비스가 쓰는 번호들입니다. 아무 프로그램이나 이 번호를 맡으면 그 서비스를 흉내 낼 수 있습니다. 그래서 유닉스 계열은 전통적으로 이 번호를 바인드할 때 특권을 요구합니다.
아래 표는 자주 만나는 조각 넷입니다.
| 케이퍼빌리티 | 허락하는 일 |
|---|---|
CAP_NET_BIND_SERVICE |
특권 포트에 바인드하기 |
CAP_SYS_TIME |
시스템 시계 바꾸기 |
CAP_CHOWN |
파일의 주인 바꾸기 |
CAP_KILL |
다른 사용자의 프로세스에 시그널 보내기 |
마지막 줄의 시그널은 운영체제가 프로세스에 보내는 짧은 알림입니다. 「멈춰라」가 대표라서, 남의 프로세스를 멈추는 일도 이 조각이 맡습니다.
조각으로 나눈 덕을 보는 예가 443번 포트만 열면 되는 웹 서버입니다. 이 서버에는 CAP_NET_BIND_SERVICE 하나만 붙이면 됩니다. 서버가 뚫려도 공격자는 포트를 맡는 힘만 얻습니다. 시계나 남의 파일에는 손을 못 댑니다.
특권을 내려놓기
이 소절은 특권이 필요한 프로그램이 위험을 줄이는 흔한 방법을 봅니다. 특권은 필요한 순간에만 쥐고, 그 뒤에는 버립니다.
조각으로 나누지 않는 전통적인 유닉스에서 443번 포트를 여는 웹 서버가 예입니다. 이 서버는 root 로 시작해서 포트부터 엽니다. 포트를 연 뒤에는 특권 없는 전용 계정으로 UID 를 바꿉니다. 그 뒤 들어오는 요청은 모두 특권 없는 상태에서 처리합니다.
UID 를 바꾸는 방법에는 잠시 내려놨다 되찾는 쪽과 되돌릴 수 없게 버리는 쪽이 있습니다. 되돌릴 수 없게 버린 프로세스는 스스로 특권을 되찾지 못합니다. 요청을 처리하는 코드의 버그로 공격자가 서버를 손에 넣을 수 있습니다. 그래도 공격자가 얻는 것은 특권 없는 계정 하나입니다.
사람이 잠깐 특권을 쓸 때도 같은 발상을 씁니다. 평소에는 일반 계정으로 일합니다. 특권이 필요한 명령 하나만 sudo 같은 도구로 root 로 돌립니다. sudo 는 허락된 사용자가 명령 하나를 다른 계정의 이름으로 실행하게 해 주는 도구입니다.
이 습관들을 묶는 원칙이 최소 권한입니다. 이름에 「권한」이 들어가지만, 「권한과 특권」 소절 끝에서 말한 느슨한 쓰임이라 둘을 가리지 않습니다. 주체에게는 그 일에 필요한 만큼만 특권을 주라는 원칙입니다. 특권을 쥐는 범위와 시간이 작을수록 사고가 났을 때 번지는 범위도 작아집니다.
데이터베이스의 특권
이 소절은 데이터베이스가 계정마다 특권을 어떻게 주고 거두는지 봅니다. 백엔드 애플리케이션이 데이터베이스에 접속할 때 바로 부딪히는 대목입니다.
데이터베이스는 SQL(Structured Query Language, 구조화 질의 언어) 문장으로 특권을 다룹니다. GRANT(grant, 주다) 문장으로 줍니다. REVOKE(revoke, 거두다) 문장으로 거둡니다.
GRANT SELECT ON orders TO report_user;
REVOKE SELECT ON orders FROM report_user;
첫 줄은 report_user 계정에 orders 테이블을 읽는 특권을 줍니다. 둘째 줄은 같은 특권을 거둡니다. 이 특권은 테이블 하나에 묶여 있어서 「권한과 특권」 소절의 표로 보면 권한에 가깝습니다.
데이터베이스 전체에 걸친 특권도 있습니다. 계정을 새로 만들거나 다른 계정에 특권을 나눠 주는 일이 그렇습니다. 운영체제의 root 특권과 닮은 힘이라 소수의 관리 계정에만 줍니다.
애플리케이션 서버가 쓰는 계정에는 필요한 테이블의 읽기·쓰기 특권만 줍니다. 테이블을 지우거나 계정을 만드는 특권은 빼 둡니다. 그러면 SQL 인젝션으로 공격자가 DROP TABLE orders; 를 끼워 넣어도 데이터베이스가 거절합니다.
CPU 의 특권 수준
이 소절은 같은 낱말이 하드웨어에서 어떻게 쓰이는지 봅니다. 운영체제가 특권 검사를 지킬 수 있는 까닭이 이 계층에 있습니다.
CPU(Central Processing Unit, 중앙 처리 장치)도 지금 도는 코드가 어느 수준의 특권을 가졌는지 늘 압니다. 이 수준을 특권 수준이라고 부릅니다. 커널은 높은 수준에서 돕니다. 일반 프로그램은 낮은 수준에서 돕니다.
높은 수준에서만 실행되는 명령을 특권 명령이라고 합니다. 장치를 직접 다루는 명령이 그렇습니다. 낮은 수준의 코드가 이런 명령을 내면 CPU 가 실행을 멈추고 커널에 넘깁니다.
그래서 일반 프로그램은 시계를 바꾸고 싶어도 시계 장치를 직접 만지지 못합니다. 시스템 콜로 커널에 부탁해야 합니다. 시스템 콜은 프로그램이 커널에 일을 맡기는 정해진 입구입니다. 부탁을 받은 커널이 그 프로세스의 특권을 확인합니다.
root 로 도는 특권 프로세스도 여기서 다르지 않습니다. CPU 로 보면 root 프로세스도 낮은 수준에서 도는 일반 프로그램입니다. 운영체제의 특권은 시스템 콜을 받은 커널이 들어줄지 말지를 가를 때 쓰입니다.
하드웨어가 코드의 수준을 가르기 때문에 프로그램은 커널을 건너뛰지 못합니다. 앞 소절들의 특권 검사는 이 구분 위에 서 있습니다.
특권이 필요한 일과 필요 없는 일
이 소절은 백엔드 개발자가 언제 특권을 만나고 언제 피할 수 있는지를 정리합니다. 특권이 필요한 일은 생각보다 적습니다.
| 일 | 특권이 필요한가 |
|---|---|
| 요청을 받아 응답하기 | ✗ |
| 자기 계정의 파일 읽고 쓰기 | ✗ |
| 1024 이상 포트에 바인드하기 | ✗ |
| 특권 포트에 바인드하기 | ✓ · 전통적인 유닉스 계열 |
| 시스템 시계 바꾸기 | ✓ |
| 다른 사용자의 프로세스 멈추기 | ✓ |
애플리케이션 서버가 하는 일은 대부분 표의 위쪽입니다. 그래서 애플리케이션 서버를 root 로 돌릴 까닭은 대개 없습니다. root 로 돌리면 요청 처리 코드의 버그 하나가 기계 전체의 사고로 번집니다.
특권을 가진 코드는 공격자가 먼저 노리는 표적이기도 합니다. 비특권 계정으로 들어온 공격자는 버그를 타고 특권을 얻으려 합니다. 이런 공격을 권한 상승이라고 부릅니다. 이 이름도 권한과 특권을 가리지 않는 느슨한 쓰임입니다.
대가
특권을 잘게 나누면 관리할 것이 늘어납니다. 어느 프로그램에 어느 조각을 줬는지 기록하고 맞춰 두어야 합니다. 조각 하나가 빠지면 배포한 뒤에야 드러납니다. CAP_NET_BIND_SERVICE 를 빠뜨린 웹 서버는 443번 포트를 열다가 거절당해 멈춥니다.
특권을 내려놓는 구조도 코드를 복잡하게 만듭니다. 특권이 필요한 일을 시작 단계에 몰아서 끝내야 합니다. 중간에 특권이 다시 필요해지면 특권을 쥔 작은 프로그램을 따로 둡니다. 그리고 그쪽에 부탁하는 길을 새로 짜야 합니다.
관련 항목
특권을 판단하는 접근 제어 개념
인가 · 인증 · 인증과 인가 · 접근 제어 · 권한 · 역할 기반 접근 제어
특권을 줄이고 쪼개는 원칙과 장치
최소 권한 · 특권 분리 · 케이퍼빌리티 · sudo · setuid · 샌드박스
특권을 쥐는 계정과 프로세스
root · 슈퍼유저 · UID · 프로세스 · 서비스 계정 · 관리자 계정
특권을 떠받치는 CPU 장치
특권 수준 · 특권 명령 · 사용자 모드와 커널 모드 · 보호 링 · 커널 · 시스템 콜
특권이 있어야 다루는 자원
특권 포트 · 포트 · 바인드 · 시스템 시계 · 시그널
데이터베이스에서 특권을 주고 거두는 SQL 문
GRANT · REVOKE · SQL · DCL
특권을 노리는 공격
다른 이름: 프리빌리지