파일 · 권한 · 사용자 관리 33 / 50 · Part 4. 사용자와 그룹 관리
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (Docker 격리 컨테이너, 테스트 계정 analyst)
기초편 연계: 「리눅스 시스템 기초」 18. Linux 사용자와 그룹

1. 들어가며

32편에서 /etc/passwd에는 비밀번호 해시가 없고 /etc/shadow를 참조한다고 했다. 이번 글에서 그 /etc/shadow를 연다.

/etc/shadow는 비밀번호 해시와 만료 정책 을 담은 파일이다. 이 파일이 중요한 이유는 두 가지다. 첫째, 해시가 유출되면 오프라인 크래킹으로 비밀번호를 알아낼 수 있다. 둘째, 계정의 잠금·만료 상태 가 여기에 있어 계정 보안 점검의 핵심 대상이다.

이번 글에서는 shadow의 필드를 해석하고, 해시 알고리즘 식별($6$ = SHA-512), 잠긴 계정 탐지, 무암호 계정 점검을 실습한다. (root 권한 실습이므로 격리된 테스트 컨테이너에서만 진행한다.)


2. 핵심 개념

2-1. 9개 필드

#필드의미
1계정명사용자명
2비밀번호 해시$id$salt$hash 또는 !/*/빈 값
3마지막 변경1970-01-01 이후 경과 일수
4최소 사용일변경 후 재변경까지 최소 일수
5최대 사용일비밀번호 유효 최대 일수
6경고 일수만료 전 경고 시작 일수
7비활성 일수만료 후 계정 비활성까지
8만료일계정 만료일(epoch 일수)
9예약미사용

2-2. 해시 알고리즘 식별

2번 필드의 $id$로 알고리즘을 안다.

$id$알고리즘비고
$6$SHA-512현재 RHEL·구 Ubuntu 기본
$5$SHA-256
$y$yescrypt최신 Debian·Ubuntu 기본
$1$MD5구식·취약

2-3. 잠금·무암호

2번 필드의미
!/!!/*로 시작로그인 잠김 (비밀번호 불가)
빈 값비밀번호 없이 로그인 — 매우 위험
$id$...정상 해시

passwd -l user는 해시 앞에 !를 붙여 잠근다. passwd -u로 해제한다.


3. 동작 원리

/etc/shadow — 해시와 계정 잠금·만료

/etc/shadow의 권한이 이 파일 보안의 전부다.

배포판권한접근
Rocky/RHEL000 root:rootroot만 (권한 검사 우회)
Ubuntu/Debian640 root:shadowroot + shadow 그룹

Rocky의 000은 "아무도 못 읽음"처럼 보이지만, root는 CAP_DAC_OVERRIDE로 권한 검사를 우회하므로(21편) 읽을 수 있다. 이 파일이 실수로 644가 되면 모든 사용자가 전 계정 해시를 읽어 오프라인 크래킹을 시도할 수 있다. 그래서 shadow 권한 점검은 CIS 벤치마크의 필수 항목이다.

비밀번호 검증 과정: 로그인 시 입력한 비밀번호에 저장된 salt를 붙여 같은 알고리즘으로 해시하고, 그 결과가 2번 필드의 hash와 일치하는지 비교한다. 해시는 단방향이라 저장된 값에서 원래 비밀번호를 복원할 수 없다(크래킹은 후보를 대입해 보는 것).


4. 명령어 실습

root 권한 실습. noexpire는 실습용으로 비밀번호를 설정한 계정, locked-user는 passwd -l로 잠근 계정이다.

# 1) 권한 확인
ls -l /etc/shadow

# 2) 필드 분해 (noexpire 계정)
getent shadow noexpire | awk -F: '{print "1 계정: "$1"\n2 해시: "substr($2,1,20)"...\n3 마지막변경(일): "$3"\n4 최소일수: "$4"\n5 최대일수: "$5"\n6 경고일수: "$6"\n7 비활성일수: "$7"\n8 만료일: "$8}'

# 3) 해시 알고리즘 식별
getent shadow noexpire | awk -F: '{print $2}' | cut -c1-3

# 4) 잠긴 계정 탐색
awk -F: '$2 ~ /^[!*]/ {print $1" -> "substr($2,1,3)}' /etc/shadow | head

# 5) 빈 비밀번호 계정 (위험)
awk -F: '$2=="" {print "빈 비밀번호: "$1}' /etc/shadow

5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — /etc/shadow 분석

텍스트 원본(실제 출력):

[root@rocky9-lab ~]# echo "=== /etc/shadow 권한 ==="
=== /etc/shadow 권한 ===
[root@rocky9-lab ~]# ls -l /etc/shadow
---------- 1 root root 701 Sep 24 14:43 /etc/shadow
[root@rocky9-lab ~]# echo "=== 필드 분해 ==="
=== 필드 분해 ===
[root@rocky9-lab ~]# getent shadow noexpire | awk -F: '{print "1 계정: "$1"\n2 해시: "substr($2,1,20)"...\n3 마지막변경(일): "$3"\n4 최소일수: "$4"\n5 최대일수: "$5"\n6 경고일수: "$6"\n7 비활성일수: "$7"\n8 만료일: "$8}'
1 계정: noexpire
2 해시: $6$93uXFXA/PIegiG.G$...
3 마지막변경(일): 20720
4 최소일수: 0
5 최대일수: 99999
6 경고일수: 7
7 비활성일수: 
8 만료일: 
[root@rocky9-lab ~]# echo "=== 해시 알고리즘 식별 ($id$) ==="
=== 해시 알고리즘 식별 ($) ===
[root@rocky9-lab ~]# getent shadow noexpire | awk -F: '{print $2}' | cut -c1-3
$6$
[root@rocky9-lab ~]# echo "=== 잠긴 계정 (! 또는 * 로 시작) ==="
=== 잠긴 계정 (! 또는 * 로 시작) ===
[root@rocky9-lab ~]# awk -F: '$2 ~ /^[!*]/ {print $1" -> "substr($2,1,3)}' /etc/shadow | head
root -> !lo
bin -> *
daemon -> *
adm -> *
lp -> *
sync -> *
shutdown -> *
halt -> *
mail -> *
operator -> *
[root@rocky9-lab ~]# echo "=== 비밀번호 없는 계정 (위험) ==="
=== 비밀번호 없는 계정 (위험) ===
[root@rocky9-lab ~]# awk -F: '$2=="" {print "빈 비밀번호: "$1}' /etc/shadow || echo "없음"

6. 결과 해석

출력해석
---------- 1 root root /etc/shadowRocky의 000 권한. root만 접근
2 해시: $6$93uXFXA/PIegiG.G$...$6$ = SHA-512, 뒤에 salt
3 마지막변경(일): 207201970 이후 20720일 ≈ 2026년. 최근 설정됨
5 최대일수: 99999사실상 만료 없음(약 273년). 정책상 유한 값 권장
6 경고일수: 7만료 7일 전 경고
$6$ (cut 결과)알고리즘 식별
root -> !lo이 컨테이너의 root는 !locked — 비밀번호 로그인 잠김(정상, 컨테이너 기본)
bin -> *, daemon -> * 등시스템 계정들. * = 로그인 불가
빈 비밀번호 계정 출력 없음무암호 계정 없음 → 정상

root -> !lo가 눈에 띈다. 컨테이너 이미지의 root는 비밀번호 로그인이 잠겨 있다(!). 이는 정상이며, 실제 서버에서 root에 !가 있으면 "root 직접 로그인 차단" 정책일 수 있다.


7. 보안 관점

점검명령위험
shadow 권한stat -c '%a' /etc/shadow640/000이 아니면 해시 노출
무암호 계정awk -F: '$2==""'비밀번호 없이 로그인
약한 해시awk -F: '$2 ~ /^\$1\$/'MD5 사용
만료 없는 정책5번 필드 99999정책상 유한값 권장(조직 기준)
잠기지 않은 시스템 계정서비스 계정에 유효 해시서비스 계정은 잠금

주의: 만료일(99999)이 길다는 것만으로 "취약점"이라 단정하지 않는다. 조직 비밀번호 정책에 따라 판단한다. $6$(SHA-512)는 현재 안전한 알고리즘이며, $1$(MD5)이 진짜 위험 신호다.


8. 보안관제 관점

[정기 점검]  stat -c '%a %U:%G' /etc/shadow                # 권한
             awk -F: '$2==""{print "무암호:",$1}' /etc/shadow
             awk -F: '$2 ~ /^\$1\$/{print "MD5:",$1}' /etc/shadow
             awk -F: '$3>0 && $5!=99999{print $1,"만료정책",$5"일"}' /etc/shadow
     ↓
[Evidence]   shadow 변경 시각(ctime), chage -l 로 계정별 만료 상태
     ↓
[Detection]  /etc/shadow 읽기 시도(권한 없는 접근)를 auditd 로 기록
     ↓
[Response]   무암호 계정 즉시 잠금(passwd -l), 약한 해시 재설정
관점내용
접근 감사shadow 읽기 시도는 해시 탈취 정황. auditd -w /etc/shadow -p r로 감시
크래킹 대비해시 유출 시 피해 범위는 알고리즘 강도에 좌우. $6$/$y$ 사용
정책chage로 계정별 만료·경고 정책 관리(35편 연계)

9. 실무에서 자주 발생하는 실수

실수결과예방
shadow 권한 변경(644 등)전 계정 해시 노출000/640 유지
shadow 직접 편집손상·로그인 불가passwd·chage·usermod 사용
MD5 해시 방치크래킹 용이최신 알고리즘으로 재설정
무암호 계정 방치무인증 접근즉시 잠금
root !를 침해로 오판오탐root 직접 로그인 차단은 정상 정책일 수 있음

10. 실습 체크리스트

[ ] /etc/shadow 권한(000/640)을 확인했다
[ ] 9개 필드를 해석했다
[ ] $6$ 로 해시 알고리즘을 식별했다
[ ] 잠긴 계정(!, *)을 탐색했다
[ ] 무암호 계정 점검 명령을 안다
[ ] shadow 가 644 가 되면 위험한 이유를 설명할 수 있다

11. 핵심 정리

  • /etc/shadow는 해시와 만료 정책 을 담고, 권한은 000(Rocky)/640(Ubuntu)다.
  • 2번 필드 $id$로 알고리즘을 안다: $6$=SHA-512, $y$=yescrypt, $1$=MD5(취약).
  • !/*로 시작하면 잠김, 빈 값이면 무암호 로그인 이다.
  • shadow가 644로 노출되면 전 계정 해시가 오프라인 크래킹에 노출된다.
  • 무암호 계정·MD5 해시가 진짜 위험 신호다. 만료값·root !는 정책에 따라 판단한다.
  • 편집은 passwd/chage/usermod로, 접근은 auditd로 감시한다.

12. 다음 편 예고

다음 글 「34. /etc/group 분석」 에서는 그룹 정보를 담은 /etc/group의 4개 필드를 해석하고, 기본 그룹과 보조 그룹의 차이, 그리고 wheel·sudo·docker 같은 위험 그룹의 구성원 점검을 실습한다.


참고 자료


시리즈 이동

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글