
서비스 · 프로세스 관리 41 / 50 · Part 5. 보안과 SOC
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
Part 5 「보안과 SOC」를 시작한다. Part 1~4에서 프로세스의 구조(exe, cwd, PPID, 세션, cgroup, fd, 소켓)와 서비스·예약 작업의 동작을 배웠다. 이제 이 지식을 관제 판단 에 쓴다.
관제 요원이 서버 한 대를 점검할 때 수백 개의 프로세스를 하나씩 볼 수는 없다. 필요한 것은 "무엇을 먼저 봐야 하는가"를 정하는 기준 이다. 이번 글에서는 지금까지 배운 지표를 판단 기준표로 정리하고, 읽기 전용 점검 스크립트로 사용자 프로세스에 플래그를 붙여 확인 우선순위를 정하는 실습을 한다.
Part 5의 실습은 모두 무해한 테스트 항목(sleep·python 복사본, logger 한 줄)을 대상으로 한다. 목표는 공격 재현이 아니라 탐지·수집·판단 절차 를 익히는 것이다.
| 분류 | 지표 | 확인 위치 | 관련 편 |
|---|---|---|---|
| 실행 파일 | exe가 (deleted) | /proc/PID/exe | 01·15 |
exe가 /tmp, /dev/shm, /var/tmp | /proc/PID/exe | 05 | |
이름이 .으로 시작 (숨김) | exe 경로 | — | |
| 패키지 소유가 아님 | rpm -qf / dpkg -S | 22 | |
| 실행 주체 | 서비스 계정의 셸·다운로드 도구 | ps -o user,comm | 30 |
| 예상 밖의 부모 (웹 서버 → 셸) | pstree -ps | 02·03 | |
| 생존 방식 | PPID 1 + TTY 없음 | ps -o ppid,tty | 08·19 |
| 서비스가 아닌 세션 scope 소속 | /proc/PID/cgroup | 18 | |
| 행위 | 허용 목록 밖 포트, 외부 연결 | ss -tnp | 16 |
| 지속적인 높은 CPU | top, ps --sort | 07·47 |
| 원칙 | 설명 |
|---|---|
| 지표는 "확인 필요" 신호 | 하나의 지표만으로 악성이라 판단하지 않는다 |
| 조합 으로 우선순위 | 여러 지표가 겹칠수록 우선 확인 |
| 정상 맥락 확인 | 서비스 cgroup, 패키지 소유, 변경 관리 기록으로 설명되면 정상 |
| 읽기 전용 | 판단 전에는 아무것도 바꾸지 않는다 (증거 보존, 43편) |

/proc/[0-9]* 순회 (root 소유 제외 — 사용자 프로세스 우선)
→ exe · cwd · PPID · TTY 읽기
→ 규칙별 플래그: DELETED_EXE / TMP_EXE / HIDDEN_NAME / ORPHAN_NO_TTY
→ 플래그 개수로 확인 순서 결정
→ 상위 항목 상세 확인: cmdline · lstart · cgroup · sessionid
실습 스크립트는 사용자 프로세스만 대상으로 했다. root 프로세스는 수가 많고 정상 데몬이 대부분이라, 실무에서는 패키지 소유 여부 같은 추가 기준(49편)과 함께 본다.
# 1) (analyst) 점검 대상: 확인이 필요한 형태의 테스트 프로세스와 일반 nohup 작업
cp /usr/bin/sleep /tmp/.cache-helper && setsid /tmp/.cache-helper 600 < /dev/null > /dev/null 2>&1 &
rm -f /tmp/.cache-helper; nohup sleep 700 > /dev/null 2>&1 &
# 2) (root) 판단 기준표를 적용하는 읽기 전용 스크립트 /root/triage.sh
# - 사용자(비 root) 프로세스의 PID·USER·PPID·TTY·EXE·CWD 출력
# - DELETED_EXE / TMP_EXE / HIDDEN_NAME / ORPHAN_NO_TTY 플래그
/root/triage.sh
# 3) 플래그가 가장 많은 프로세스 상세 확인
ls -l /proc/$P/exe; tr '\0' ' ' < /proc/$P/cmdline
ps -o pid,ppid,user,lstart,etime,stat,cmd -p $P
cat /proc/$P/cgroup | tail -1; cat /proc/$P/sessionid



