리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.
리눅스 시스템 기초 30 / 50 · Part 3. Linux 명령어 활용 (마무리)
실습 환경: Rocky Linux 9 (10.0.0.200) · 22편 실습 파일 구조 포함
이전 글: 29. 명령어 조합을 이용한 시스템 분석
🔗 심화 시리즈 — 「파일 · 권한 · 사용자 관리」
이 주제를 더 깊게 다룬 실무형 보안 시리즈: 46. World-Writable 파일 점검 · 47. SUID/SGID 파일 보안 점검 · 48. Linux 계정 보안 점검 · 50. Linux 파일·권한·사용자 보안관제 실습
Part 3의 마지막 글이다. 21편 grep부터 29편 시스템 분석까지 배운 명령어를 하나의 보안 점검 스크립트 로 묶는다. Part 2의 권한·계정·SUID 개념과 Part 3의 명령어 조합·스크립트 작성이 여기서 합쳐진다.
이 스크립트는 침해사고가 났을 때가 아니라 평소에 정기적으로 돌려 "정상 상태에서 벗어난 것"을 찾는 용도다. 10편의 Triage 스크립트가 "정보를 수집"했다면, 이 스크립트는 그 정보를 판정 한다.
목표는 하나다. 사람이 매번 기억해서 명령어를 치지 않아도, 계정·권한·네트워크·프로세스·지속성·접근 설정을 한 번에 점검하고 OK/WARN으로 요약 하는 것.
| 영역 | 점검 항목 | 관련 글 |
|---|---|---|
| 1. 계정 | UID 0 중복, 빈 비밀번호, 로그인 쉘 가진 시스템 계정 | 18편 |
| 2. 권한·SUID | /etc/shadow 권한, world-writable 파일 | 15·16편 |
| 3. 네트워크 | LISTEN 포트 목록 | 1편 |
| 4. 프로세스 | 커널 스레드 위장, 삭제·임시 경로 실행 | 29편 |
| 5. 지속성 | 최근 변경된 cron, authorized_keys | 22편 |
| 6. 접근 설정 | PermitRootLogin, SELinux 상태 | 36·47편 |
| 10편 Triage | 30편 seccheck | |
|---|---|---|
| 목적 | 정보 수집 | 정상/이상 판정 |
| 출력 | 원본 명령 결과 파일 | OK / WARN 요약 |
| 판단 | 사람이 나중에 | 스크립트가 기준으로 |
| 종료 코드 | 항상 0 | WARN 있으면 1 (자동화 연계) |

