
서비스 · 프로세스 관리 44 / 50 · Part 5. 보안과 SOC
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
16편에서 ss -p로 포트와 프로세스를 연결하는 방법을 배웠다. 관제 점검에서는 여기서 한 걸음 더 나아가야 한다. "22번 포트를 sshd가 쓰고 있다"는 사실만으로는 판단이 안 된다. 이 서버에서 그 포트가 승인된 것인지, 그 프로세스가 정상 경로의 프로그램인지 를 함께 봐야 한다.
이번 글에서는 대기 포트를 소유 프로세스 · 실행 계정 · 실행 파일 경로 · 허용 목록 과 결합해 한 줄씩 판단하는 점검 스크립트를 만들고, 허용 목록에 없는 테스트 웹 서버를 찾아낸다. 외부 연결 요약까지 포함해 네트워크 관점의 점검 절차를 정리한다.
| 항목 | 질문 | 도구 |
|---|---|---|
| 대기 포트 | 이 서버가 받는 접속은 무엇인가 | ss -tlnp, ss -ulnp |
| 바인딩 주소 | 외부에 노출되는가 (0.0.0.0, [::]) 로컬 전용인가 (127.0.0.1) | ss -tln |
| 소유 프로세스 | 누가, 어떤 실행 파일로 열었나 | /proc/PID/exe, ps -o user |
| 허용 여부 | 서버 역할상 승인된 포트인가 | 허용 목록 (자산 관리) |
| 연결 | 누구와 통신 중인가 | ss -tnp state established |
| 방식 | 장점 | 단점 |
|---|---|---|
| 허용 목록(allowlist) | 목록 밖은 모두 표시 → 새로운 것에 강함 | 서버별 목록 관리 필요 |
| 차단 목록(denylist) | 관리가 쉬움 | 알려진 것만 잡음 |
서버마다 역할이 정해져 있으므로(웹 서버: 80·443, DB: 3306 …) 대기 포트는 허용 목록으로 관리하기 좋다. 42편의 포트 기준선이 허용 목록의 출발점이 된다.

ss -tlnpH 한 줄씩
→ LOCAL 에서 포트 추출, users:(("...",pid=N,...)) 에서 PID 추출
→ readlink /proc/N/exe , ps -o user= -p N
→ 포트 ∉ ALLOW → NOT_ALLOWED
→ exe ∈ /tmp · /dev/shm · /var/tmp → TMP_EXE
→ 표 한 줄 출력
두 플래그가 동시에 붙으면 41편 기준으로 우선 확인 대상이다. 반대로 허용 포트라도 실행 파일 경로가 바뀌었다면(예: sshd가 /usr/sbin이 아닌 경로) 확인해야 하므로, 실행 경로 열은 허용 포트에도 항상 표시한다.
# 1) (analyst) 점검 대상: 임시 경로의 python 복사본으로 연 테스트 웹 서버 (로컬 전용)
cp /usr/bin/python3 /tmp/py-lab && cd /tmp && nohup /tmp/py-lab -m http.server 8443 --bind 127.0.0.1 > /dev/null 2>&1 &
# 2) (root) 점검 스크립트 /root/net-audit.sh
# ALLOW="22 8082" ← 이 서버에서 승인된 대기 포트
# ss 결과 + exe + user + 플래그(NOT_ALLOWED, TMP_EXE)
/root/net-audit.sh
# 3) 연결 요약 (SSH 외 연결 수)
ss -tnpH state established | awk '{print $3, "<->", $4}' | head -5
ss -tnpH state established '( not sport = :22 )' | wc -l



