chmod 8진수 권한 읽는 법과 777을 피하는 이유
rwx와 8진수 644, 755, 600의 관계, 디렉터리에서 x가 뜻하는 것, SSH 키 권한 오류, umask 계산을 실제 명령 출력과 함께 정리합니다.
Permission denied에서 시작하는 경우
배포 서버에 올린 스크립트를 ./deploy.sh로 실행하려고 하면 Permission denied가 나올 때가 있습니다. 이때 ls -l로 권한을 보면 대개 실행 비트가 빠져 있습니다.
$ ls -l deploy.sh
-rw-r--r-- ... deploy.sh
$ chmod 755 deploy.sh
$ ls -l deploy.sh
-rwxr-xr-x ... deploy.sh
여기서 rw-r--r--와 rwxr-xr-x를 읽을 수 있으면 권한 문제의 대부분은 스스로 진단할 수 있습니다. 이 글은 그 표기를 읽는 법부터 시작해서, 디렉터리에 적용되는 규칙, 보안상 피해야 하는 값, SSH 키 오류, 새 파일의 기본 권한을 정하는 umask까지 순서대로 정리합니다.
rwx 세 글자와 세 묶음
파일 권한은 아홉 개의 비트로 이루어집니다. 이 아홉 개는 세 묶음으로 나뉩니다.
- 소유자(user): 파일의 주인
- 그룹(group): 파일에 연결된 그룹의 구성원
- 기타(other): 위 두 가지에 해당하지 않는 나머지 모든 사용자
각 묶음 안에는 읽기(r), 쓰기(w), 실행(x) 세 비트가 있습니다. 이 세 비트는 8진수 한 자리로 표현할 수 있습니다. r은 4, w는 2, x는 1입니다. 한 묶음의 값은 해당하는 비트를 더한 값입니다.
rw- = 4 + 2 + 0 = 6
r-- = 4 + 0 + 0 = 4
r-- = 4 + 0 + 0 = 4
→ 644
세 묶음을 소유자, 그룹, 기타 순서로 나란히 적으면 644가 됩니다. 실제 테스트에서 아래 값은 다음과 같이 표시되었습니다.
| 8진수 | 기호 표기 | 의미 |
|---|---|---|
| 600 | rw------- | 소유자만 읽고 쓰기 |
| 644 | rw-r--r-- | 소유자는 읽고 쓰고, 나머지는 읽기만 |
| 755 | rwxr-xr-x | 소유자는 모두, 나머지는 읽고 실행 |
| 777 | rwxrwxrwx | 모든 사용자가 모든 작업 가능 |
일반 설정 파일은 644, 실행 파일이나 디렉터리는 755, 비밀 값이 들어 있는 파일은 600이 흔한 선택입니다. chmod 계산기에서 소유자, 그룹, 기타 각각의 r, w, x를 체크하면 같은 방식으로 8진수와 기호 표기를 얻을 수 있습니다.
디렉터리에서 r, w, x는 다르게 작동한다
디렉터리에 적용되는 권한은 파일과 의미가 다릅니다.
- r: 디렉터리 안의 이름 목록을 읽습니다.
- w: 디렉터리 안에 항목을 만들거나 지웁니다.
- x: 이름을 알고 있을 때 그 항목까지 경로를 통과합니다.
가장 많이 놓치는 부분은 x입니다. 디렉터리에 x가 없으면 소유자 본인이라도 그 안의 파일에 접근할 수 없습니다. 아래 테스트는 디렉터리를 644로 두었을 때의 결과입니다. 디렉터리의 주인은 같은 사용자입니다.
$ chmod 644 secret
$ ls secret
ls: fts_read: Permission denied
$ cat secret/key.txt
cat: secret/key.txt: Permission denied
secret/key.txt 파일 자체는 644로 설정되어 있어도 소용이 없습니다. 디렉터리의 x가 먼저 막고 있기 때문입니다. 반대로 디렉터리를 711로 바꾸면 소유자는 목록을 볼 수 있고, 다른 사용자는 이름을 알고 있는 경우에만 들어갈 수 있습니다. 같은 테스트에서 711로 바꾸었을 때는 cat이 파일 내용을 정상 출력했습니다.
디렉터리에서 r만 있고 x가 없는 상태는 목록을 보여 주는 것처럼 보이지만, 실제로는 ls가 항목의 속성을 읽는 단계에서 실패합니다. 디렉터리 권한을 줄 때는 r과 x를 짝으로 생각하는 편이 안전합니다.
777이 위험한 이유
777은 모든 사용자에게 읽기, 쓰기, 실행을 모두 허용합니다. 문제를 해결하는 가장 빠른 방법처럼 보이지만, 그만큼 보안 경계가 사라집니다. 예를 들어 웹 서버가 업로드를 받는 디렉터리에 777을 주면, 웹 서버 계정이 아닌 다른 프로세스나 사용자도 그 안에 파일을 쓸 수 있습니다. 쓰기와 실행이 같이 열리면 공격자가 스크립트를 올려 실행하는 경로가 생깁니다.
실무에서는 다음 순서로 범위를 좁힙니다.
- 파일과 디렉터리의 소유자가 누구인지
ls -l로 확인합니다. - 쓰기 권한이 필요한 주체가 소유자 하나라면 755나 644로 충분합니다.
- 그룹이 함께 써야 한다면 소유 그룹을 맞춘 뒤 그룹에만 쓰기를 줍니다.
- 기타 사용자에게는 읽기 정도만 줍니다.
777로 해결했다면, 해결 후 권한을 다시 줄여도 동작하는지 한 번 확인하는 것이 좋습니다.
SSH 키 권한 오류
SSH 클라이언트는 개인키 파일이 다른 사용자에게 읽히는 상태이면 그 키를 사용하지 않습니다. OpenSSH는 다음과 같은 메시지를 출력합니다.
Permissions 0644 for '/home/me/.ssh/id_ed25519' are too open.
이 경우 해결은 개인키 파일을 소유자만 읽을 수 있도록 바꾸는 것입니다.
chmod 600 ~/.ssh/id_ed25519
chmod 700 ~/.ssh
개인키 파일에는 600을, ~/.ssh 디렉터리에는 관례적으로 700을 씁니다. 공개키 파일(.pub)은 644여도 되지만, 개인키와 같은 디렉터리에 두고 권한을 일관되게 맞추는 편이 관리하기 쉽습니다. 파일을 다른 서버로 복사하면서 권한이 644로 바뀌는 경우가 많으므로, 복사 후에도 권한을 다시 확인해야 합니다.
setuid, setgid, sticky
8진수 네 자리로 표기하면 맨 앞 자리가 특수 비트입니다. 4는 setuid, 2는 setgid, 1은 sticky입니다.
- setuid(4): 실행 파일을 실행할 때 실행한 사용자가 아니라 파일 소유자의 권한으로 실행합니다.
passwd처럼 일반 사용자가 시스템 설정을 바꿔야 하는 프로그램에 쓰입니다. - setgid(2): 실행 파일은 파일 그룹의 권한으로 실행합니다. 디렉터리에 설정하면 그 안에 새로 만든 파일이 디렉터리의 그룹을 따라갑니다.
- sticky(1): 디렉터리에 설정하면 모든 사용자가 쓸 수 있더라도 각자 자기 소유의 항목만 지울 수 있습니다. 공용 임시 디렉터리에서 사용하는 규칙입니다.
$ chmod 4755 tool
$ ls -l tool
-rwsr-xr-x ... tool
소유자 자리의 x가 s로 바뀌어 표시됩니다. setuid가 설정된 파일은 소유자가 root라면 실행하는 사람에게 root 권한을 빌려주는 것과 같으므로, 직접 만든 스크립트에 이 비트를 붙이는 일은 피해야 합니다. chmod 계산기는 소유자, 그룹, 기타 세 묶음의 r, w, x만 다루므로 특수 비트는 직접 계산해야 합니다.
umask로 새 파일의 기본 권한 정하기
새로 만든 파일과 디렉터리의 권한은 기본값에서 umask를 뺀 값으로 정해집니다. 기본값은 파일이 666, 디렉터리가 777입니다. umask는 빼기 연산처럼 보이지만 실제로는 비트 단위로 제거하는 연산입니다. 많이 쓰는 umask 022를 적용하면 결과는 다음과 같습니다.
파일: 666 & ~022 = 644
디렉터리: 777 & ~022 = 755
테스트에서 umask를 022로 설정한 뒤 파일과 디렉터리를 만들면 실제로 -rw-r--r--와 drwxr-xr-x가 나왔습니다. 공용 서버에서 그룹 쓰기를 막고 싶다면 umask를 027이나 077처럼 바꿀 수 있습니다. 다만 umask는 셸 세션마다 적용되므로, 배포 스크립트나 서비스 계정의 환경 설정 파일에서 어떤 값이 쓰이는지 확인해야 합니다.
점검 목록
- 권한 오류가 나면
ls -l로 실행 대상과 경로 위의 모든 디렉터리 권한을 확인합니다. - 디렉터리에는 r과 x가 함께 필요하다는 점을 기억합니다.
- 777을 준 흔적이 있는지 찾고, 필요한 주체만 남기도록 범위를 좁힙니다.
- SSH 개인키는 600,
~/.ssh는 700으로 맞춥니다. - 파일을 복사하거나 압축을 풀고 나서 권한이 바뀌었는지 확인합니다.
- setuid, setgid, sticky가 의도한 파일에만 있는지 확인합니다.
- 새 파일의 권한이 예상과 다르면 umask 값을 먼저 확인합니다.