파일 · 권한 · 사용자 관리 34 / 50 · Part 4. 사용자와 그룹 관리
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (Docker 격리 컨테이너, 테스트 계정analyst)
기초편 연계: 「리눅스 시스템 기초」 18. Linux 사용자와 그룹
27편에서 그룹이 여러 사용자에게 권한을 한 번에 주는 수단이라고 했다. 그 그룹 정보가 저장되는 파일이 /etc/group이다.
이번 글에서는 /etc/group의 4개 필드를 해석하고, 초보자가 가장 많이 헷갈리는 기본 그룹과 보조 그룹의 차이 를 실습으로 확인한다. 핵심 함정은 이것이다. 사용자의 기본 그룹은 /etc/group의 구성원 목록에 나오지 않는다. 기본 그룹은 /etc/passwd의 GID로만 결정되기 때문이다.
그리고 계정 보안 점검의 핵심인 위험 그룹(wheel·sudo·docker) 구성원 확인 을 다룬다.
devops:x:1001:alice,bob,analyst를 :로 나누면:
| # | 필드 | 예 | 의미 |
|---|---|---|---|
| 1 | 그룹명 | devops | 그룹 이름 |
| 2 | 비밀번호 | x | /etc/gshadow 참조 (거의 미사용) |
| 3 | GID | 1001 | 그룹 번호 |
| 4 | 구성원 | alice,bob,analyst | 이 그룹을 보조 그룹 으로 가진 사용자 |
| 구분 | 어디서 결정 | 특징 |
|---|---|---|
| 기본(primary) | /etc/passwd의 4번 필드(GID) | 새 파일의 그룹, id의 gid= |
| 보조(supplementary) | /etc/group의 4번 필드 | 추가 접근 권한, id의 groups= 뒤쪽 |
함정: 사용자 alice의 기본 그룹이 users라면, users 그룹의 /etc/group 줄에 alice가 나오지 않을 수 있다. 기본 그룹 멤버십은 passwd에만 기록되기 때문이다. 그래서 "특정 사용자가 어떤 그룹에 속하는가"는 /etc/group만 봐서는 안 되고 id user로 확인한다.

로그인하면 프로세스는 기본 그룹(GID) 하나와 보조 그룹 여러 개를 갖는다. 권한 검사 시 커널은 파일의 그룹과 프로세스의 기본 그룹 + 모든 보조 그룹 을 비교해, 일치하는 것이 있으면 그룹 권한 칸을 적용한다(21편).
/etc/group은 644 root:root로 누구나 읽을 수 있다. 그래서 getent group으로 그룹 구성을 확인하는 것은 일반 사용자도 가능하다. 쓰기는 root만 가능하며, usermod/gpasswd가 이 파일을 수정한다.
보안 관점에서 이 파일의 4번 필드는 권한 부여 기록 이다. 어떤 계정이 wheel(관리자), docker(사실상 root), shadow(해시 읽기) 그룹에 들어 있는지가 곧 그 계정이 가진 권한이다.
devops그룹에 alice·bob·analyst가 보조 그룹으로 속해 있다. alice의 기본 그룹은users(100)다.
# 1) 그룹 필드 확인
getent group devops root
# 2) 필드 분해
getent group devops | awk -F: '{print "1 그룹명: "$1"\n2 password: "$2"\n3 GID: "$3"\n4 구성원(보조): "$4}'
# 3) 기본 그룹은 group 파일에 안 나온다
id analyst
getent passwd alice | awk -F: '{print "alice GID: "$4}'
getent group $(getent passwd alice | cut -d: -f4) # alice 기본 그룹
# 4) 위험 그룹 구성원 점검
for g in wheel sudo docker disk; do
m=$(getent group $g)
if [ -n "$m" ]; then echo "$g -> 구성원[$(echo $m|cut -d: -f4)]"; else echo "$g -> (그룹 없음)"; fi
done

