📚 네트워크 · 패킷 분석 › 02. 포트 · 프로토콜 분석 — 89편
이전 글: 88. 위험 Port · 다음 글: 90. 정상 포트로 위장한 통신 — 443을 쓰는 비정상 트래픽
참고(리눅스 시스템 기초): 16. 프로세스와 네트워크 소켓 (ss -p) — 소켓과 프로세스를 연결해 보는 기본 방법
02편에서 "포트 번호만으로 서비스를 단정하면 안 된다"고 정리했습니다. 이번 글은 그 다음 단계, 그렇다면 실제로 무엇이 동작하는지 어떻게 확인하는가를 다룹니다.
비표준 포트는 크게 두 가지 이유로 등장합니다.
| 구분 | 예 |
|---|---|
| 정상적인 이유 | 같은 서버에 웹 서비스 여러 개(8080, 8443), 관리 콘솔, 개발용 서버, SSH 포트 변경(2222), 애플리케이션 고유 포트 |
| 의심해야 할 이유 | 허가되지 않은 원격 접속 도구, 백도어 리스너, 탐지를 피하려는 목적의 포트 변경, 임시로 띄워 둔 파일 공유 서버 |
관제 이벤트나 점검 결과에 "알 수 없는 포트 45123 LISTEN" 같은 항목이 나왔을 때, 포트 번호를 검색해서 나온 서비스 이름을 그대로 적으면 틀릴 수 있습니다. 이 글의 목표는 증거에 기반해 서비스를 식별하는 절차를 갖추는 것입니다.
| 방향 | 방법 | 신뢰도 | 조건 |
|---|---|---|---|
| 호스트 내부 | 소켓 → 프로세스 → 실행 파일 → 패키지 → 서비스 유닛 | 높음 | 해당 서버 접근 권한 필요 |
| 네트워크 | 접속 시 응답 형태(배너), 트래픽 첫 바이트 패턴, 프로토콜 파서 판정 | 중간 | 서버 접근 권한이 없어도 가능 |
가능하면 호스트 내부 확인이 우선이고, 네트워크 확인은 이를 보완하거나 서버 접근이 어려울 때 사용합니다.
많은 프로토콜은 연결 직후 누가 먼저 말하는지, 첫 바이트가 무엇인지로 구분할 수 있습니다.
| 프로토콜 | 먼저 말하는 쪽 | 첫 신호 특징 |
|---|---|---|
| SSH | 서버(그리고 클라이언트도 버전 문자열 전송) | SSH-2.0-... 형태의 텍스트 한 줄 |
| FTP / SMTP | 서버 | 220 ... 응답 코드로 시작하는 텍스트 |
| HTTP | 클라이언트 | GET / HTTP/1.1 같은 요청 줄, 응답은 HTTP/1.1 200 OK |
| TLS (HTTPS 등) | 클라이언트 | 첫 바이트 0x16(Handshake 레코드), 이어서 버전 0x03 0x0? |
| MySQL/MariaDB | 서버 | 버전 문자열을 포함한 greeting 패킷 |
| Redis | 클라이언트 | *1\r\n$4\r\nPING\r\n 같은 RESP 형식 텍스트 |
"서버가 먼저 말하는 프로토콜"은 연결만 해도 정체가 드러나고, "클라이언트가 먼저 말하는 프로토콜"은 적절한 요청을 보내야 응답을 볼 수 있습니다.
IDS·NSM 도구는 포트 번호와 관계없이 페이로드를 보고 프로토콜을 판정하는 기능을 가지고 있습니다.
app_proto 필드로 기록conn.log의 service 필드에 기록이 도구들의 상세 사용법은 03. Wireshark 패킷 분석, 06. 방화벽 · IDS/IPS 분석 시리즈에서 다룹니다.
"알 수 없는 포트"를 발견했을 때의 식별 흐름입니다.

