infra
Platform

모듈 맵

[Linux] chmod/chown으로 파일 읽기·쓰기·실행 권한 완벽 제어

0 / 37 완료

펼치기
0 / 37 완료0%

리눅스 서버 운영 · 06 / 37

[Linux] chmod/chown으로 파일 읽기·쓰기·실행 권한 완벽 제어

Linux 파일 권한 체계를 이해하고 chmod, chown으로 권한을 제어합니다

🚨INCIDENT ALERT
HIGH

새벽 1시, 배포 직후 모니터링 알림이 울렸습니다. 앱이 설정 파일을 읽지 못해 시작에 실패하고 있습니다. 로그를 보니 PermissionError: [Errno 13] Permission denied: '/opt/myapp/config/app.yml'. 배포 스크립트에서 파일을 복사한 뒤 chown 단계를 빠뜨렸고, 서비스 계정이 설정 파일에 접근하지 못하는 상황입니다.

ls -la 한 줄로 문제가 어디에 있는지 보이고, chmod + chown 두 줄로 해결됩니다. 파일 권한 체계를 이해하면 Permission denied가 더 이상 미스터리가 아닙니다.

파일 권한 (File Permissions)

이번 챕터에서 배울 것
  • 1ls -la 출력을 읽고 rwx 권한과 소유자·그룹·기타 구분을 설명할 수 있다
  • 2chmod로 숫자 모드와 심볼릭 모드를 사용해 권한을 변경할 수 있다
  • 3chown, chgrp으로 파일의 소유자와 그룹을 변경할 수 있다
  • 4SUID, SGID, Sticky Bit의 동작을 이해하고 공유 디렉토리를 설계할 수 있다
  • 5umask로 새 파일의 기본 권한을 제어하고 ACL로 세밀한 권한을 부여할 수 있다
실습 환경 준비
권한 확인 명령어 사전 점검
ls -la /etc/passwd /etc/shadow /usr/bin/passwd
ACL 도구 설치 여부 확인
which getfacl setfacl || sudo apt-get install -y acl
실습용 테스트 파일 생성
touch ~/test-perms.sh && mkdir -p ~/shared-dir
현재 umask 값 확인
umask

Linux 파일 권한은 9비트로 표현됩니다. `ls -la` 출력의 첫 번째 필드를 읽는 법을 익히는 것이 출발점입니다.

ls -l 권한 분해 — 맨 앞 1글자는 파일 타입(-/d/l), 이어지는 9글자는 owner(rwx)·group(r-x)·others(r--) 각 3비트 권한확대

각 기호가 파일과 디렉토리에서 의미하는 바가 다릅니다. 특히 x 권한은 파일이면 실행, 디렉토리이면 진입(cd) 허용을 뜻한다는 점을 기억하세요.

기호숫자파일에서의 의미디렉토리에서의 의미
r4파일 내용 읽기디렉토리 목록 조회 (ls)
w2파일 내용 수정디렉토리 안에 파일 생성/삭제
x1파일 실행디렉토리 진입 (cd)
-0권한 없음권한 없음

숫자 표기법: 각 주체의 권한을 더해서 표현합니다.

  • rwx = 4+2+1 = 7
  • r-x = 4+0+1 = 5
  • r-- = 4+0+0 = 4
  • 755 = owner(rwx=7) + group(r-x=5) + others(r-x=5)
  • 644 = owner(rw-=6) + group(r--=4) + others(r--=4)
  • 600 = owner(rw-=6) + group(---=0) + others(---=0)

실무에서 가장 자주 쓰는 조합:

  • 755 — 실행 가능한 스크립트, 디렉토리
  • 644 — 설정 파일, 웹 콘텐츠
  • 600 — SSH 키, 비밀번호 파일
  • 640 — 그룹 구성원만 읽어야 하는 로그
  • 777 — 절대 프로덕션에서 쓰면 안 됨

일반 rwx 9비트 외에 3개의 특수 비트가 있습니다. 보안 감사와 공유 디렉토리 설계에서 반드시 알아야 합니다.

SUID (Set User ID) — 비트값 4000

SUID가 설정된 실행 파일은, 실행하는 사람의 권한이 아닌 파일 소유자의 권한으로 실행됩니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part1/exam_5 && cd /tmp/linux/part1/exam_5

# passwd 명령이 대표적인 SUID 예시
ls -la /usr/bin/passwd
# -rwsr-xr-x  1  root  root  ...  /usr/bin/passwd
#    ^
#    s = SUID 비트 (소문자 s = 실행 권한 있음, 대문자 S = 실행 권한 없음)

