
서비스 · 프로세스 관리 49 / 50 · Part 5. 보안과 SOC
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
41~48편에서 판단 기준, 기준선, 증거 수집, 네트워크, 예약 작업, unit 변경, 자원, 감사 로그를 하나씩 다뤘다. 관제 현장에서 이를 매번 손으로 입력하면 사람마다 결과가 다르고, 빠뜨리는 항목이 생긴다. 반복 점검은 스크립트로 표준화 해야 한다.
이번 글에서는 지금까지의 점검을 하나의 읽기 전용 점검 스크립트 로 묶는다. 섹션별로 결과를 출력하고, 경고(WARN) 개수를 요약하고, UTC 시각이 붙은 파일로 저장해 이전 결과와 비교한다. 작성 중 실제로 겪은 셸 하위 셸 카운터 버그 와 수정 방법도 함께 정리한다.
| 원칙 | 구현 |
|---|---|
| 읽기 전용 | 조회 명령만 사용. 수정·삭제·종료 명령 금지 |
| 섹션 구분 | [번호] 항목 제목 → 비어 있는 섹션도 "이상 없음"이라는 결과 |
| 경고 형식 통일 | [WARN] 대상 설명 한 줄 → grep·SIEM 수집이 쉬움 |
| 요약 | 마지막 줄 요약: WARN N건 |
| 저장 | /var/log/soc-audit/<host>_<UTC>.txt |
| 매개변수 | 서버별 차이(허용 포트)는 인자로 |
| 이식성 | 배포판별 명령(rpm/dpkg)은 분기 처리 |
| 섹션 | 점검 | 근거 편 |
|---|---|---|
| 1 | 실행 파일이 삭제됐거나 임시 경로 | 41·43 |
| 2 | 허용 목록 밖 대기 포트 | 44 |
| 3 | 패키지 밖 unit·drop-in, 사용자 unit | 46 |
| 4 | 사용자 crontab, cron 패키지 변경, at 대기 | 45 |
| 5 | failed·masked unit | 23·24 |
| 6 | CPU 80% 이상 | 47 |
n=0
ss -tlnpH | while read l; do n=$((n+1)); done # while 이 하위 셸에서 실행 → 바깥 n 은 0
while read l; do n=$((n+1)); done < <(ss -tlnpH) # 현재 셸에서 실행 → n 증가
첫 버전에서는 섹션 2·4·5·6이 파이프 뒤 while을 써서 경고는 출력되는데 요약 개수에 반영되지 않았다. 프로세스 치환(< <(...))으로 고쳤다.

soc-proc-audit.sh "22 8082"
→ OUT=/var/log/soc-audit/<host>_<UTC>.txt
→ { 헤더(host·time·kernel) → [1]~[6] 점검 → 요약 } | tee $OUT
→ 다음 점검 후 diff <(grep WARN 이전) <(grep WARN 최신) → 새로 생긴 경고만
# 1) 스크립트 작성 (전체 내용은 실행 결과 화면) 후 실행 — 허용 포트 22, 8082
chmod 700 /root/soc-proc-audit.sh
/root/soc-proc-audit.sh "22 8082"
# 2) 결과 보관과 비교 — 허용 목록을 22 로 좁혀 다시 실행
ls -l /var/log/soc-audit/
/root/soc-proc-audit.sh "22" > /dev/null
diff <(grep WARN <이전 결과>) <(grep WARN <최신 결과>)
스크립트는 root 권한이 필요하지만 아무것도 바꾸지 않는다. 운영 서버에 배포하기 전에는 테스트 서버에서 실행 시간과 출력량을 확인한다.