텍스트 원본(실제 출력):
[analyst@rocky9-lab ~]$ echo "=== /etc/group 필드 4개 ==="
=== /etc/group 필드 4개 ===
[analyst@rocky9-lab ~]$ getent group devops root
devops:x:1001:alice,bob,analyst
root:x:0:
[analyst@rocky9-lab ~]$ echo "--- 분해 (devops) ---"
--- 분해 (devops) ---
[analyst@rocky9-lab ~]$ getent group devops | awk -F: '{print "1 그룹명: "$1"\n2 password: "$2"\n3 GID: "$3"\n4 구성원(보조): "$4}'
1 그룹명: devops
2 password: x
3 GID: 1001
4 구성원(보조): alice,bob,analyst
[analyst@rocky9-lab ~]$ echo "=== 기본 그룹 vs 보조 그룹 ==="
=== 기본 그룹 vs 보조 그룹 ===
[analyst@rocky9-lab ~]$ id analyst
uid=1000(analyst) gid=1000(analyst) groups=1000(analyst),1001(devops)
[analyst@rocky9-lab ~]$ echo "alice 의 기본 그룹은 /etc/passwd 의 GID 로 결정 (group 파일에 안 나올 수 있음)"
alice 의 기본 그룹은 /etc/passwd 의 GID 로 결정 (group 파일에 안 나올 수 있음)
[analyst@rocky9-lab ~]$ getent passwd alice | awk -F: '{print "alice GID: "$4}'
alice GID: 100
[analyst@rocky9-lab ~]$ getent group $(getent passwd alice | cut -d: -f4)
users:x:100:
[analyst@rocky9-lab ~]$ echo "=== 특정 그룹의 전체 구성원 ==="
=== 특정 그룹의 전체 구성원 ===
[analyst@rocky9-lab ~]$ getent group devops
devops:x:1001:alice,bob,analyst
[analyst@rocky9-lab ~]$ echo "=== 위험 그룹 점검 ==="
=== 위험 그룹 점검 ===
[analyst@rocky9-lab ~]$ for g in wheel sudo docker disk; do m=$(getent group $g); if [ -n "$m" ]; then echo "$g -> 구성원[$(echo $m|cut -d: -f4)]"; else echo "$g -> (그룹 없음)"; fi; done
wheel -> 구성원[]
sudo -> (그룹 없음)
docker -> (그룹 없음)
disk -> 구성원[]
| 출력 | 해석 |
|---|---|
devops:x:1001:alice,bob,analyst | GID 1001, 세 명이 보조 그룹으로 속함 |
root:x:0: | root 그룹, 구성원 목록 비어 있음(root의 기본 그룹이라 별도 표기 불필요) |
id analyst → ...,1001(devops) | analyst의 보조 그룹에 devops 포함 |
alice GID: 100 | alice의 기본 그룹은 users(100) |
getent group 100 → users:x:100: | users 그룹 줄에 alice가 없다 — 기본 그룹은 passwd로만 기록 |
wheel -> 구성원[] | wheel 그룹 존재하나 구성원 없음 (Rocky 기본) |
sudo -> (그룹 없음) | Rocky에는 sudo 그룹이 없음 (Ubuntu는 있음) |
docker -> (그룹 없음) | docker 미설치 |
alice GID: 100과 users:x:100:(alice 없음)의 조합이 이번 실습의 핵심이다. alice는 분명 users 그룹에 속하지만, /etc/group의 users 줄에는 나오지 않는다. 기본 그룹은 passwd, 보조 그룹은 group 이라는 분리를 보여 준다.
wheel/sudo 차이도 배포판 포인트다. RHEL 계열은 wheel, Debian/Ubuntu 계열은 sudo 그룹이 관리자 그룹이다(41·42편).
| 그룹 | 부여되는 권한 |
|---|---|
wheel(RHEL)/sudo(Debian) | sudo로 관리자 명령 실행 |
docker | 컨테이너로 호스트 root 접근 = 사실상 root |
disk | 디스크 장치 직접 접근 = 파일 권한 우회 |
shadow | /etc/shadow 읽기 = 해시 접근 |
adm | 로그 파일 읽기 |
점검 원칙: 이들 그룹의 구성원은 최소화하고 정기 확인한다. 공격자가 root를 얻은 뒤 자신의 계정을 wheel/sudo/docker에 추가 하는 것은 흔한 지속성(persistence) 기법이다. /etc/group 변경 시각과 구성원을 Baseline과 비교한다.
[Baseline] 민감 그룹 구성원 저장
for g in wheel sudo docker disk shadow adm; do getent group $g; done > baseline/groups.txt
↓
[정기 점검] 현재와 비교
diff <(for g in wheel sudo docker disk shadow adm; do getent group $g; done) baseline/groups.txt
↓
[Detection] /etc/group·/etc/gshadow 변경, gpasswd/usermod 실행(auditd)
↓
[Evidence] 변경 시각(stat), secure/auth.log 의 그룹 추가 기록
↓
[Response] 무단 멤버십 제거(gpasswd -d user group), 경위 조사(49편)
| 관점 | 내용 |
|---|---|
| IOC | 민감 그룹에 예상 밖 계정 추가 |
| Detection | usermod -aG, gpasswd -a 실행을 감시 |
| Baseline | wheel/sudo/docker 정상 구성원 목록 관리(48편) |
| 실수 | 결과 | 예방 |
|---|---|---|
/etc/group만 보고 멤버십 판단 | 기본 그룹 누락 | id user로 확인 |
usermod -G에서 -a 누락 | 기존 보조 그룹 삭제 | 항상 -aG |
| 편의로 docker/wheel 추가 | 사실상 root 부여 | 최소 권한 |
/etc/group 직접 편집 | 손상 | gpasswd/usermod |
| 민감 그룹 미점검 | 은닉 권한 상승 | 정기 점검 |
[ ] /etc/group 4개 필드를 해석했다
[ ] 기본 그룹과 보조 그룹의 차이를 설명할 수 있다
[ ] 기본 그룹이 group 파일에 안 나오는 것을 확인했다
[ ] id user 로 전체 그룹을 확인했다
[ ] wheel/sudo 배포판 차이를 안다
[ ] 민감 그룹 구성원 점검 방법을 안다
/etc/group은 그룹명·x·GID·구성원 4개 필드다./etc/passwd의 GID 로 결정되고 group 파일 구성원에 안 나온다.id user로 확인한다.wheel/sudo·docker·disk·shadow 그룹은 강력한 권한이다.다음 글 「35. useradd로 사용자 생성」 에서는 계정을 만드는 useradd의 전체 과정을 다룬다. 홈 디렉터리 생성과 skel 복사, UID 자동 할당, 그리고 시스템 계정(-r)과 로그인 불가 셸 설정을 실습한다.