텍스트 원본(실제 출력):
[analyst@rocky9-lab ~]$ cp /usr/bin/python3 /tmp/py-lab && cd /tmp && nohup /tmp/py-lab -m http.server 8443 --bind 127.0.0.1 > /dev/null 2>&1 &
[analyst@rocky9-lab ~]$ sleep 1; ss -tlnH 'sport = :8443' | wc -l
1
[root@rocky9-lab ~]# cat > /root/net-audit.sh <<'EOF'
> #!/bin/bash
> ALLOW="22 8082" # 이 서버에서 승인된 대기 포트
> printf '%-22s %-7s %-8s %-26s %s\n' LOCAL PID USER EXE FLAG
> ss -tlnpH | while read -r st rq sq local peer proc; do
> port=${local##*:}; pid=$(grep -oP 'pid=\K[0-9]+' <<<"$proc" | head -1)
> exe=$(readlink /proc/$pid/exe); user=$(ps -o user= -p $pid); f=""
> [[ " $ALLOW " == *" $port "* ]] || f="$f NOT_ALLOWED"
> case "$exe" in /tmp/*|/dev/shm/*|/var/tmp/*) f="$f TMP_EXE";; esac
> printf '%-22s %-7s %-8s %-26s %s\n' "$local" "$pid" "$user" "${exe:0:26}" "${f:- ok}"
> done
> EOF
[root@rocky9-lab ~]# chmod 700 /root/net-audit.sh; /root/net-audit.sh
LOCAL PID USER EXE FLAG
0.0.0.0:22 28 root /usr/sbin/sshd ok
127.0.0.1:8082 31 analyst /usr/bin/python3.9 ok
127.0.0.1:8443 1462 analyst /tmp/py-lab NOT_ALLOWED TMP_EXE
[root@rocky9-lab ~]# ss -tnpH state established | awk '{print $3, "<->", $4}' | head -5
172.17.0.2:22 <-> 172.17.0.1:52510
[root@rocky9-lab ~]# ss -tnpH state established '( not sport = :22 )' | wc -l
0
[root@rocky9-lab ~]# pkill -u analyst -f py-lab; rm -f /tmp/py-lab; true
| 관찰 | 의미 |
|---|---|
0.0.0.0:22 ... /usr/sbin/sshd ok | 승인된 포트, 정상 경로. 다만 0.0.0.0이라 외부 노출 되어 있음을 인지한다 |
127.0.0.1:8082 ... /usr/bin/python3.9 ok | labapp. 허용 목록에 있고 로컬 전용이다 |
127.0.0.1:8443 1462 analyst /tmp/py-lab NOT_ALLOWED TMP_EXE | 승인되지 않은 포트 + 임시 경로 실행 파일. 확인 대상 1순위 |
연결 요약: 172.17.0.2:22 <-> 172.17.0.1:52510 | 현재 연결은 실습용 SSH 하나뿐이다 |
| SSH 외 연결 0건 | 이 서버는 지금 다른 곳과 통신하지 않는다. 이것도 기록할 사실이다 |
실행 파일이
/usr/bin/python3.9인 labapp과/tmp/py-lab인 테스트 서버는 같은 python 프로그램 이다. 프로그램 이름이 아니라 실행 경로 를 봐야 둘을 구분할 수 있다.
| 주제 | 내용 |
|---|---|
| 노출 범위 | 내부용 관리 도구·DB가 0.0.0.0에 바인딩되어 있으면 방화벽 한 줄 실수로 외부에 노출된다. 바인딩 주소 자체를 점검한다 |
| 소켓 활성화 | 포트 주인이 systemd(PID 1)로 보이면 37편 방식으로 socket unit을 확인한다 |
| 권한 | 전체 결과를 보려면 root로 실행해야 한다. 일반 계정 결과의 빈 Process 열은 "없음"이 아니다 (16편) |
| 도구 변조 | ss 결과는 /proc/net/tcp 직접 확인, 외부에서의 포트 스캔 결과와 교차 검증한다 |
| 나가는 연결 | 대기 포트 점검만으로는 서버가 먼저 연결을 여는 통신을 볼 수 없다. established 연결과 방화벽·프록시 로그를 함께 본다 |
[정기 점검] /root/net-audit.sh > net_$(hostname)_$(date -u +%FT%H%MZ).txt
↓
[판단] NOT_ALLOWED 또는 TMP_EXE 행 → 41편 기준 확인
허용 포트인데 EXE 경로가 평소와 다름 → 확인
↓
[교차 검증] 방화벽·NetFlow 로그: 해당 포트로 외부 접속이 있었는가
외부 포트 스캔 결과와 서버 내부 결과 비교
↓
[조치 전] 43편 증거 수집 (소켓 정보 포함)
↓
[개선] 승인된 포트면 허용 목록·기준선 갱신 / 아니면 원인 조사
| 서버 역할 | 허용 목록 예 |
|---|---|
| 웹 서버 | 22(관리망), 80, 443 |
| DB 서버 | 22(관리망), 3306 (앱 서버 대역만) |
| 관제 수집 서버 | 22, 514(syslog), 5044(beats) |
| 실수 | 결과 | 예방 |
|---|---|---|
| 포트 번호만 보고 정상 판단 | 다른 프로그램이 같은 포트를 쓰는 경우 누락 | 실행 경로 병기 |
| TCP만 점검 | UDP 대기(DNS, syslog 등) 누락 | ss -ulnp 병행 |
| 허용 목록 없이 매번 판단 | 사람마다 결과가 다름 | 서버 역할별 목록 문서화 |
| IPv6 대기 누락 | [::]:포트 노출 간과 | IPv6 결과 포함 |
ss -tnp state established의 열 위치 착각 | 잘못된 필드 출력 | state 필터 사용 시 State 열이 빠짐을 기억 |
[ ] 대기 포트·바인딩 주소·소유 프로세스·허용 여부를 점검 항목으로 정리했다
[ ] ss 결과에 exe·user·플래그를 붙인 점검 스크립트를 작성했다
[ ] 허용 목록 밖 + 임시 경로 테스트 서버를 NOT_ALLOWED TMP_EXE 로 식별했다
[ ] 같은 python 이라도 실행 경로로 정상/확인 대상을 구분했다
[ ] SSH 외 연결 수를 확인하고 기록했다
[ ] 서버 역할별 허용 목록 예를 작성할 수 있다
0.0.0.0 vs 127.0.0.1).다음 글 「45. 예약 작업(cron·at) 무결성 점검」 에서는 cron 파일의 기준 해시 를 만들고, 패키지 파일 검증(rpm -V)과 수정 시각으로 변경을 탐지한 뒤 원래 상태로 복구하는 절차를 실습한다.