일반 사용자는 /etc/shadow를 읽거나 쓸 수 없지만, passwd 명령은 root 소유에 SUID가 설정되어 있어 실행 시 root 권한으로 /etc/shadow를 수정할 수 있습니다.

보안 함의: SUID가 설정된 파일이 취약하면 권한 상승(privilege escalation) 공격 경로가 됩니다. 정기적으로 목록을 감사해야 합니다.

로컬 터미널
# 시스템 전체에서 SUID 파일 찾기 (보안 감사)
find / -perm -4000 -type f 2>/dev/null

SGID (Set Group ID) — 비트값 2000

  • 실행 파일에 설정: SUID와 동일하게 그룹 기준으로 동작
  • 디렉토리에 설정: 해당 디렉토리 안에 생성되는 모든 파일이 디렉토리의 그룹을 상속
로컬 터미널
# 공유 디렉토리 설정 예시
ls -ld /opt/shared/
# drwxrwsr-x  2  root  devteam  4096  ...  /opt/shared/
#       ^
#       s = SGID 비트

Sticky Bit — 비트값 1000

디렉토리에 설정 시, 해당 디렉토리 안의 파일은 파일 소유자나 root만 삭제 가능합니다. 다른 사용자가 쓰기 권한이 있어도 남의 파일은 지울 수 없습니다.

로컬 터미널
# /tmp가 대표 예시
ls -ld /tmp
# drwxrwxrwt  ...  /tmp
#          ^
#          t = sticky bit (소문자 t = 실행 권한 있음)

/tmp는 모든 사용자가 파일을 만들 수 있지만(rwxrwxrwx), sticky bit 덕분에 자기 파일만 지울 수 있습니다.

특수 비트 숫자 표기법:

특수 비트는 일반 3자리 숫자 앞에 한 자리를 추가해서 표현합니다. 4000은 SUID, 2000은 SGID, 1000은 Sticky Bit입니다.

로컬 터미널
chmod 4755 /usr/local/bin/mytool   # SUID + 755
chmod 2775 /opt/shared             # SGID + 775
chmod 1777 /tmp                    # Sticky + 777

커널이 rwx를 판정하는 순서 — owner→group→other

💡개념

파일 하나를 열 때 커널이 권한을 판정하는 순서 — 클래스 매칭 4단계

cat app.yml 한 줄, 혹은 프로그램이 파일을 여는 open() 한 번. 이때 커널은 rwx 9비트를 한꺼번에 보는 게 아니라 소유자→그룹→others 순으로 딱 한 클래스만 골라 그 3비트로 허용 여부를 정합니다. 이 순서를 알면 "왜 소유자인데 거부되지", "others엔 권한이 있는데 왜 나는 막히지"가 한 번에 풀립니다. 권한 판정은 아래 흐름의 결과입니다.

TEXT
[프로세스]  open("/opt/app/app.yml", O_RDONLY)   euid=1000(deploy)
   │
   ① 파일 소유 uid == 프로세스 euid ?
   │      예    → owner 3비트(rwx)로 판정하고 즉시 끝
   │      아니오 ↓
   ② 파일 소유 gid 가 내 그룹(주·보조)에 있나 ?
   │      예    → group 3비트(rwx)로 판정하고 즉시 끝
   │      아니오 ↓
   ③ 그 외     → others 3비트(rwx)로 판정
   │
   ④ 고른 3비트에 요청 동작(r·w·x)이 있으면 허용
   │      없으면 즉시 EACCES (Permission denied)
   ▼
[결과]  접근 허용   또는   Permission denied

각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:

