리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.
리눅스 시스템 기초 20 / 50 · Part 2. 파일·명령어·권한 (마무리)
실습 환경: Rocky Linux 9 (10.0.0.200)
이전 글: 19. sudo와 su
🔗 심화 시리즈 — 「파일 · 권한 · 사용자 관리」
이 주제를 더 깊게 다룬 실무형 보안 시리즈: 45. 파일 권한과 최소 권한 원칙 · 46. World-Writable 파일 점검 · 47. SUID/SGID 파일 보안 점검
Part 2에서는 파일 관리(11편)부터 경로(12편), 파일 종류(13편), inode(14편), 권한(15·16편), 소유권(17편), 계정(18편), sudo(19편)까지 다뤘다. 이번 글은 Part 2의 마무리로, 이 모든 내용을 하나의 원칙으로 묶는다. 최소 권한 원칙(Principle of Least Privilege) 이다.
모든 사용자와 프로세스는 자기 업무에 필요한 최소한의 권한만, 필요한 기간 동안만 가져야 한다.
이 원칙은 두 가지 의미에서 보안관제와 직결된다.
| 계층 | 최소 권한 적용 방법 | 관련 글 |
|---|---|---|
| 계정 | 개인 계정 사용, 서비스 계정 nologin, 휴면 계정 잠금 | 3·18편 |
| 파일 | 필요한 권한만(640, 750), 코드는 root 소유, 불필요한 SUID 제거 | 15~17편 |
| 관리 권한 | root 직접 사용 금지, sudo 명령 한정 | 19편 |
| 서비스 | 전용 계정으로 실행, systemd 보안 옵션 | 이번 글, 36·37편 |
| 접근 경로 | root SSH 로그인 금지, 필요한 포트만 개방 | 45·46편 |
| 강제 접근 제어 | SELinux로 프로세스별 허용 범위 제한 | 47편 |
| 개념 | 의미 |
|---|---|
| 직무 분리 (Separation of Duties) | 한 계정이 모든 권한을 갖지 않도록 역할을 나눔 |
| 기본 거부 (Default Deny) | 명시적으로 허용한 것 외에는 모두 차단 |
| 심층 방어 (Defense in Depth) | 한 계층이 실패해도 다음 계층이 막도록 여러 겹 적용 |
| Capabilities | root 권한을 기능 단위(CAP_NET_BIND_SERVICE 등)로 쪼개 필요한 것만 부여 |