핵심 설계는 세 가지다.
set -uo pipefail — -e는 뺐다. grep이 "0건"일 때 종료 코드 1을 반환하는데(27편), -e가 있으면 정상인데도 스크립트가 멈추기 때문이다. 대신 각 명령에 || true를 붙여 결과 없음을 허용한다.exit 1. cron이나 모니터링 도구가 이 종료 코드로 이상 여부를 감지할 수 있다.#!/bin/bash
# seccheck.sh — Linux 기본 보안 점검 (읽기 전용) · 리눅스 시스템 기초 Part 3 정리
# 사용법: sudo ./seccheck.sh [기준값디렉터리]
# 기준값이 있으면 SUID·리스닝 포트를 diff 로 비교한다.
set -uo pipefail # -e 는 쓰지 않음: grep 0건(exit 1)에도 계속 진행
BASE="${1:-}"
PASS=0; WARN=0
ok() { printf ' [ OK ] %s\n' "$*"; PASS=$((PASS+1)); }
warn() { printf ' [WARN] %s\n' "$*"; WARN=$((WARN+1)); }
hdr() { printf '\n== %s ==\n' "$*"; }
hdr "1. 계정 (18편)"
u0=$(awk -F: '$3==0{print $1}' /etc/passwd | grep -vx root || true)
[[ -z "$u0" ]] && ok "UID 0 계정은 root 뿐" || warn "root 외 UID 0: $u0"
empty=$(awk -F: '($2==""){print $1}' /etc/shadow 2>/dev/null || true)
[[ -z "$empty" ]] && ok "빈 비밀번호 계정 없음" || warn "빈 비밀번호: $empty"
svc=$(awk -F: '$3<1000 && $3>0 && $7 ~ /(bash|sh)$/{print $1}' /etc/passwd || true)
[[ -z "$svc" ]] && ok "시스템 계정 로그인 쉘 정상" || warn "로그인 쉘 가진 시스템 계정: $svc"
hdr "2. 권한 · SUID (15·16편)"
sp=$(stat -c '%a' /etc/shadow)
[[ "$sp" == "0" || "$sp" == "000" || "$sp" == "600" || "$sp" == "640" ]] && ok "/etc/shadow 권한 $sp" || warn "/etc/shadow 권한 과다: $sp"
ww=$(find / -xdev -type f -perm -0002 ! -path '/proc/*' ! -path '/sys/*' 2>/dev/null | head -5 || true)
[[ -z "$ww" ]] && ok "world-writable 파일 없음" || warn "world-writable 파일: $(echo "$ww" | tr '\n' ' ')"
hdr "3. 네트워크 (1편)"
if command -v ss >/dev/null; then
echo " LISTEN 포트:"; ss -tulnH 2>/dev/null | awk '{print " "$1, $5}' | sort -u
fi
hdr "4. 프로세스 (29편)"
fake=$(ps -eo ppid,comm,args 2>/dev/null | awk '$2 ~ /^\[/ && $1!=2 && $1!=0 {print $2}' | head || true)
[[ -z "$fake" ]] && ok "커널 스레드 위장 프로세스 없음" || warn "위장 의심: $fake"
dele=0
for e in /proc/[0-9]*/exe; do
t=$(readlink "$e" 2>/dev/null) || continue
[[ "$t" == *"(deleted)"* || "$t" == /tmp/* || "$t" == /dev/shm/* ]] && dele=$((dele+1))
done
[[ "$dele" -eq 0 ]] && ok "삭제·임시 경로 실행 프로세스 없음" || warn "삭제/임시 실행 프로세스 $dele 개"
hdr "5. 지속성 (22·30편)"
cronx=$(find /etc/cron.d /etc/cron.daily /var/spool/cron -type f -mtime -7 2>/dev/null || true)
[[ -z "$cronx" ]] && ok "최근 7일 변경된 cron 없음" || warn "최근 변경 cron: $(echo "$cronx" | tr '\n' ' ')"
keys=$(find /root /home -name authorized_keys -mtime -7 2>/dev/null || true)
[[ -z "$keys" ]] && ok "최근 7일 변경된 authorized_keys 없음" || warn "최근 변경 키: $(echo "$keys" | tr '\n' ' ')"
hdr "6. 접근 설정 (36·47편 예정)"
if command -v sshd >/dev/null; then
prl=$(sshd -T 2>/dev/null | awk '/^permitrootlogin/{print $2}')
[[ "$prl" == "no" || "$prl" == "prohibit-password" ]] && ok "PermitRootLogin=$prl" || warn "PermitRootLogin=$prl"
fi
if command -v getenforce >/dev/null; then
se=$(getenforce)
[[ "$se" == "Enforcing" ]] && ok "SELinux=$se" || warn "SELinux=$se"
fi
# 기준값 비교 (선택)
if [[ -n "$BASE" && -d "$BASE" ]]; then
hdr "7. 기준값 비교"
find / -xdev -type f -perm -4000 2>/dev/null | sort > /tmp/.suid_now
if [[ -f "$BASE/suid.txt" ]]; then
new=$(comm -13 "$BASE/suid.txt" /tmp/.suid_now || true)
[[ -z "$new" ]] && ok "새 SUID 없음" || warn "기준값에 없는 SUID: $(echo "$new" | tr '\n' ' ')"
fi
rm -f /tmp/.suid_now
fi
printf '\n결과: OK %d / WARN %d\n' "$PASS" "$WARN"
[[ "$WARN" -eq 0 ]] && exit 0 || exit 1
cd ~/lab
chmod 700 seccheck.sh
sudo ./seccheck.sh # 점검만
sudo ./seccheck.sh /root/baseline # 기준값과 SUID 비교
echo "종료 코드: $?" # WARN 있으면 1
실습 환경에서 실행한 실제 요약 결과다(경로 일부 축약).
== 1. 계정 (18편) ==
[ OK ] UID 0 계정은 root 뿐
[ OK ] 빈 비밀번호 계정 없음
[WARN] 로그인 쉘 가진 시스템 계정: postgres
== 2. 권한 · SUID (15·16편) ==
[ OK ] /etc/shadow 권한 640
[WARN] world-writable 파일: /.../backup.sh ...
== 4. 프로세스 (29편) ==
[ OK ] 커널 스레드 위장 프로세스 없음
[WARN] 삭제/임시 실행 프로세스 1 개
== 5. 지속성 (22·30편) ==
[ OK ] 최근 7일 변경된 cron 없음
[ OK ] 최근 7일 변경된 authorized_keys 없음
결과: OK 6 / WARN 3
| WARN 항목 | 판단 |
|---|---|
| 로그인 쉘 가진 서비스 계정 | postgres처럼 정상적으로 쉘이 필요한 경우도 있음 → 화이트리스트로 관리 |
| world-writable 파일 | 실행 경로면 위험, 임시 락 파일이면 정상 → 경로로 판단 |
| 삭제/임시 실행 프로세스 | 29편에서 본 분석 대상 → 개별 확인 필요 |
WARN이 곧 침해는 아니다. WARN은 "사람이 확인할 대상"이라는 뜻이다. 그래서 정상인 WARN은 기준값·화이트리스트로 걸러내고, 매번 새로 뜨는 WARN 에 집중한다. 이것이 10·16편에서 강조한 기준값(baseline) 비교 의 이유다.
| 원칙 | 이유 |
|---|---|
| 읽기 전용 점검 | 점검이 증거·상태를 바꾸지 않음 |
| 기준값 비교 | "정상"을 미리 정의해야 "이상"을 판정 가능 |
| WARN = 조사 대상 | 자동 차단이 아니라 사람의 확인으로 연결 |
| 스크립트 자체 보호 | root로 실행되므로 권한 700, root 소유 (15·28편) |
| 종료 코드 활용 | 모니터링·cron이 이상을 감지 |
이 스크립트도 공격 대상 이 될 수 있다. root로 정기 실행되기 때문에, 내용이 변조되면 "항상 OK"를 보고하도록 만들 수 있다. 그래서 스크립트 자체도 무결성 점검(40편 Tripwire) 대상에 포함한다.
[정상 상태] baseline 확보 (SUID·포트·계정 목록)
↓
[정기 실행] cron 매일 새벽 seccheck.sh (38편)
↓
[판정] OK/WARN + 종료 코드
↓
[연계] WARN → 로그 파일 + 중앙 서버 전송 → SIEM 이벤트 (46편)
↓
[분석] 새 WARN → 29편 방식으로 개별 프로세스·파일 파고들기
| 편 | 명령어 | 이 스크립트에서 |
|---|---|---|
| 21 | grep | 로그·설정 패턴 검색 |
| 22 | find | world-writable, SUID, 최근 변경 |
| 23 | head/tail | 결과 요약 |
| 24 | sort/uniq | 목록 정렬·비교 |
| 25 | cut/awk | 필드 추출·조건 |
| 26 | sed | 정규화 |
| 27 | 파이프·리다이렉션 | 결과 저장·비교 |
| 28 | 스크립트 | 전체 구조 |
| 29 | 시스템 분석 | 프로세스 점검 |
| 30 | 종합 | 6개 영역 점검 |
[정기 점검] seccheck.sh → WARN: 새 LISTEN 포트 4444
↓
[개별 분석] 29편: ss → lsof → /proc/PID/exe → 위장 프로세스 확인
↓
[로그 확인] 21·28편: 그 시각의 secure 로그 → 로그인 성공 계정
↓
[판단·대응] 침해 확정 → 증적·차단·지속성 점검
set -uo pipefail을 쓰되, grep 0건 때문에 -e는 빼고 || true로 처리한다.Part 4 「프로세스·서비스·리소스」가 시작된다. 다음 글 「31. Linux 프로세스 이해」 에서는 이 시리즈에서 계속 등장한 프로세스를 정면으로 다룬다. 프로세스의 상태 전이(실행·대기·좀비), PID·PPID 관계, fork/exec 모델을 정리하고, 좀비·고아 프로세스가 보안관제에서 무엇을 뜻하는지 살펴본다.