단계하는 일여기서 막히면
① 소유자 판정파일의 소유 uid와 프로세스 euid를 비교. 같으면 owner 3비트만 적용하고 group·others는 아예 안 본다owner가 ---이면 group·others에 권한이 있어도 소유자는 거부 → Permission denied
② 그룹 판정소유자가 아니면 파일의 소유 gid가 프로세스의 주 그룹·보조 그룹에 있는지 확인. 맞으면 group 3비트만 적용방금 usermod -aG로 그룹에 넣었는데 기존 세션 gid가 갱신 안 됨 → 재로그인 전까지 group 권한 미적용
③ others 판정소유자도 그룹도 아니면 others 3비트 적용others가 ---이면 그 밖의 모든 사용자 거부 — 웹서버(다른 계정)가 640 파일을 못 읽는 게 이 경우
④ rwx 비트 검사고른 클래스 비트에 요청 동작이 있는지 확인 — 파일 r=내용 읽기·w=수정·x=실행, 디렉터리 r=목록(lsw=생성/삭제·x=진입(cd)경로 중간 디렉터리에 x가 없으면 그 안 파일에 권한이 아무리 넉넉해도 도달 불가(한 단계만 막혀도 차단)

첫 매칭에서 끝난다는 것이 핵심입니다 — 커널은 owner→group→others로 폴백하지 않고, 처음 걸린 클래스 하나로 판정을 마칩니다. 그래서 ----rwx---(owner ---, group rwx)인 파일은 소유자인 내가 오히려 거부되고 그 그룹 구성원은 통과합니다. 소유자가 남보다 권한이 적을 수 있는 것입니다. 여기에 두 예외가 얹힙니다 — passwd 같은 setuid 실행 파일은 실행 순간 프로세스 euid가 파일 소유자(root)로 승격돼 ①의 비교 대상이 바뀌고, setgid는 같은 원리를 그룹에, 공유 디렉터리의 sticky bit(/tmp1777)는 삭제만 따로 "본인·root만"으로 제한합니다. 그리고 새로 만들어지는 파일이 애초에 어떤 비트로 태어날지는 umask가 정합니다.

진단은 "내가 어느 클래스로 판정되는가"부터. ls -l로 파일의 소유 uid·gid와 세 클래스 비트를 읽고, id로 내 euid·소속 그룹을 확인해 내가 owner·group·others 중 무엇에 해당하는지 맞춰 봅니다. 경로 전체가 궁금하면 namei -l /opt/app/app.yml로 루트부터 각 단계의 소유·권한을 훑어 어디서 x가 끊기는지 찾고, stat app.yml로 8진수 권한과 소유자를 정확히 확인합니다.

1권한 읽기 & chmod 연습

세 파일의 권한 차이를 비교하면서 ls -la 출력을 읽는 법을 익힙니다.

로컬 터미널
ls -la /etc/passwd /etc/shadow /usr/bin/passwd
OUTPUT
-rw-r--r--  1  root  root    2847  Mar 26  /etc/passwd
-rw-r-----  1  root  shadow  1423  Mar 26  /etc/shadow
-rwsr-xr-x  1  root  root   68208  Mar 26  /usr/bin/passwd

shadow는 root 그룹만 읽을 수 있고, passwd 실행 파일에는 SUID(s)가 설정되어 있습니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part2/exam_1 && cd /tmp/linux/part2/exam_1
touch deploy.sh
ls -la deploy.sh
OUTPUT
-rw-r--r--  1  ubuntu  ubuntu  0  Mar 26  deploy.sh
로컬 터미널
# 심볼릭 모드로 권한 변경
chmod u+x deploy.sh          # 소유자에게 실행 권한 추가
chmod g-w deploy.sh          # 그룹에서 쓰기 권한 제거
chmod o=r deploy.sh          # others는 읽기만

# 또는 한 번에
chmod u+x,g=rx,o-rwx deploy.sh
ls -la deploy.sh
OUTPUT
-rwxr-x---  1  ubuntu  ubuntu  0  Mar 26  deploy.sh
ls -la /etc/passwd /etc/shadow /usr/bin/passwd
🔍실행 후 확인할 것
  • /etc/passwd 는 -rw-r--r-- (644) — 누구나 읽을 수 있지만 쓸 수 없습니다
  • /etc/shadow 는 -rw-r----- (640) — root 그룹만 읽을 수 있습니다
  • /usr/bin/passwd 의 s 는 SUID 비트 — root 권한으로 실행됨을 의미합니다
  • chmod u+x,g=rx,o-rwx 처럼 콤마로 여러 변경을 한 번에 적용할 수 있습니다
2chown으로 소유자·그룹 변경

배포 계정과 웹서버 계정이 디렉토리를 공유하는 실무 패턴을 연습합니다.

로컬 터미널
mkdir -p /tmp/linux/part2/exam_2 && cd /tmp/linux/part2/exam_2
mkdir -p app logs config

sudo useradd -r -s /sbin/nologin deploy 2>/dev/null || true

# 소유자와 그룹 동시 변경
sudo chown deploy:www-data app

# 소유자만 변경 (그룹 유지)
sudo chown deploy logs

# 그룹만 변경
sudo chown :www-data config

# 디렉토리 전체 재귀 변경 (-R)
sudo chown -R deploy:www-data /tmp/linux/part2/exam_2/

ls -la
sudo chown deploy:www-data app
🔍실행 후 확인할 것
  • chown user:group 형식으로 소유자와 그룹을 동시에 변경합니다
  • chown :group 처럼 콜론 앞을 비우면 그룹만 변경됩니다
  • -R 옵션은 하위 모든 파일/디렉토리에 재귀 적용합니다 — 경로를 두 번 확인하세요
3SGID로 팀 공유 디렉토리 설정

SGID를 설정하면 공유 디렉토리에 생성된 파일이 디렉토리의 그룹을 자동 상속합니다.

로컬 터미널
mkdir -p /tmp/linux/part2/exam_3 && cd /tmp/linux/part2/exam_3

sudo groupadd devteam 2>/dev/null || true
sudo useradd -m -s /bin/bash alice 2>/dev/null || true
sudo usermod -aG devteam alice

mkdir -p shared
sudo chgrp devteam shared
sudo chmod 2775 shared
ls -ld shared
OUTPUT
drwxrwsr-x  2  ubuntu  devteam  4096  ...  shared
로컬 터미널
# alice가 파일 생성 후 그룹 확인
sudo -u alice touch shared/alice_report.txt
ls -la shared/
OUTPUT
-rw-rw-r--  1  alice  devteam  0  ...  alice_report.txt

alice의 기본 그룹이 아닌 devteam 이 자동 설정됩니다.

sudo chmod 2775 shared
🔍실행 후 확인할 것
  • ls -ld 출력에서 그룹 실행 권한 위치에 s 가 보이면 SGID가 설정된 것입니다
  • SGID 없이 alice 가 파일을 만들면 alice 의 기본 그룹이 설정되어 bob 이 접근하지 못합니다
  • chmod 2775 = SGID(2000) + rwxrwxr-x(775) 입니다
4보안 감사 — SUID 파일 목록 확인

정기 보안 감사에서 SUID/SGID 파일과 world-writable 파일을 점검합니다.

로컬 터미널
# SUID 파일 목록 (권한 상승 공격 경로)
find / -perm -4000 -type f 2>/dev/null

# world-writable 파일 (others에 쓰기 권한)
find /var/www -perm -0002 -type f 2>/dev/null

# 소유자 없는 파일 (삭제된 계정의 파일)
find /home -nouser 2>/dev/null
find / -perm -4000 -type f 2>/dev/null
🔍실행 후 확인할 것
  • find 결과를 파일로 저장해두면 다음 감사 시 diff 로 변경 사항을 추적할 수 있습니다
  • 예상치 못한 SUID 파일이 나타나면 권한 상승 공격 경로일 수 있으므로 즉시 조사합니다
  • 2>/dev/null 은 /proc, /sys 같은 의사 파일시스템의 Permission denied 오류를 숨깁니다

umask: 새 파일의 기본 권한

`umask`는 새 파일이나 디렉토리를 만들 때 자동으로 **제거할 권한 비트**를 지정합니다. "마스크"이므로 설정한 비트가 **없어지는** 권한입니다.

기본 생성 권한:

  • 파일: 666 (실행 권한은 기본 부여 안 함)
  • 디렉토리: 777

umask 계산은 산술 빼기가 아닌 비트 NOT AND 연산입니다. 파일 기본 퍼미션에서 umask를 비트 반전한 값과 AND를 취합니다: 기본값 & ~umask.

umask 계산 — 파일 기본값 666과 ~022(umask 반전)를 AND하면 644(rw-r--r--), 디렉토리 777 & ~022는 755(rwxr-xr-x)가 된다. 흔한 값은 022(644/755 기본)·027(640/750 타인 차단)·077(600/700 소유자 전용). 뺄셈이 아니라 비트 연산이라 umask 033은 666-033=633이 아니라 666&~033=644다. 파일은 실행권한을 기본 부여하지 않아 666에서, 디렉토리는 진입에 x가 필요해 777에서 시작한다확대

umask 022일 때:

파일:      666 & ~022 = 644  (rw-r--r--)
디렉토리:  777 & ~022 = 755  (rwxr-xr-x)

umask 027일 때:

파일:      666 & ~027 = 640  (rw-r-----)
디렉토리:  777 & ~027 = 750  (rwxr-x---)

단순한 경우엔 빼기 결과와 같지만, umask 033처럼 교차 비트가 있으면 달라집니다. 예를 들어 666 - 033 = 633이지만 실제 동작은 666 & ~033 = 644입니다.

로컬 터미널
# 현재 umask 확인
umask
# 0022

# 기호 형식으로 확인
umask -S
# u=rwx,g=rx,o=rx

# umask 변경 (현재 세션만)
umask 027

# 테스트
touch newfile.txt && ls -la newfile.txt
# -rw-r-----  ...  (640 확인)

# 영구 변경: ~/.bashrc 또는 /etc/profile에 추가
echo "umask 027" >> ~/.bashrc

실무 가이드라인:

  • 개인 개발 서버: 022 (기본값)
  • 보안이 중요한 서비스 계정: 027
  • 매우 민감한 환경: 077 (소유자만 접근)

서비스 계정의 umask를 강화하면 실수로 생성되는 파일의 권한 노출을 예방할 수 있습니다.

로컬 터미널
# 현재 umask 확인
umask
# 0022

# 파일 생성 후 기본 권한 확인
touch with_default_umask.txt
ls -la with_default_umask.txt
# -rw-r--r--  (644)

# umask를 027로 변경
umask 027

# 이제 새로 생성하는 파일의 권한 확인
touch with_secure_umask.txt
ls -la with_secure_umask.txt
# -rw-r-----  (640) — others는 접근 불가

# systemd 서비스에서 umask 설정 예시
# /etc/systemd/system/myapp.service 에 추가:
# [Service]
# UMask=0027

ACL: rwx만으로 부족할 때

전통적인 rwx 모델은 소유자/그룹/기타의 3단계만 지원합니다. ACL을 사용하면 **특정 사용자나 그룹에 개별적으로 권한**을 부여할 수 있습니다.

ACL이 필요한 상황:

  • nginx 프로세스(www-data)는 /app/config.yml을 읽어야 하는데, 파일 소유자는 deploy 팀 계정
  • 감사팀 사용자 auditor에게만 /var/log/app/의 읽기 권한 부여
  • CI/CD 봇 계정이 특정 파일만 쓸 수 있도록 제한
로컬 터미널
# ACL 조회
getfacl /app/config.yml

# 예시 출력:
# file: app/config.yml
# owner: deploy
# group: deploy
# user::rw-
# group::r--
# other::---
# user:www-data:r--     ← ACL로 추가된 항목

ls -la에서 ACL이 설정된 파일은 권한 필드 끝에 + 기호가 붙습니다:

-rw-r-----+  1  deploy  deploy  1024  ...  config.yml

ACL을 사용해 특정 서비스 계정에만 파일 접근 권한을 부여합니다.

로컬 터미널
# ACL 지원 여부 확인 (파일시스템이 ACL 마운트 옵션 필요)
mount | grep -i acl
# 또는
tune2fs -l /dev/sda1 | grep "Default mount options"

# 특정 사용자에게 읽기 권한 부여
setfacl -m u:www-data:r /app/config.yml

# 특정 그룹에게 읽기+실행 권한 부여
setfacl -m g:auditors:rx /var/log/app/

# 재귀적으로 디렉토리 전체에 ACL 적용
setfacl -R -m u:www-data:rx /app/static/

# 기본 ACL 설정 (새로 생성되는 파일에도 자동 적용)
setfacl -d -m u:www-data:r /app/config/

# ACL 확인
getfacl /app/config.yml

# ACL 제거 (특정 사용자)
setfacl -x u:www-data /app/config.yml

# ACL 전체 제거
setfacl -b /app/config.yml

실무 팁: ACL 설정 후 getfacl 출력을 파일로 저장해두면 나중에 setfacl --restore로 일괄 복원할 수 있습니다.

로컬 터미널
# 디렉토리 전체 ACL 백업
getfacl -R /app/ > /backup/app_acl_backup.txt

# 복원
setfacl --restore=/backup/app_acl_backup.txt

상황: 스크립트를 실행하려는데 위 오류가 납니다. 분명히 파일이 있는데 왜 실행이 안 될까요?

원인: 파일에 실행 권한(x)이 없거나, 현재 사용자가 파일 소유자가 아니라 실행 권한을 가진 주체(owner/group/others)에 해당하지 않습니다.

진단:

로컬 터미널
ls -la deploy.sh
OUTPUT
-rw-r--r--  1  ubuntu  ubuntu  ...  deploy.sh

권한 문자열에 x 가 하나도 없습니다.

해결:

로컬 터미널
# 소유자에게 실행 권한 추가
chmod u+x deploy.sh

# 디렉토리 진입이 안 될 때 (cd: Permission denied)
ls -ld /app/config
# drw-r--r--  (실행 권한 x 없음)
sudo chmod u+x /app/config

# 인터프리터를 직접 지정하면 chmod 없이 실행 가능
bash deploy.sh

상황: Nginx가 /var/www/html/app/index.html을 제공하지 못하고 403 Forbidden을 반환합니다.

원인: 웹 서버(www-data)가 파일에 도달하려면 경로 상의 모든 디렉토리에 실행(x) 권한이 있어야 합니다. 중간 디렉토리 하나라도 막혀 있으면 파일 자체의 권한과 무관하게 403이 발생합니다.

진단:

로컬 터미널
namei -om /var/www/html/app/index.html
OUTPUT
drwxr-xr-x  root    root   /
drwxr-xr-x  root    root   var
drwxr-xr-x  root    root   www
drwxr-xr-x  root    root   html
drw-------  deploy  deploy app     ← others에 x 없음! 여기서 차단
-rw-r--r--  deploy  deploy index.html

해결:

로컬 터미널
# 막힌 디렉토리에 실행 권한 추가
sudo chmod o+x /var/www/html/app

# 파일 자체 읽기 권한도 확인
sudo chmod 644 /var/www/html/app/index.html

상황: setfacl 로 ACL을 설정하려는데 위 오류가 납니다.

원인: 파일시스템이 acl 마운트 옵션 없이 마운트되어 있습니다. ext4의 경우 기본적으로 ACL이 비활성화된 배포판이 있습니다.

진단:

로컬 터미널
mount | grep "on / "
OUTPUT
/dev/sda1 on / type ext4 (rw,relatime)

acl 옵션이 없습니다.

해결:

로컬 터미널
# 임시 재마운트 (재부팅 시 초기화)
sudo mount -o remount,acl /

# 영구 설정: /etc/fstab 편집 후
# defaults,acl 추가
sudo mount -o remount /

# XFS 파일시스템은 기본적으로 ACL을 지원하므로 설정 불필요

상황: 보안 감사 중 find / -perm -4000 -type f 를 실행했는데 /proc, /sys 같은 의사 파일시스템에서 Permission denied 가 수천 줄 쏟아집니다. 실제 SUID 파일을 찾기 어렵고 CI 파이프라인도 종료 코드 1로 실패합니다.

원인: /proc, /sys, /run 은 실제 파일시스템이 아닌 커널 가상 파일시스템으로, 일반 사용자가 접근할 수 없는 경로가 많습니다. find 는 이 경로에서 Permission denied 를 만나면 에러를 출력하고 계속 진행합니다.

진단:

로컬 터미널
find / -perm -4000 -type f 2>&1 | head -5
OUTPUT
find: '/proc/tty/driver': Permission denied
find: '/proc/1234/fd': Permission denied
/usr/bin/passwd
/usr/bin/sudo

해결:

로컬 터미널
# stderr 버리기 (의사 파일시스템 노이즈 제거)
find / -perm -4000 -type f 2>/dev/null

# 또는 실제 파일시스템만 검사
find /usr /bin /sbin /opt /home -perm -4000 -type f 2>/dev/null

상황: 웹 서버가 파일을 제공하지 못해 빠르게 chmod -R 777 /var/www 를 실행했습니다. 당장은 동작했지만, 보안 감사에서 지적을 받았고 이후 업로드 디렉토리를 통해 웹쉘이 심어지는 인시던트가 발생했습니다.

원인: chmod 777 은 누구나 파일을 생성·실행할 수 있게 만듭니다. 업로드 디렉토리에 이것이 적용되면 공격자가 PHP 웹쉘을 업로드해 서버 전체를 장악할 수 있습니다.

진단:

로컬 터미널
# 어느 경로에서 막히는지 정확히 찾기
namei -om /var/www/html/app/index.html
OUTPUT
drw-------  deploy  deploy  app     ← 여기서 차단

해결:

로컬 터미널
# 최소 권한으로 수정
sudo chown -R deploy:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 750 {} \;
sudo find /var/www/html -type f -exec chmod 640 {} \;

# 검증
curl -I http://localhost/app/index.html

심화 — 삭제 권한은 파일이 아니라 디렉토리에 있다

💡개념

심화: 파일을 지키는 권한은 그 파일에 없다

rwx를 파일에 걸면 그 파일의 운명을 다 통제한다고 생각하기 쉽습니다. 하지만 '파일을 지우거나 이름을 바꾸는 것'은 파일이 아니라 그 파일이 담긴 디렉토리를 바꾸는 연산입니다. 이 한 가지 사실이 "왜 sticky bit가 필요한가", "왜 읽기 전용 파일이 지워지는가"를 한꺼번에 설명합니다.

  • 삭제(unlink)·이름변경(rename)은 디렉토리 엔트리를 수정합니다. 그래서 필요한 권한은 파일의 rwx가 아니라, 그 파일이 든 디렉토리의 쓰기(w)와 실행(x) 권한입니다. 파일 자신의 권한은 이 연산에 관여하지 않습니다.
  • 결과 1: 내가 소유하고 444(읽기 전용)로 잠근 파일도, 그 디렉토리에 쓰기 권한이 있는 사람은 지울 수 있습니다. 파일의 444는 '내용 수정'을 막을 뿐 '존재'는 지키지 못합니다. 많은 에디터·rsync·배포 스크립트가 "덮어쓰기"를 실제로는 "지우고 새로 쓰기"로 구현해 이 경로를 그대로 탑니다.
  • 결과 2: 반대로 내가 소유한 파일이라도 디렉토리에 쓰기 권한이 없으면 나도 못 지웁니다(rm이 되묻거나 Operation not permitted).
  • 그래서 sticky bit(+t)가 있습니다. 공용 디렉토리에 누구나 파일을 만들게 쓰기를 열어두면(예: /tmp), 위 규칙 때문에 남의 파일도 지울 수 있게 됩니다. sticky bit는 이 허점을 막아 "삭제는 파일 소유자·디렉토리 소유자·root만"으로 제한합니다. /tmp1777인 이유가 바로 이것입니다.
  • 한 가지 반직관을 더: 권한 클래스 검사는 '첫 매칭에서 끝'입니다. 커널은 owner→group→others 순으로 보되, 네가 소유자이면 owner 비트만 적용하고 group/others로 폴백하지 않습니다. 그래서 ----rwx---(owner ---, group rwx)인 파일은, 소유자인 내가 오히려 접근 거부되고 그 그룹 구성원은 접근됩니다 — 소유자가 남보다 권한이 적을 수 있습니다.
로컬 터미널
# 파일이 아니라 상위 디렉토리 권한을 봐야 삭제 가능 여부를 안다
ls -ld "$(dirname /opt/app/config/app.yml)"   # 이 디렉토리의 w·x가 삭제를 좌우
namei -om /opt/app/config/app.yml             # 경로상 각 단계의 소유자·권한 확인

# root조차 못 지우게 하는 진짜 불변(immutable) — 파일 권한을 넘어선 속성 레벨
sudo chattr +i /opt/app/config/app.yml
lsattr /opt/app/config/app.yml                # ----i--------- 이면 잠김

정리하면 권한을 볼 때 "이 파일을 누가 지울 수 있나"의 답은 파일이 아니라 디렉토리에, "정말 못 지우게"의 답은 rwx가 아니라 chattr +i에 있습니다.

상황: 중요한 설정 파일을 실수로 못 고치게 chmod 444(읽기 전용)로 잠갔습니다. 그런데 배포나 정리 잡이 돌 때마다 그 파일이 삭제되거나 새 내용으로 교체됩니다. 파일 권한은 분명히 읽기 전용인데도 그렇습니다.

원인: 삭제·교체는 파일 권한이 아니라 그 파일이 든 디렉토리의 쓰기+실행 권한이 결정합니다. 그 디렉토리가 배포 계정에 쓰기 가능이라, 배포 계정은 444 파일도 unlink한 뒤 같은 이름으로 새로 만들 수 있습니다. 파일의 444는 '내용 수정'만 막을 뿐 '존재'는 못 지킵니다. 게다가 rsync나 에디터의 "덮어쓰기"는 내부적으로 "지우고 새로 쓰기"라, 읽기 전용이어도 그대로 교체됩니다.

진단: 파일이 아니라 디렉토리를 봅니다 — ls -ld "$(dirname 파일경로)"namei -om 파일경로로 상위 디렉토리에 누가 쓰기 권한을 갖는지 확인합니다. 배포 잡의 실행 계정이 그 디렉토리의 소유자거나 쓰기 그룹에 포함돼 있으면, 그 계정이 삭제·교체할 수 있다는 뜻입니다. 파일 권한만 계속 들여다보면 원인을 못 찾습니다.

해결: 존재를 지키려면 파일이 아니라 디렉토리(또는 속성)를 조입니다. (1) 설정 파일이 든 디렉토리의 쓰기 권한을 배포 계정에서 회수합니다 — chown root:app 디렉토리 후 그룹 쓰기를 빼고(예: 750/755) 서비스는 읽기만 하게 합니다. (2) 여러 주체가 공유해야 하는 디렉토리면 sticky bit(chmod +t)를 줘 타인 삭제를 막습니다. (3) 정말 아무도(root조차) 못 지우게 해야 하면 sudo chattr +i 파일로 immutable 속성을 걸어 파일 권한을 넘어선 잠금을 겁니다(해제는 chattr -i). "읽기 전용은 내용 잠금이지 삭제 잠금이 아니다"가 핵심 교훈입니다.


💼
실무 맥락스타트업 SRE 팀에 합류한 첫 주, 팀 리드가 '새 결제 서비스 배포 전에 권한 설계 리뷰해줘'라고 합니다. 현재 모든 서비스가 root로 실행 중입니다.
현업 패턴

명령어·단축키 빠른 참조

이 모듈에서 다룬 권한·소유권 명령을 실전 옵션과 함께 모았습니다.

명령어/단축키용도자주 쓰는 예
ls -la권한·소유자·타입 읽기첫 글자 타입(d/-/l), 끝의 +는 ACL
chmod(숫자)권한 절대값 지정chmod 750 deploy.sh(rwxr-x---)
chmod(기호)특정 비트만 가감chmod u+x,g-w,o-r file
chown / chgrp소유자·그룹 변경sudo chown -R deploy:www-data app/
umask새 파일 기본 권한 마스크umask 027(파일 640·디렉토리 750), umask -S
chmod 4755 (SUID)소유자 권한으로 실행passwd처럼 -rwsr-xr-x
chmod 2775 (SGID)디렉토리 그룹 상속팀 공유 디렉토리 그룹 자동 부여
chmod 1777 (Sticky)본인 파일만 삭제/tmpdrwxrwxrwt
find -permSUID·world-writable 감사find / -perm -4000 -type f 2>/dev/null
getfacl / setfacl특정 사용자·그룹 개별 권한setfacl -m u:www-data:r /app/config.yml
namei -om경로상 각 단계 권한 추적namei -om /var/www/html/app/index.html(403 원인)
chattr +i / lsattr불변(immutable) 잠금·확인sudo chattr +i app.yml(root도 삭제 불가)

관련 모듈로 더 깊이:

다음 모듈에서는 패키지 관리 — aptdnf로 소프트웨어를 설치·업데이트하고 저장소를 관리하는 방법을 다룹니다.

지식 확인

퀴즈 — 10문제

Q1

웹 서버 실행 파일의 소유자와 그룹이 모두 nginx입니다. nginx 그룹 프로세스는 실행할 수 있되, 외부 사용자(others)는 아무것도 할 수 없어야 합니다. 올바른 chmod 값은?

Q2

배포 후 `ls -la`에서 `-rwxr-xr--` 권한의 실행 파일을 발견했습니다. 보안 팀에서 '그룹 외 사용자는 이 파일을 실행하면 안 된다'고 지적했습니다. 문제가 되는 권한 부분과 수정 명령은?

Q3

배포 직후 서비스 계정(deploy)이 소유한 실행 파일을 웹 서버 프로세스(www-data 그룹)가 실행해야 합니다. 현재 소유자는 root:root입니다. 올바른 수정 명령은?

Q4

CI 서버에서 여러 개발자 계정이 `/shared/builds/` 디렉토리에 빌드 결과물을 저장합니다. 서로의 파일을 읽을 수는 있지만, 다른 사람의 파일을 삭제하면 안 됩니다. 디렉토리에 어떤 설정이 필요한가?

Q5

운영 서버에서 새로 생성되는 설정 파일이 `-rw-rw-r--` (664) 권한으로 만들어집니다. 보안 정책상 그룹에 쓰기 권한(w)이 있으면 안 됩니다. umask를 어떻게 바꿔야 `-rw-r--r--` (644)가 기본값이 되는가?

Q6

한 파일의 그룹 쓰기 권한만 제거하고 나머지(소유자·others)는 그대로 두려 한다. chmod의 숫자(octal) 방식과 기호(symbolic) 방식 중 이 작업에 더 편한 것과 명령은?

Q7

[심화] 어떤 파일을 chmod 444(읽기 전용)로 잠갔는데도 다른 사용자가 그 파일을 삭제할 수 있었습니다. 이유는?

Q8

[심화] 실수 수정을 막으려 444로 잠근 설정 파일이 배포 때마다 삭제·교체됩니다. 파일의 존재 자체를 지키려면?

Q9

[1급] 여러 사용자가 공유하는 /project 디렉터리에서, 그 안에 새로 만들어지는 파일과 하위 디렉터리가 개인 기본 그룹이 아니라 항상 /project의 그룹을 물려받게 하려면?

Q10

[1급] chmod 4755 some_binary를 적용한 뒤 ls -l로 보면 소유자 실행 자리가 어떻게 표시되며 그 의미는?

0 / 10 답변

🧪 실습으로 확인하기

systemd — 나만의 서비스 등록

초급

Python 스크립트를 systemd unit 파일로 등록하여 서버 재시작 후에도 자동 기동되고, 크래시 시 자동 재시작되는 서비스를 만든다.

45📋 4단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요