icon

안동민 개발노트

12장 : 보호와 보안

리눅스 권한 체계


Linux는 모든 것이 파일입니다. 일반 파일, 디렉토리, 장치, 소켓까지 파일로 표현됩니다. 따라서 파일 권한 체계가 곧 시스템 보호의 기초입니다. 이 권한 체계는 40년 넘게 Unix의 핵심으로 동작해 왔습니다.


사용자, 그룹, 기타

Linux 파일 권한은 세 범주로 나뉩니다.

  • 소유자(Owner, u): 파일을 만든 사용자
  • 그룹(Group, g): 소유자가 속한 그룹의 멤버들
  • 기타(Others, o): 나머지 모든 사용자

각 범주에 세 가지 권한이 있습니다.

권한비트파일에 대한 효과디렉토리에 대한 효과
읽기(r)4파일 내용 읽기디렉토리 목록 보기(ls)
쓰기(w)2파일 내용 수정파일 생성·삭제·이름 변경
실행(x)1프로그램으로 실행디렉토리에 진입(cd)

디렉토리의 권한은 파일과 의미가 다릅니다. 디렉토리에 읽기 권한만 있으면 파일 목록은 볼 수 있지만, 파일의 내용이나 상세 정보(크기, 시각)는 볼 수 없습니다(inode 접근에 x가 필요). 실행 권한이 없으면 cd로 디렉토리에 진입할 수 없습니다. 즉 디렉토리에 rx가 함께 있어야 실질적으로 내용을 볼 수 있습니다.

권한_읽기.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).

권한 확인 순서

프로세스가 파일에 접근할 때, 커널은 다음 순서로 권한을 확인합니다.

  1. 프로세스의 EUID(유효 사용자 ID)가 파일 소유자와 같으면 → 소유자 권한 적용
  2. 프로세스의 EGID가 파일 그룹과 같으면 → 그룹 권한 적용
  3. 둘 다 아니면 → 기타 권한 적용

중요: 소유자가 그룹에도 속하지만, 소유자 권한이 먼저 평가됩니다. 소유자 권한이 rw-이고 그룹 권한이 rwx인 경우, 소유자는 실행 불가입니다.


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   # 기타의 모든 권한 제거

# 재귀적 변경
chmod -R 755 /var/www/html
permission_demo.c
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

int main() {
    /* 파일 생성 후 권한 변경 */
    FILE *fp = fopen("test.txt", "w");
    fprintf(fp, "sensitive data\n");
    fclose(fp);

    /* 소유자만 읽기/쓰기 가능하게 변경 */
    chmod("test.txt", S_IRUSR | S_IWUSR);  /* 0600 */

    /* 권한 확인 */
    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
# 소유자와 그룹 변경 (root만 가능)
chown user:group file.txt
chown -R www-data:www-data /var/www    # 재귀적 변경

# 그룹만 변경
chgrp developers project/

umask

umask는 새로 생성되는 파일의 기본 권한에서 제거할 권한을 지정합니다. 파일의 기본 권한은 666, 디렉토리는 777입니다.

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 # 640으로 생성됨
mkdir new_dir      # 750으로 생성됨

SetUID, SetGID, Sticky Bit

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

SetUID (4000)

이 비트가 설정된 실행 파일은 실행 시 파일 소유자의 권한으로 실행됩니다. 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)는 모든 권한 검사를 우회합니다. root로 직접 로그인하는 것은 위험합니다. 실수 하나(rm -rf / 오타)로 시스템 전체가 망가질 수 있습니다.

sudo는 일반 사용자가 특정 명령만 root 권한으로 실행할 수 있게 합니다. /etc/sudoers 파일에 정책을 정의합니다.

sudoers_examples.sh
# /etc/sudoers (visudo로만 편집!)

# 사용자 alice는 모든 명령을 root로 실행 가능
alice ALL=(ALL:ALL) ALL

# 사용자 deploy는 systemctl만 root로 실행 가능
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx, \
                            /usr/bin/systemctl restart app

# dev 그룹은 docker 명령만 실행 가능
%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

보안 모범 사례

  • root 직접 로그인 비활성화 (/etc/ssh/sshd_config에서 PermitRootLogin no)
  • sudo를 통해 최소한의 명령만 허용
  • SSH 키 기반 인증 사용 (패스워드 비활성화)
  • 2FA(Two-Factor Authentication) 적용
  • 정기적으로 SetUID 파일 감사

프로세스 권한의 실제

프로세스에는 여러 ID가 있습니다.

ID이름용도
RUID실제 사용자 ID프로세스를 시작한 사용자
EUID유효 사용자 ID접근 제어 시 사용되는 ID
SUID저장된 사용자 IDSetUID 실행 시 원래 EUID 저장

일반적으로 RUID = EUID입니다. SetUID 파일을 실행하면 EUID가 파일 소유자 ID로 바뀝니다. 프로그램은 seteuid()로 SUID에 저장된 원래 권한으로 돌아갈 수 있습니다.

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) */

    /* 특권 작업 완료 후 권한 내려놓기 (보안!) */
    /* seteuid(getuid()); */

    return 0;
}

다음 절에서는 OS를 위협하는 보안 공격과 방어 기법을 살펴보겠습니다.

목차