그림 1. 포트 번호가 아니라 '누가 · 무엇을 · 왜' 열었는지로 판정합니다
알 수 없는 포트 발견 (예: TCP 8081 LISTEN)
↓
[호스트 접근 가능?] ── 예 ──→ ss -tlnp → PID·프로세스 이름
│ ↓
│ /proc/<PID>/exe → 실제 실행 파일 경로
│ ↓
│ rpm -qf / dpkg -S → 어느 패키지 소속인가 (없으면 수동 설치 파일)
│ ↓
│ systemctl status <PID> → 어떤 서비스 유닛이 띄웠는가
│ ↓
│ 실행 인자·작업 디렉터리·실행 계정 확인
↓ 아니오
[네트워크 확인] 연결 후 대기 → 서버가 먼저 말하는가? (SSH/FTP/SMTP/DB 배너)
↓ 응답 없음
HTTP 요청 시도 → HTTP 응답인가?
↓ 아니오
TLS 연결 시도 → 인증서·프로토콜 협상 결과 확인
↓
IDS/NSM의 app_proto·service 판정과 대조
↓
결론: "포트 8081 = python3 http.server (사용자 계정 실행, 패키지 외 스크립트)"
핵심은 결론에 포트 번호가 아니라 프로세스·실행 파일·실행 주체가 들어가야 한다는 점입니다.
실습 환경: 본인 소유 Linux VM (Rocky Linux 또는 Ubuntu). 인터페이스 ens33은 예시이며, 서버 VM 192.168.10.20과 확인용 VM 모두 본인 소유입니다.
# Rocky Linux
sudo dnf install -y nmap-ncat curl openssl lsof
# Ubuntu
sudo apt update && sudo apt install -y netcat-openbsd curl openssl lsof
# 터미널 1: 8081 포트에 간단한 HTTP 서버 (실습 후 Ctrl+C로 종료)
mkdir -p ~/lab-web && cd ~/lab-web && echo "lab" > index.html
python3 -m http.server 8081 --bind 192.168.10.20
sudo ss -tlnp | grep ':8081'
PID=$(sudo ss -tlnpH 'sport = :8081' | grep -oP 'pid=\K[0-9]+' | head -n1)
echo "$PID"
sudo readlink -f /proc/$PID/exe # 실제 실행 파일
sudo cat /proc/$PID/cmdline | tr '\0' ' '; echo # 실행 인자
sudo readlink -f /proc/$PID/cwd # 작업 디렉터리
ps -o user=,pid=,ppid=,lstart=,cmd= -p "$PID" # 실행 계정·시작 시각·부모 프로세스
systemctl status "$PID" --no-pager # 서비스 유닛 소속 여부
sudo lsof -i :8081 -P -n
실행 파일이 어느 패키지에 속하는지 확인합니다.
# Rocky Linux
rpm -qf "$(sudo readlink -f /proc/$PID/exe)"
# Ubuntu
dpkg -S "$(sudo readlink -f /proc/$PID/exe)"
# 서버가 먼저 말하는지 확인 (SSH 포트 예시, 3초 후 종료)
timeout 3 nc 192.168.10.20 22
# HTTP 응답 여부 확인
curl -sv --max-time 3 http://192.168.10.20:8081/ -o /dev/null
# TLS 서비스인지 확인 (8443 등에서)
openssl s_client -connect 192.168.10.20:8443 </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
nmap의 서비스 버전 탐지(-sV)도 같은 원리를 자동화한 것이지만, 스캔은 05. 네트워크 스캔 · 공격 징후 분석 시리즈에서 본인 실습 환경을 대상으로만 다룹니다.
📷 [실습 화면 삽입]
ss -tlnp와/proc/<PID>/exe,cmdline확인 결과
📷 [실습 화면 삽입] 다른 VM에서
nc(SSH 배너)와curl -v(HTTP 응답 헤더) 결과
SSH 배너 형식 예시(값은 환경마다 다름)
SSH-2.0-OpenSSH_8.7
curl -sv 응답 헤더 일부 형식 예시(값은 환경마다 다름)
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.9.18
< Content-type: text/html
호스트 내부 확인 결과를 정리하면 다음과 같습니다. 형식 예시(값은 환경마다 다름)
| 항목 | 결과 |
|---|---|
| 포트 / 바인드 | TCP 8081 / 192.168.10.20 |
| 프로세스 / PID | python3 / 2417 |
| 실행 파일 | /usr/bin/python3.9 (패키지 소속) |
| 실행 인자 | python3 -m http.server 8081 --bind 192.168.10.20 |
| 실행 계정 / 작업 디렉터리 | 일반 사용자 / ~/lab-web |
| 서비스 유닛 | 사용자 세션(scope) 소속, 상시 서비스 아님 |
| 판정 | 사용자가 수동으로 띄운 임시 HTTP 파일 서버 |
점검 체크리스트
| 관찰 내용 | 정상일 수 있는 경우 | 의심해야 하는 경우 |
|---|---|---|
| 8080·8443 등 웹 대체 포트 | WAS, 관리 콘솔, 프록시 | 등록되지 않은 서버에서 새로 열림 |
| SSH가 2222 등에서 동작 | 운영 정책에 따른 포트 변경 | 기존 22와 별도로 추가 SSH 데몬이 존재 |
| 높은 번호 포트 LISTEN | 애플리케이션 고유 포트, RPC 동적 포트 | /tmp·/dev/shm·홈 디렉터리의 실행 파일이 LISTEN |
| 실행 파일의 패키지 소속 | 공식 패키지 또는 배포 절차로 설치된 파일 | 패키지에 속하지 않고 이름이 시스템 프로세스를 흉내냄 |
| 포트와 프로토콜 불일치 | 설정 변경 이력이 있는 서비스 | 80 포트에서 HTTP가 아닌 프로토콜, IDS의 app_proto가 판정 실패 |
Suricata eve.json flow 이벤트 일부 형식 예시(값은 환경마다 다름)
{"event_type":"flow","src_ip":"192.168.10.50","dest_ip":"192.168.10.20","dest_port":2222,"proto":"TCP","app_proto":"ssh"}
dest_port는 2222인데 app_proto가 ssh라면, 포트는 비표준이지만 프로토콜은 SSH라는 뜻입니다. 반대로 dest_port가 443인데 app_proto가 tls가 아니라면 다음 글(21편)에서 다룰 위장 통신을 의심할 근거가 됩니다.
# LISTEN 중인 프로세스의 실행 파일 경로를 한 번에 확인
for pid in $(sudo ss -tulnpH | grep -oP 'pid=\K[0-9]+' | sort -u); do
printf "%s\t%s\n" "$pid" "$(sudo readlink -f /proc/$pid/exe)"
done
실행 파일 경로가 /tmp, /var/tmp, /dev/shm에 있거나 (deleted)로 표시되면(실행 후 파일이 삭제됨) 우선 확인 대상입니다.
app_proto/service 판정, 방화벽의 허용 포트 목록과 실제 트래픽 비교.220, HTTP/1.1, TLS 0x16)으로 프로토콜을 추정합니다.app_proto·service 필드는 포트와 무관하게 프로토콜을 판정하므로 포트-프로토콜 불일치 확인에 유용합니다.