웹·앱 서버가 root로 실행되면 취약점 하나로 서버 전체가 넘어간다. systemd 유닛에서 다음 옵션으로 서비스의 권한을 제한할 수 있다.
| 옵션 | 효과 |
|---|---|
User=, Group= | 전용 계정으로 실행 |
NoNewPrivileges=yes | 실행 중 SUID 등으로 권한을 더 얻지 못하게 함 |
ProtectSystem=strict | /usr, /etc 등을 읽기 전용으로 |
ProtectHome=yes | /home, /root 접근 차단 |
PrivateTmp=yes | 서비스 전용 /tmp (다른 프로세스와 분리) |
ReadWritePaths= | 쓰기 허용 경로를 명시 |
CapabilityBoundingSet= | 허용할 capability만 지정 |
systemd-analyze security <유닛>은 이런 옵션이 얼마나 적용되었는지 노출 점수(exposure) 로 보여준다.
과거에는 1024번 미만 포트를 열려면 root가 필요했다. capabilities를 쓰면 root 전체가 아니라 포트 바인딩 권한(CAP_NET_BIND_SERVICE)만 줄 수 있다. 반대로 공격자가 getcap으로 과도한 capability가 붙은 파일을 찾아 권한 상승에 악용하기도 하므로 점검 대상이다.
# 1) 어떤 계정으로 서비스가 실행 중인가
ps -eo user,pid,cmd --sort=user | grep -vE '^\s*root .*\[' | head -30
# 2) systemd 서비스 보안 점수
systemd-analyze security --no-pager | head -20
systemd-analyze security httpd.service --no-pager | tail -5
# 3) 서비스 권한 제한 적용 (drop-in 파일로 원본 유지)
sudo systemctl edit httpd.service
# [Service]
# NoNewPrivileges=yes
# PrivateTmp=yes
# ProtectHome=yes
sudo systemctl daemon-reload && sudo systemctl restart httpd
systemctl show httpd -p NoNewPrivileges -p PrivateTmp -p ProtectHome
# 4) capabilities가 부여된 파일
sudo getcap -r / 2>/dev/null
# 5) Part 2 종합 점검
awk -F: '$3==0 {print "UID0:", $1}' /etc/passwd
sudo grep -rh 'NOPASSWD' /etc/sudoers /etc/sudoers.d/ 2>/dev/null
sudo find / -xdev -type f -perm -0002 ! -path '/proc/*' 2>/dev/null | head
sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication)'
getenforce
아래 출력은 형식 설명용 예시다.
① systemd-analyze security
UNIT EXPOSURE PREDICATE
auditd.service 8.9 EXPOSED
httpd.service 9.2 UNSAFE
sshd.service 9.6 UNSAFE
chronyd.service 3.9 OK
점수가 높을수록 제한이 적다. 모든 서비스를 낮출 수는 없지만(sshd는 로그인 처리를 위해 넓은 권한이 필요), 외부 입력을 직접 처리하는 웹·앱 서비스 는 우선 낮추는 대상이다.
② 종합 점검 판단표
| 항목 | 기준 | 판단 |
|---|---|---|
| UID 0 계정 | root만 | 둘 이상이면 즉시 조사 |
| NOPASSWD | 자동화 계정의 특정 명령만 | ALL과 함께 있으면 위험 |
| world-writable 파일 | 없음(임시 경로 제외) | 실행 경로에 있으면 권한 상승 위험 |
PermitRootLogin | no 또는 prohibit-password | yes면 조치 |
| SELinux | Enforcing | Disabled면 강제 접근 제어 없음 |
최소 권한이 없을 때와 있을 때 같은 공격이 어떻게 달라지는지 비교해 보면 원칙의 가치가 분명해진다.
| 공격 단계 | 최소 권한 없음 | 최소 권한 적용 |
|---|---|---|
| 웹 취약점으로 코드 실행 | 웹 서버가 root → 즉시 서버 장악 | apache 권한 → 제한된 행동 |
| 웹셸 저장 | 웹 루트 쓰기 가능 → 성공 | 웹 루트 읽기 전용 → 실패 (로그 발생) |
| 권한 상승 시도 | NOPASSWD: ALL, SUID 쉘 → 성공 | sudo 거부, SUID 없음 → 실패 (로그 발생) |
| 민감 정보 수집 | 설정 파일 644 → 비밀번호 획득 | 640 → 거부 |
| 지속성 설치 | /etc 수정 가능 | 불가, SELinux 거부 |
오른쪽 열의 "실패 (로그 발생)" 이 관제에서 중요한 지점이다. 최소 권한은 공격을 막는 동시에 공격 시도를 로그로 드러나게 한다.
| 로그 | 위치 | 의미 |
|---|---|---|
Permission denied / EACCES | audit (success=no), 애플리케이션 로그 | 권한 밖의 접근 시도 |
user NOT in sudoers | secure | 관리 권한 없는 계정의 권한 상승 시도 |
FAILED SU | secure | root 비밀번호 추측 |
SELinux AVC denied | audit.log | 프로세스가 허용 범위를 벗어남 (47편) |
| 서비스 계정의 대화형 로그인 | secure | 원래 로그인하지 않는 계정 |
10편의 Triage 스크립트에 Part 2 점검 항목(UID 0, NOPASSWD, SUID 목록, world-writable, 서비스 실행 계정)을 추가하고, 정상 상태를 기준값으로 저장해 두면 최소 권한이 무너지는 변화 를 바로 발견할 수 있다.
[Alert] audit: apache 계정의 /var/www/html 쓰기 시도 실패 (success=no, EACCES) 반복
↓
[의미] 웹 루트가 읽기 전용이라 웹셸 저장이 막힘 → 공격 시도가 로그로 드러남
↓
[IOC] 같은 시각 access_log 의 업로드 요청, 출발지 IP, 시도 파일명
↓
[판단] 공격 시도는 있었으나 최소 권한으로 차단 → 취약점 존재 확인이 핵심
↓
[Response] IP 차단, 업로드 취약점 수정, 다른 쓰기 가능 경로 점검
| 편 | 핵심 | 최소 권한과의 연결 |
|---|---|---|
| 11 | 파일 관리 | 증적은 속성 보존 복사 |
| 12 | 경로 | 절대경로로 실행 대상 고정 |
| 13 | 파일 종류 | 업로드 폴더 실행 금지 |
| 14 | inode | ctime 기반 타임라인 |
| 15·16 | 권한·특수 권한 | 필요한 비트만, SUID 최소화 |
| 17 | 소유권 | 코드는 root 소유 |
| 18 | 계정 | 서비스 계정 nologin, UID 0 하나 |
| 19 | sudo | 명령 한정, 명령 단위 기록 |
User=, NoNewPrivileges, ProtectSystem, PrivateTmp로 서비스 권한을 줄이고 systemd-analyze security로 점검한다.getcap -r /로 점검한다.Part 3 「Linux 명령어 활용」이 시작된다. 다음 글 「21. grep으로 로그와 문자열 검색」 에서는 지금까지 여러 번 등장한 grep을 정리한다. 정규표현식 기초와 함께, /var/log/secure와 웹 로그에서 로그인 실패, 권한 상승, 공격 패턴을 검색하는 실전 패턴 을 다룬다.