리눅스 시스템 기초 · 입문편 — 본문의 "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 파일·권한·사용자 보안관제 실습

1. 들어가며

Part 3의 마지막 글이다. 21편 grep부터 29편 시스템 분석까지 배운 명령어를 하나의 보안 점검 스크립트 로 묶는다. Part 2의 권한·계정·SUID 개념과 Part 3의 명령어 조합·스크립트 작성이 여기서 합쳐진다.

이 스크립트는 침해사고가 났을 때가 아니라 평소에 정기적으로 돌려 "정상 상태에서 벗어난 것"을 찾는 용도다. 10편의 Triage 스크립트가 "정보를 수집"했다면, 이 스크립트는 그 정보를 판정 한다.

목표는 하나다. 사람이 매번 기억해서 명령어를 치지 않아도, 계정·권한·네트워크·프로세스·지속성·접근 설정을 한 번에 점검하고 OK/WARN으로 요약 하는 것.


2. 핵심 개념

2-1. 점검 6개 영역

영역점검 항목관련 글
1. 계정UID 0 중복, 빈 비밀번호, 로그인 쉘 가진 시스템 계정18편
2. 권한·SUID/etc/shadow 권한, world-writable 파일15·16편
3. 네트워크LISTEN 포트 목록1편
4. 프로세스커널 스레드 위장, 삭제·임시 경로 실행29편
5. 지속성최근 변경된 cron, authorized_keys22편
6. 접근 설정PermitRootLogin, SELinux 상태36·47편

2-2. 점검(check)과 수집(triage)의 차이

10편 Triage30편 seccheck
목적정보 수집정상/이상 판정
출력원본 명령 결과 파일OK / WARN 요약
판단사람이 나중에스크립트가 기준으로
종료 코드항상 0WARN 있으면 1 (자동화 연계)

3. 동작 원리

기본 보안 점검 스크립트 — Part 2·3의 명령어를 하나로

핵심 설계는 세 가지다.

  1. set -uo pipefail — -e는 뺐다. grep이 "0건"일 때 종료 코드 1을 반환하는데(27편), -e가 있으면 정상인데도 스크립트가 멈추기 때문이다. 대신 각 명령에 || true를 붙여 결과 없음을 허용한다.
  2. 모두 읽기 전용 — 점검은 시스템을 바꾸지 않는다(10·11편 원칙).
  3. OK/WARN 카운트 + 종료 코드 — WARN이 하나라도 있으면 exit 1. cron이나 모니터링 도구가 이 종료 코드로 이상 여부를 감지할 수 있다.

4. 실습 — 보안 점검 스크립트

#!/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

5. 결과 분석

실습 환경에서 실행한 실제 요약 결과다(경로 일부 축약).

== 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) 비교 의 이유다.


6. 보안 관점

원칙이유
읽기 전용 점검점검이 증거·상태를 바꾸지 않음
기준값 비교"정상"을 미리 정의해야 "이상"을 판정 가능
WARN = 조사 대상자동 차단이 아니라 사람의 확인으로 연결
스크립트 자체 보호root로 실행되므로 권한 700, root 소유 (15·28편)
종료 코드 활용모니터링·cron이 이상을 감지

이 스크립트도 공격 대상 이 될 수 있다. root로 정기 실행되기 때문에, 내용이 변조되면 "항상 OK"를 보고하도록 만들 수 있다. 그래서 스크립트 자체도 무결성 점검(40편 Tripwire) 대상에 포함한다.


7. SOC / 보안관제 활용

7-1. 점검 자동화 흐름

[정상 상태]  baseline 확보 (SUID·포트·계정 목록)
   ↓
[정기 실행]  cron 매일 새벽 seccheck.sh (38편)
   ↓
[판정]       OK/WARN + 종료 코드
   ↓
[연계]       WARN → 로그 파일 + 중앙 서버 전송 → SIEM 이벤트 (46편)
   ↓
[분석]       새 WARN → 29편 방식으로 개별 프로세스·파일 파고들기

7-2. Part 3 전체 정리

편명령어이 스크립트에서
21grep로그·설정 패턴 검색
22findworld-writable, SUID, 최근 변경
23head/tail결과 요약
24sort/uniq목록 정렬·비교
25cut/awk필드 추출·조건
26sed정규화
27파이프·리다이렉션결과 저장·비교
28스크립트전체 구조
29시스템 분석프로세스 점검
30종합6개 영역 점검

7-3. 분석 흐름

[정기 점검]  seccheck.sh → WARN: 새 LISTEN 포트 4444
   ↓
[개별 분석]  29편: ss → lsof → /proc/PID/exe → 위장 프로세스 확인
   ↓
[로그 확인]  21·28편: 그 시각의 secure 로그 → 로그인 성공 계정
   ↓
[판단·대응]  침해 확정 → 증적·차단·지속성 점검

8. 핵심 정리

  • 보안 점검 스크립트는 정상에서 벗어난 것을 정기적으로 찾는 도구다 (Triage = 수집, seccheck = 판정).
  • 6개 영역(계정·권한·네트워크·프로세스·지속성·접근 설정)을 읽기 전용 으로 점검한다.
  • set -uo pipefail을 쓰되, grep 0건 때문에 -e는 빼고 || true로 처리한다.
  • WARN은 침해가 아니라 "확인 대상" 이다. 기준값·화이트리스트로 정상 WARN을 걸러낸다.
  • 종료 코드(WARN 시 1)로 cron·모니터링과 연계한다.
  • 스크립트 자체도 root 실행이므로 권한 700, 무결성 점검 대상에 포함한다.

9. 다음 글

Part 4 「프로세스·서비스·리소스」가 시작된다. 다음 글 「31. Linux 프로세스 이해」 에서는 이 시리즈에서 계속 등장한 프로세스를 정면으로 다룬다. 프로세스의 상태 전이(실행·대기·좀비), PID·PPID 관계, fork/exec 모델을 정리하고, 좀비·고아 프로세스가 보안관제에서 무엇을 뜻하는지 살펴본다.


참고 자료

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

0개의 댓글