텍스트 원본(실제 출력):
[analyst@rocky9-lab ~]$ cp /usr/bin/sleep /tmp/.cache-helper && setsid /tmp/.cache-helper 600 < /dev/null > /dev/null 2>&1 &
[analyst@rocky9-lab ~]$ sleep 0.3; rm -f /tmp/.cache-helper; nohup sleep 700 > /dev/null 2>&1 &
[analyst@rocky9-lab ~]$ sleep 0.3; pgrep -a -u analyst sleep; pgrep -a -u analyst -f cache-helper
860 sleep 700
858 /tmp/.cache-helper 600
[root@rocky9-lab ~]# cat > /root/triage.sh <<'EOF'
> #!/bin/bash
> # 사용자 프로세스를 점검 기준으로 표시 (읽기 전용)
> printf '%-6s %-8s %-6s %-4s %-28s %-14s %s\n' PID USER PPID TTY EXE CWD FLAGS
> for d in /proc/[0-9]*; do
> p=${d#/proc/}; u=$(stat -c %U $d 2>/dev/null) || continue
> [ "$u" = root ] && continue
> exe=$(readlink $d/exe 2>/dev/null) || continue
> cwd=$(readlink $d/cwd 2>/dev/null); ppid=$(awk '/^PPid/{print $2}' $d/status)
> tty=$(ps -o tty= -p $p | tr -d ' '); f=""
> case "$exe" in *" (deleted)") f="$f DELETED_EXE";; esac
> case "$exe" in /tmp/*|/dev/shm/*|/var/tmp/*) f="$f TMP_EXE";; esac
> case "$(basename "${exe% (deleted)}")" in .*) f="$f HIDDEN_NAME";; esac
> [ "$ppid" = 1 ] && [ "$tty" = "?" ] && f="$f ORPHAN_NO_TTY"
> printf '%-6s %-8s %-6s %-4s %-28s %-14s %s\n' "$p" "$u" "$ppid" "$tty" "${exe:0:28}" "${cwd:0:14}" "${f:- -}"
> done
> EOF
[root@rocky9-lab ~]# chmod 700 /root/triage.sh; /root/triage.sh
PID USER PPID TTY EXE CWD FLAGS
31 analyst 1 ? /usr/bin/python3.9 /opt/labapp ORPHAN_NO_TTY
35 dbus 1 ? /usr/bin/dbus-broker-launch / ORPHAN_NO_TTY
37 dbus 35 ? /usr/bin/dbus-broker / -
821 analyst 1 ? /usr/lib/systemd/systemd / ORPHAN_NO_TTY
823 analyst 821 ? /usr/lib/systemd/systemd / -
858 analyst 1 ? /tmp/.cache-helper (deleted) /home/analyst DELETED_EXE TMP_EXE HIDDEN_NAME ORPHAN_NO_TTY
860 analyst 1 ? /usr/bin/sleep /home/analyst ORPHAN_NO_TTY
[root@rocky9-lab ~]# P=$(for d in /proc/[0-9]*; do readlink $d/exe 2>/dev/null | grep -q deleted && echo ${d#/proc/}; done | head -1); echo "PID=$P"
PID=858
[root@rocky9-lab ~]# ls -l /proc/$P/exe; tr '\0' ' ' < /proc/$P/cmdline; echo
lrwxrwxrwx 1 analyst analyst 0 Sep 24 13:40 /proc/858/exe -> '/tmp/.cache-helper (deleted)'
/tmp/.cache-helper 600
[root@rocky9-lab ~]# ps -o pid,ppid,user,lstart,etime,stat,cmd -p $P
PID PPID USER STARTED ELAPSED STAT CMD
858 1 analyst Thu Sep 24 13:40:13 2026 00:01 Ss /tmp/.cache-helper 600
[root@rocky9-lab ~]# cat /proc/$P/cgroup | tail -1; cat /proc/$P/sessionid; echo
0::/docker/5a46f907edb23397a00c52e8214c9c387f1cf283dd50c13b81f6c01a58252f3b/user.slice/user-1000.slice/session-15.scope
15
[root@rocky9-lab ~]# pkill -u analyst -x sleep; pkill -u analyst -f cache-helper; true
| 관찰 | 의미 |
|---|---|
PID 858 /tmp/.cache-helper (deleted) — 플래그 4개 | 임시 경로·숨김 이름·삭제된 실행 파일·터미널 없는 고아. 가장 먼저 확인할 대상 이다 |
PID 860 /usr/bin/sleep — ORPHAN_NO_TTY | 정상 경로의 프로그램이지만 로그아웃 후에도 남은 작업이다. 소유자에게 용도를 확인한다 |
PID 31 python3.9 (labapp) — ORPHAN_NO_TTY | systemd 서비스는 원래 PPID 1·TTY 없음이다. cgroup이 labapp.service 라면 정상 — 규칙의 오탐이다 |
| dbus·systemd --user — ORPHAN_NO_TTY | 시스템·사용자 manager도 같은 모양이다. 이 플래그 하나로는 판단할 수 없다 |
858 상세: cmdline /tmp/.cache-helper 600, STAT Ss | setsid로 새 세션의 리더 가 된 프로세스 (19편) |
cgroup .../user-1000.slice/session-15.scope | 서비스가 아니라 analyst의 로그인 세션 에서 시작되었다 |
sessionid 15 | 이 번호로 인증 로그·감사 로그의 로그인 기록과 연결할 수 있다 (48·50편) |
규칙 기반 점검은 오탐(ORPHAN_NO_TTY의 정상 서비스) 을 반드시 낳는다. 그래서 플래그 개수로 우선순위를 정하고, cgroup·패키지 소유 같은 정상 맥락 으로 걸러 내는 단계가 필요하다.
| 주제 | 내용 |
|---|---|
| 우선순위 | 관제 시간은 한정되어 있다. 지표 조합으로 확인 순서를 정하는 것 자체가 관제 역량이다 |
| 정상 맥락 | systemd 서비스, 패키지 소유 파일, 변경 관리 기록은 정상 판단의 근거가 된다. 반대로 이들로 설명되지 않는 항목이 조사 대상이다 |
| 증거 우선 | 플래그가 붙었다고 바로 종료하지 않는다. (deleted) 실행 파일은 프로세스가 살아 있을 때만 복사할 수 있다 (43편) |
| 사람 확인 | 사용자 계정의 프로세스는 기술 판단만으로 끝내지 않고 소유자에게 용도를 확인 하는 절차를 둔다 |
[점검 실행] /root/triage.sh > triage_$(date +%F_%H%M).txt
↓
[우선순위] 플래그 수 내림차순 → 상위 항목부터
↓
[정상 맥락] cat /proc/PID/cgroup → *.service 면 해당 unit 확인 (패키지·변경 기록)
rpm -qf / dpkg -S <exe> → 패키지 소유 여부
↓
[설명 불가] 43편 휘발성 증거 수집 → 44편 네트워크 확인 → 담당자·소유자 확인
↓
[기록] 판단 근거(지표·맥락·확인 결과)를 티켓에 남김
| 판단 결과 | 조치 |
|---|---|
| 정상 (맥락으로 설명됨) | 예외 목록에 근거와 함께 등록 |
| 확인 필요 | 증거 수집 후 소유자 확인 |
| 이상 (설명 불가 + 지표 다수) | 에스컬레이션, 증거 수집, 격리 판단 |
| 실수 | 결과 | 예방 |
|---|---|---|
| 지표 하나로 악성 판정 | 정상 서비스 중단 | 조합 + 정상 맥락 확인 |
| 플래그 붙은 프로세스 즉시 kill | 증거 소실 | 수집 후 조치 |
| 일반 계정으로 점검 | exe·cwd를 못 읽음 | root로 실행 |
| 이름(comm)만 보고 판단 | 위장 이름에 속음 | exe 경로 기준 |
| 오탐 예외를 근거 없이 등록 | 실제 이상을 가림 | 예외에 근거·만료일 기록 |
[ ] 판단 기준표의 지표와 확인 위치를 정리했다
[ ] 읽기 전용 점검 스크립트로 사용자 프로세스에 플래그를 붙였다
[ ] 플래그 4개가 겹친 테스트 프로세스를 최우선으로 식별했다
[ ] 정상 서비스(labapp)의 ORPHAN_NO_TTY 오탐을 cgroup 으로 걸러냈다
[ ] cgroup 과 sessionid 로 프로세스의 시작 세션을 확인했다
[ ] 판단 결과별 조치(예외·확인·에스컬레이션)를 구분할 수 있다
다음 글 「42. 정상 프로세스 기준선(baseline) 만들기」 에서는 "평소 상태"를 파일로 저장해 두고 변화만 찾아내는 방법을 다룬다. 프로세스·대기 포트·활성 unit의 기준선을 만들고, 새 서비스와 포트가 생겼을 때 diff로 찾아낸다.