리눅스 권한 체계
Linux의 사용자·그룹·기타 권한 판정과 chmod·chown·umask를 익히고 특수 권한과 sudo를 안전하게 사용합니다.
Linux는 많은 자원을 파일과 파일 디스크립터 인터페이스로 다룹니다.
일반 파일, 디렉터리, 장치, 소켓까지 파일로 표현됩니다.
따라서 파일 권한 체계가 곧 시스템 보호의 기초입니다.
이 권한 체계는 40년 넘게 Unix의 핵심으로 동작해 왔습니다.
사용자, 그룹, 기타
Linux 파일 권한은 세 범주로 나뉩니다.
- 소유자(Owner, u): 파일에 기록된 소유 UID의 사용자. 생성 후 chown으로 바뀔 수 있음
- 그룹(Group, g): 파일에 기록된 소유 GID에 속한 사용자들
- 기타(Others, o): 나머지 모든 사용자
각 범주에 세 가지 권한이 있습니다.
| 권한 | 비트 | 파일에 대한 효과 | 디렉터리에 대한 효과 |
|---|---|---|---|
| 읽기(r) | 4 | 파일 내용 읽기 | 디렉터리 목록 보기(ls) |
| 쓰기(w) | 2 | 파일 내용 수정 | 검색(x) 권한과 함께 항목 생성·삭제·이름 변경 |
| 실행(x) | 1 | 프로그램으로 실행 | 디렉터리에 진입(cd) |
디렉터리의 권한은 파일과 의미가 다릅니다.
디렉터리에 읽기 권한만 있으면 파일 목록은 볼 수 있지만, 파일의 내용이나 상세 정보(크기, 시각)는 볼 수 없습니다(inode 접근에 x가 필요).
실행 권한이 없으면 cd로 디렉터리에 진입할 수 없습니다.
목록을 보고 항목을 탐색하려면 보통 r과 x가 함께 필요합니다. 이름을 이미 알면 디렉터리의 x와 대상 파일 권한으로 읽을 수 있어, 디렉터리 r이 모든 파일 접근의 필수 조건은 아닙니다.
ls -la
# -rw-r--r-- 1 user group 1024 Jan 15 10:30 report.txt
# │├──┤├──┤├──┤
# │ u g o
# └ 파일 유형 (- = 일반, d = 디렉토리, l = 심볼릭 링크)
# drwxr-x--- 디렉토리: 소유자 rwx, 그룹 rx, 기타 접근불가rw-r--r--은 소유자가 읽기+쓰기, 그룹과 기타는 읽기만 가능하다는 뜻입니다.
숫자(8진수)로 표현하면 644입니다 (6=4+2, 4=4, 4=4).
권한 확인 순서
확장 ACL이나 특권 예외가 없는 일반적인 모드 비트 판정은 다음과 같습니다. Linux 파일 접근은 fsuid/fsgid를 사용하며 보통 유효 ID와 같습니다.
- 프로세스의 EUID(유효 사용자 ID)가 파일 소유자와 같으면 → 소유자 권한 적용
- 프로세스의 파일 접근 그룹 ID 또는 보조 그룹 중 하나가 파일 그룹과 같으면 → 그룹 권한 적용
- 둘 다 아니면 → 기타 권한 적용
중요: 소유자가 그룹에도 속하지만, 소유자 권한이 먼저 평가됩니다.
소유자 권한이 rw-이고 그룹 권한이 rwx인 경우, 소유자는 실행 불가입니다.
일반 Unix 모드 비트의 범주 선택
파일 접근 UID가 소유 UID와 같다. group의 x를 대체 적용하지 않는다. 각 단계의 값과 전환 이유를 비교합니다.
보호 · 일반 모드 비트
owner가 선택되면 group·others 권한을 더하지 않습니다
확장 ACL·특권 예외를 제외한 판정입니다. 파일 접근 UID가 소유 UID와 일치하면 owner를 선택합니다. owner가 rw-이고 group이 rwx라도 소유자의 실행 요청은 owner 비트로 거부됩니다.
확장 ACL·capability·MAC 등은 생략한 일반 모드 비트 판정입니다. Linux fsuid/fsgid는 보통 유효 ID와 같고 그룹 판정에는 보조 그룹도 포함합니다. 선택한 범주가 거부하면 다른 범주로 넘어가 권한을 더하지 않습니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 범주 확인부터 실행 거부까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | 일반 Unix 모드 비트의 범주 선택 |
| 노드·선 표기 | 아니요 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 범주 확인 파일 접근 UID가 소유 UID와 같다. |
| |
| 선택 비트 검사 owner의 x 비트만 읽는다. |
|
|
| 실행 거부 group의 x를 대체 적용하지 않는다. |
|
|
일반 Unix 모드 비트의 범주 선택
파일 접근 UID가 소유 UID와 같다. group의 x를 대체 적용하지 않는다. 각 단계의 값과 전환 이유를 비교합니다.
보호 · 일반 모드 비트
owner가 선택되면 group·others 권한을 더하지 않습니다
확장 ACL·특권 예외를 제외한 판정입니다. 파일 접근 UID가 소유 UID와 일치하면 owner를 선택합니다. owner가 rw-이고 group이 rwx라도 소유자의 실행 요청은 owner 비트로 거부됩니다.
확장 ACL·capability·MAC 등은 생략한 일반 모드 비트 판정입니다. Linux fsuid/fsgid는 보통 유효 ID와 같고 그룹 판정에는 보조 그룹도 포함합니다. 선택한 범주가 거부하면 다른 범주로 넘어가 권한을 더하지 않습니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 범주 확인부터 실행 거부까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | 일반 Unix 모드 비트의 범주 선택 |
| 노드·선 표기 | 아니요 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 범주 확인 파일 접근 UID가 소유 UID와 같다. |
| |
| 선택 비트 검사 owner의 x 비트만 읽는다. |
|
|
| 실행 거부 group의 x를 대체 적용하지 않는다. |
|
|
확장 ACL의 named user·group과 mask, capability·MAC·마운트 옵션 등은 이 단순 판정 밖에서도 영향을 줍니다. 자세한 규칙은 Linux ACL을 참고합니다.
chmod, chown, umask
# 숫자 방식 (8진수)
chmod 755 script.sh # rwxr-xr-x
chmod 600 secret.key # rw-------
chmod 644 readme.txt # rw-r--r--
# 기호 방식
chmod u+x script.sh # 소유자에 실행 권한 추가
chmod go-w secret.txt # 그룹과 기타에서 쓰기 권한 제거
chmod a+r public.txt # 모두(all)에게 읽기 권한 추가
chmod o= private.txt # 기타의 모든 권한 제거
# 재귀적 변경: 디렉토리와 이미 실행 가능한 파일에만 X 적용
chmod -R u+rwX,go+rX,go-w /var/www/html#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
int main() {
/* 파일 생성 후 권한 변경 */
int fd = open("test.txt", O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd == -1) { perror("open"); return 1; }
FILE *fp = fdopen(fd, "w");
if (!fp) { perror("fdopen"); close(fd); return 1; }
if (fprintf(fp, "sensitive data\n") < 0) {
perror("fprintf"); fclose(fp); return 1;
}
if (fclose(fp) != 0) { perror("fclose"); return 1; }
/* 소유자만 읽기/쓰기 가능하게 변경 */
if (chmod("test.txt", S_IRUSR | S_IWUSR) != 0) { /* 0600 */
perror("chmod"); return 1;
}
/* 권한 확인 */
if (access("test.txt", R_OK) == 0)
printf("읽기 가능\n");
if (access("test.txt", X_OK) != 0)
printf("실행 불가 (errno=%d)\n", errno);
return 0;
}# 소유자 변경에는 보통 CAP_CHOWN 특권이 필요
chown user:group file.txt
chown -R www-data:www-data /var/www # 재귀적 변경
# 그룹만 변경
chgrp developers project/C 예제는 기존 파일을 덮어쓰지 않고 처음부터 0600으로 생성합니다. 생성 후 chmod 전까지 비밀이 노출되는 구간을 피합니다. access는 실제 UID/GID 기준의 검사이며 이후 open 성공을 보장하는 사전 권한 검사로 사용하지 않습니다.
umask
umask는 새로 생성되는 파일의 기본 권한에서 제거할 권한을 지정합니다.
애플리케이션이 흔히 요청하는 생성 모드는 일반 파일 0666, 디렉터리 0777입니다. 기본 ACL이 없는 경우 결과는 요청 모드 & ~umask이며 단순 뺄셈이 아닙니다. 기본 ACL이 있으면 상속 규칙도 적용됩니다.
| umask | 파일 결과 | 디렉터리 결과 | 의미 |
|---|---|---|---|
| 022 | 644 (rw-r--r--) | 755 (rwxr-xr-x) | 기본 (기타 쓰기 방지) |
| 027 | 640 (rw-r-----) | 750 (rwxr-x---) | 기타 접근 완전 차단 |
| 077 | 600 (rw-------) | 700 (rwx------) | 소유자만 접근 |
umask # 현재 umask 확인 (보통 0022)
umask 027 # 설정: 기타 사용자의 모든 권한 제거
touch new_file.txt # 새 파일·기본 ACL 없음·0666 요청이면 640
mkdir new_dir # 기본 ACL 없음·0777 요청이면 750권한을 더하지 않는 선택과 비트 제거
모드 0464를 예로 보면 owner는 r--, group은 rw-, others는 r--입니다. 소유자이면서 파일 그룹에도 속한 호출자가 write를 요청해도 owner의 쓰기 비트가 없으므로 이 단순 모델에서는 거부합니다. 그룹 권한을 뒤에 더하는 판정이 아닙니다. 또한 요청 모드 0666과 umask 027의 결과는 0640입니다. 0666에서 0027을 산술로 뺀 값이 아니라 요청한 비트에서 mask의 비트를 제거합니다.
직접 판정: 확장 ACL·특권·MAC 예외를 제외하고 모드 0464의 파일을 소유자, 비소유 그룹 구성원, 그 밖의 사용자가 각각 write하려고 합니다. 세 결과를 구하세요. 기본 ACL 없이 요청 모드 0600·umask 022로 만드는 파일에 group 읽기가 생기나요? 추가로 named user deploy:rwx, ACL mask r-x인 별도 ACL 예에서 deploy의 write를 판정하세요.
직접 계산한 뒤 풀이 확인
owner는 r--이라 거부, 비소유 그룹 구성원은 rw-라 허용, others는 r--이라 거부입니다. 0600에서 022 비트를 제거해도 0600이어서 group 읽기는 생기지 않습니다. 별도 named-user ACL에서는 rwx와 mask r-x에 공통인 r-x만 유효해 write는 거부됩니다. ACL mask의 범위는 named user·group 계열이며 owner와 others에 일괄 적용하는 mask가 아닙니다.
SetUID, SetGID, Sticky Bit
일반 권한 외에 세 가지 특수 권한이 있습니다.
SetUID (4000)
SetUID가 적용되는 실행 파일은 exec 시 유효 UID가 파일 소유자 UID로 바뀝니다. nosuid 마운트·no_new_privs 등에서는 이 전환이 억제될 수 있습니다.
passwd 명령이 대표적입니다.
일반 사용자가 passwd를 실행하면 root 권한으로 /etc/shadow를 수정할 수 있습니다.
# SetUID가 설정된 파일 찾기
ls -la /usr/bin/passwd
# -rwsr-xr-x 1 root root 59640 ... /usr/bin/passwd
# ^ s = SetUID + 실행 권한
# 시스템에서 SetUID 파일 전체 검색 (보안 감사)
find / -perm -4000 -type f 2>/dev/null
# /usr/bin/passwd
# /usr/bin/sudo
# /usr/bin/su
# /usr/bin/newgrpSetUID는 편리하지만 보안 위험이 큽니다.
SetUID root 프로그램에 취약점이 있으면 root 권한 탈취로 이어집니다.
불필요한 SetUID는 제거해야 합니다.
SetGID (2000)
파일에 설정하면 실행 시 파일 그룹의 권한으로 실행됩니다.
디렉터리에 설정하면 더 유용합니다 — 그 안에 생성되는 모든 파일과 디렉터리가 부모 디렉터리의 그룹을 상속합니다.
# 팀 공유 디렉토리 설정
mkdir /shared/project
chgrp developers /shared/project
chmod 2775 /shared/project # SetGID + rwxrwxr-x
# 이제 누가 파일을 만들든 그룹은 developers
touch /shared/project/test.txt
ls -la /shared/project/test.txt
# -rw-rw-r-- 1 alice developers ... test.txtSticky Bit (1000)
디렉터리에 설정하면 삭제·이름 변경에 파일 소유자·디렉터리 소유자 또는 해당 검사를 우회할 특권이 추가로 요구됩니다. 기본 디렉터리 쓰기·검색 권한도 필요합니다.
/tmp에 설정되어 있어 다른 사용자의 임시 파일을 삭제하지 못하게 합니다.
ls -ld /tmp
# drwxrwxrwt 18 root root 4096 ... /tmp
# ^ t = Sticky Bit + 실행 권한
# Sticky Bit 없으면: /tmp에 쓰기 권한이 있는 모든 사용자가
# 다른 사용자의 파일도 삭제 가능 (매우 위험!)sudo와 root 권한 관리
root(UID 0)는 전통적인 DAC에서 강한 특권을 가지지만 모든 검사를 무조건 우회하지는 않습니다. capability 집합·사용자 namespace·SELinux 같은 MAC 정책·읽기 전용 마운트도 영향을 줍니다.
root로 직접 로그인하는 것은 위험합니다.
실수 하나(rm -rf / 오타)로 시스템 전체가 망가질 수 있습니다.
sudo는 일반 사용자가 특정 명령만 root 권한으로 실행할 수 있게 합니다.
/etc/sudoers 파일에 정책을 정의합니다.
# /etc/sudoers (visudo로만 편집!)
# 사용자 alice는 모든 대상 사용자·그룹으로 모든 명령 실행 가능
alice ALL=(ALL:ALL) ALL
# 사용자 deploy는 systemctl만 root로 실행 가능
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx, \
/usr/bin/systemctl restart app
# 위험한 위임의 예: root Docker 접근은 사실상 호스트 root 수준 권한
%dev ALL=(root) /usr/bin/dockersudo apt update # root 권한으로 패키지 업데이트
sudo -u postgres psql # postgres 사용자로 psql 실행
sudo -l # 내가 실행할 수 있는 명령 목록
# sudo 로그 확인 (감사 추적)
grep sudo /var/log/auth.logsudoers의 docker 예제는 명령 경로 하나를 허용한다고 권한이 작아지지 않는 사례입니다. Docker 데몬 접근은 root 수준 권한을 줄 수 있습니다. 서비스 재시작 위임도 서비스 파일·실행 코드의 수정 권한까지 검토해야 합니다.
보안 모범 사례
- root 직접 로그인 비활성화 (
/etc/ssh/sshd_config에서PermitRootLogin no) - sudo를 통해 최소한의 명령만 허용
- SSH 키 기반 인증 사용 (패스워드 비활성화)
- 2FA(Two-Factor Authentication) 적용
- 정기적으로 SetUID 파일 감사
프로세스 권한의 실제
프로세스에는 여러 ID가 있습니다.
| ID | 이름 | 용도 |
|---|---|---|
| RUID | 실제 사용자 ID | 프로세스를 시작한 사용자 |
| EUID | 유효 사용자 ID | 접근 제어 시 사용되는 ID |
| SUID | 저장된 사용자 ID | exec의 SetUID 적용 후 유효 UID를 저장 |
일반적으로 RUID = EUID입니다.
SetUID 파일을 실행하면 EUID가 파일 소유자 ID로 바뀝니다.
exec 후의 유효 UID가 saved set-user-ID에 저장됩니다. 예를 들어 일반 사용자가 SetUID root 바이너리를 실행하면 RUID는 일반 사용자, EUID와 saved ID는 0일 수 있습니다. 허용된 seteuid 전환으로 일시적으로 내렸다가 saved ID로 복귀할 수 있으며, 이는 특권을 영구 폐기한 것과 다릅니다.
#include <stdio.h>
#include <unistd.h>
int main() {
printf("Real UID: %d\n", getuid());
printf("Effective UID: %d\n", geteuid());
/* SetUID root 프로그램에서:
* getuid() = 1000 (일반 사용자)
* geteuid() = 0 (root) */
/* 일시적인 EUID 전환 예: saved ID를 없애는 영구 특권 폐기가 아님 */
/* seteuid(getuid()); */
return 0;
}다음 절에서는 OS를 위협하는 보안 공격과 방어 기법을 살펴보겠습니다.
파일 모드와 추가 속성의 실제 경계
파일 쓰기 권한을 제거해도 부모 디렉터리에 쓰기·검색 권한이 있으면 이름 삭제가 가능할 수 있습니다. 반대로 파일을 읽고 쓸 수 있어도 부모 디렉터리 쓰기가 없으면 삭제가 거절됩니다. 이 설명은 기본 DAC의 전제이며 sticky bit·immutable·ACL·MAC·마운트 정책이 추가로 영향을 줄 수 있습니다.
lsattr/chattr는 rwx와 다른 파일 시스템 추가 속성을 다룹니다. immutable은 소문자 i이고 +i로 설정, -i로 해제합니다. 해당 변경의 capability와 파일 시스템 지원이 필요하므로 컨테이너의 UID 0만으로 성공을 보장하지 않습니다. 기본 권한 계산과 지원 여부를 실제로 구분하는 격리 Linux 실습을 이어 진행합니다.