안동민 개발노트

본문 시작

리눅스 권한 체계

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이 모든 파일 접근의 필수 조건은 아닙니다.

권한_읽기.sh
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와 같습니다.

  1. 프로세스의 EUID(유효 사용자 ID)가 파일 소유자와 같으면 → 소유자 권한 적용
  2. 프로세스의 파일 접근 그룹 ID 또는 보조 그룹 중 하나가 파일 그룹과 같으면 → 그룹 권한 적용
  3. 둘 다 아니면 → 기타 권한 적용

중요: 소유자가 그룹에도 속하지만, 소유자 권한이 먼저 평가됩니다.

소유자 권한이 rw-이고 그룹 권한이 rwx인 경우, 소유자는 실행 불가입니다.

일반 Unix 모드 비트의 범주 선택
owner·group·others 중 하나 선택소유 UID가 같으면 owner, 아니면 파일 그룹 소속이면 group, 나머지는 others를 적용합니다. 어느 범주든 권한이 부족하면 거부합니다.파일 소유 UID 일치?owner파일 그룹 소속?기본 + 보조 그룹group둘 다 불일치others예아니오예아니오선택된 비트로 요청 연산 판정

확장 ACL·capability·MAC 등은 생략한 일반 모드 비트 판정입니다. Linux fsuid/fsgid는 보통 유효 ID와 같고 그룹 판정에는 보조 그룹도 포함합니다. 선택한 범주가 거부하면 다른 범주로 넘어가 권한을 더하지 않습니다.

확장 ACL의 named user·group과 mask, capability·MAC·마운트 옵션 등은 이 단순 판정 밖에서도 영향을 줍니다. 자세한 규칙은 Linux ACL을 참고합니다.


chmod, chown, umask

chmod_examples.sh
# 숫자 방식 (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
permission_demo.c
#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;
}
chown_examples.sh
# 소유자 변경에는 보통 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파일 결과디렉토리 결과의미
022644 (rw-r--r--)755 (rwxr-xr-x)기본 (기타 쓰기 방지)
027640 (rw-r-----)750 (rwxr-x---)기타 접근 완전 차단
077600 (rw-------)700 (rwx------)소유자만 접근
umask_demo.sh
umask              # 현재 umask 확인 (보통 0022)
umask 027          # 설정: 기타 사용자의 모든 권한 제거
touch new_file.txt # 새 파일·기본 ACL 없음·0666 요청이면 640
mkdir new_dir      # 기본 ACL 없음·0777 요청이면 750

SetUID, SetGID, Sticky Bit

일반 권한 외에 세 가지 특수 권한이 있습니다.

SetUID (4000)

SetUID가 적용되는 실행 파일은 exec 시 유효 UID가 파일 소유자 UID로 바뀝니다. nosuid 마운트·no_new_privs 등에서는 이 전환이 억제될 수 있습니다.

passwd 명령이 대표적입니다.

일반 사용자가 passwd를 실행하면 root 권한으로 /etc/shadow를 수정할 수 있습니다.

setuid_audit.sh
# 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/newgrp

SetUID는 편리하지만 보안 위험이 큽니다.

SetUID root 프로그램에 취약점이 있으면 root 권한 탈취로 이어집니다.

불필요한 SetUID는 제거해야 합니다.

SetGID (2000)

파일에 설정하면 실행 시 파일 그룹의 권한으로 실행됩니다.

디렉토리에 설정하면 더 유용합니다 — 그 안에 생성되는 모든 파일과 디렉토리가 부모 디렉토리의 그룹을 상속합니다.

setgid_directory.sh
# 팀 공유 디렉토리 설정
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.txt

Sticky Bit (1000)

디렉토리에 설정하면 삭제·이름 변경에 파일 소유자·디렉토리 소유자 또는 해당 검사를 우회할 특권이 추가로 요구됩니다. 기본 디렉토리 쓰기·검색 권한도 필요합니다.

/tmp에 설정되어 있어 다른 사용자의 임시 파일을 삭제하지 못하게 합니다.

sticky_bit.sh
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 파일에 정책을 정의합니다.

sudoers_examples.sh
# /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/docker
sudo_usage.sh
sudo apt update              # root 권한으로 패키지 업데이트
sudo -u postgres psql        # postgres 사용자로 psql 실행
sudo -l                      # 내가 실행할 수 있는 명령 목록

# sudo 로그 확인 (감사 추적)
grep sudo /var/log/auth.log

sudoers의 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저장된 사용자 IDexec의 SetUID 적용 후 유효 UID를 저장

일반적으로 RUID = EUID입니다.

SetUID 파일을 실행하면 EUID가 파일 소유자 ID로 바뀝니다.

exec 후의 유효 UID가 saved set-user-ID에 저장됩니다. 예를 들어 일반 사용자가 SetUID root 바이너리를 실행하면 RUID는 일반 사용자, EUID와 saved ID는 0일 수 있습니다. 허용된 seteuid 전환으로 일시적으로 내렸다가 saved ID로 복귀할 수 있으며, 이는 특권을 영구 폐기한 것과 다릅니다.

euid_demo.c
#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를 위협하는 보안 공격과 방어 기법을 살펴보겠습니다.