텍스트 원본(실제 출력):
[root@rocky9-lab ~]# cat > /root/soc-proc-audit.sh <<'EOF'
> #!/bin/bash
> # 프로세스·서비스 보안 점검 v1.0 — 읽기 전용. 사용: soc-proc-audit.sh [허용포트 목록]
> ALLOW=${1:-"22"}; OUT=/var/log/soc-audit/$(hostname)_$(date -u +%Y%m%dT%H%M%SZ).txt; mkdir -p ${OUT%/*}
> n=0; warn(){ n=$((n+1)); printf ' [WARN] %s\n' "$*"; }
> sec(){ printf '\n[%s] %s\n' "$1" "$2"; }
> {
> echo "host=$(hostname) time=$(date -u +%FT%TZ) kernel=$(uname -r)"
> sec 1 "실행 파일이 삭제됐거나 임시 경로에서 실행 중인 프로세스"
> for d in /proc/[0-9]*; do e=$(readlink $d/exe 2>/dev/null) || continue
> case "$e" in *" (deleted)"|/tmp/*|/dev/shm/*|/var/tmp/*) warn "pid=${d#/proc/} user=$(stat -c %U $d) exe=$e";; esac; done
> sec 2 "허용 목록(${ALLOW}) 밖의 대기 포트"
> while read -r s r q l p x; do port=${l##*:}; [[ " $ALLOW " == *" $port "* ]] || warn "$l $(grep -oP 'users:\(\("\K[^"]+' <<<"$x")"; done < <(ss -tlnpH)
> sec 3 "systemd: 패키지 밖 unit · drop-in · 사용자 unit"
> for f in $(find /etc/systemd/system -type f \( -name '*.service' -o -name '*.timer' -o -name '*.socket' -o -name '*.conf' \)); do rpm -qf "$f" >/dev/null 2>&1 || warn "미소유 $f"; done
> for f in /home/*/.config/systemd/user/*; do [ -e "$f" ] && warn "사용자 unit $f"; done
> sec 4 "예약 작업"
> for f in /var/spool/cron/*; do [ -f "$f" ] && warn "crontab $(basename $f): $(grep -vc '^#' $f) 줄"; done
> while read l; do warn "cron 패키지 파일 변경 $l"; done < <(rpm -V cronie crontabs 2>/dev/null | grep -v '^\.\.\.\.\.\.\.T')
> [ -n "$(atq)" ] && warn "at 대기 작업 $(atq | wc -l)건"
> sec 5 "실패 · 마스크된 unit"
> while read u rest; do warn "failed $u"; done < <(systemctl --failed --no-legend --plain)
> while read u rest; do warn "masked $u"; done < <(systemctl list-unit-files --state=masked --no-legend)
> sec 6 "CPU 80% 이상 프로세스"
> while read l; do warn "$l"; done < <(ps -eo pid=,user=,%cpu=,comm= | awk '$3>=80')
> printf '\n요약: WARN %d건\n' $n
> } | tee $OUT
> echo "저장: $OUT"
> EOF
[root@rocky9-lab ~]# chmod 700 /root/soc-proc-audit.sh; /root/soc-proc-audit.sh "22 8082"
host=rocky9-lab time=2026-09-24T13:44:54Z kernel=6.18.44-fc-v37
[1] 실행 파일이 삭제됐거나 임시 경로에서 실행 중인 프로세스
[2] 허용 목록(22 8082) 밖의 대기 포트
[3] systemd: 패키지 밖 unit · drop-in · 사용자 unit
[WARN] 미소유 /etc/systemd/system/labapp.service
[WARN] 미소유 /etc/systemd/system/labapp.service.d/50-sandbox.conf
[WARN] 미소유 /etc/systemd/system/labapp.service.d/override.conf
[4] 예약 작업
[5] 실패 · 마스크된 unit
[6] CPU 80% 이상 프로세스
요약: WARN 3건
저장: /var/log/soc-audit/rocky9-lab_20260924T134454Z.txt
[root@rocky9-lab ~]# ls -l /var/log/soc-audit/
total 4
-rw-r--r-- 1 root root 560 Sep 24 13:44 rocky9-lab_20260924T134454Z.txt
[root@rocky9-lab ~]# sleep 1; /root/soc-proc-audit.sh "22" > /dev/null; ls /var/log/soc-audit/ | wc -l
2
[root@rocky9-lab ~]# diff <(grep WARN $(ls -t /var/log/soc-audit/* | sed -n 2p)) <(grep WARN $(ls -t /var/log/soc-audit/* | sed -n 1p))
0a1
> [WARN] 127.0.0.1:8082 python3
4c5
< 요약: WARN 3건
---
> 요약: WARN 4건
| 관찰 | 의미 |
|---|---|
헤더 host=rocky9-lab time=...Z kernel=6.18.44-fc-v37 | 결과 파일만 봐도 어느 서버를 언제 점검했는지 알 수 있다. 커널 버전은 실습 환경(호스트 커널 공유)을 그대로 보여 준다 |
| [1]·[2]·[4]·[5]·[6] 비어 있음 | 해당 항목 이상 없음 이라는 결과다. 섹션을 남겨 두어야 "점검했다"는 증거가 된다 |
[3] labapp.service와 drop-in 2개 미소유 | 25·28·38편에서 관리자가 만든 unit이다. 승인된 항목 이므로 예외 목록으로 관리할 대상 |
요약: WARN 3건, 저장 경로 출력 | 결과가 파일로 남았다 |
2차 실행(허용 포트 22만) → diff 4c5, WARN 3건 → 4건 | 허용 목록을 좁히자 127.0.0.1:8082 python3가 새 경고로 추가되었다. diff 화면의 > 줄이 새로 생긴 경고 다 |
결과를 보면 이 서버에서 경고는 모두 설명 가능한 항목 이다. 점검 스크립트의 역할은 판단이 아니라 판단할 대상을 빠짐없이, 같은 형식으로 보여 주는 것 이다.
| 주제 | 내용 |
|---|---|
| 스크립트 보호 | 점검 스크립트 자체가 변조되면 결과를 믿을 수 없다. root 소유·700 권한, 해시 관리, 가능하면 관제 서버에서 배포·실행 |
| 결과 보호 | 결과 파일은 서버 밖으로 수집한다. 서버 안의 과거 결과는 조작될 수 있다 |
| 예외 관리 | 승인된 항목(labapp 등)은 스크립트 안에 하드코딩하지 말고 근거·만료일이 있는 예외 파일 로 관리한다 |
| 한계 | 스크립트는 알려진 점검 항목만 본다. 기준선 비교(42편)·감사 로그(48편)와 함께 운영한다 |
[배포] 관제 서버 → 대상 서버에 스크립트 배포 (해시 확인)
[실행] systemd timer (35편) 로 매일 1회 · 결과 파일 생성
[수집] 결과 파일 → SIEM / 중앙 저장소
[분석] 서버별 WARN 수 추이 · 전일 대비 신규 WARN (diff)
[대응] 신규 WARN → 41편 판단 → 43편 수집 → 50편 절차
| 운영 지표 | 의미 |
|---|---|
| 서버별 WARN 수 | 평소보다 늘면 우선 점검 |
| 신규 WARN 수 | 전일 대비 새로 생긴 항목 — 실제 조사 대상 |
| 예외 목록 크기 | 너무 커지면 점검이 무력화된다 |
| 실행 실패 | 스크립트가 돌지 않은 서버 = 관제 공백 |
| 실수 | 결과 | 예방 |
|---|---|---|
| 파이프 뒤 while로 카운트 | 요약 개수 오류 (실측) | < <(명령) |
| 이상 없는 섹션을 출력하지 않음 | 점검 누락과 구분 불가 | 섹션 제목은 항상 출력 |
| 점검 스크립트에 조치 명령 포함 | 오탐 한 번에 서비스 장애 | 점검과 조치 분리 |
| 결과를 로컬에만 저장 | 비교·보존 불가 | 중앙 수집 |
| 배포판 차이 무시 | Ubuntu에서 rpm 없음 → 섹션 오류 | command -v rpm \|\| dpkg 분기 |
[ ] 읽기 전용 · 섹션 · WARN 형식 · 요약 · 저장 원칙으로 스크립트를 작성했다
[ ] 6개 섹션이 각각 어느 편의 점검인지 연결할 수 있다
[ ] 파이프 뒤 while 의 하위 셸 카운터 문제를 이해하고 수정했다
[ ] 결과 파일을 UTC 시각 이름으로 저장했다
[ ] 허용 목록을 바꿔 두 결과를 diff 로 비교했다
[ ] 승인된 항목을 예외로 관리하는 방법을 설명할 수 있다
< <(명령)으로 입력한다.시리즈의 마지막 글 「50. 서비스·프로세스 보안관제 종합 실습」 에서는 훈련용 시나리오(무해한 항목)를 두고 탐지 → 증거 수집 → 타임라인 → 격리·제거 → 검증 → 보고 의 전 과정을 이 시리즈의 도구만으로 수행하고, 보고서 템플릿으로 정리한다.