Step 82. 프로젝트 — 네트워크 지도와 위험도 평가
Level 1 — 프로그래밍과 컴퓨터 내부 | 난이도 ★★★★☆ | 예상 소요 시간 4시간
전제: Step 81을 마쳤다. nmap으로
-sV스캔과-oN파일 저장을 할 수 있다. 파이썬 기본 문법(파일 읽기, 정규표현식, 리스트)을 안다.
- 준비물: 리눅스 터미널, nmap, 파이썬 3. 그리고 Step 81에서 저장한 스캔 리포트 파일.
- 주의: ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. 이 챕터의 모든 실측은
127.0.0.1스캔 파일만을 대상으로 수행했습니다.
공격자가 네트워크에 들어와 제일 먼저 하는 일이 무엇인지 아십니까? "뭐가 있는지 지도를 그리는 것"입니다. 방어자의 첫 일도 정확히 같습니다 — 모르는 것은 지킬 수 없기 때문입니다. 오늘은 Step 81의 스캔 결과를 재료로, "발견 → 정리 → 우선순위 → 행동"이라는 보안 진단의 전체 뼈대를 한 번 돌려 봅니다. 스캔까지가 nmap의 일이라면, 그 결과를 위험도 표로 바꾸는 것은 여러분의 파이썬 실력이 할 일입니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 자산(asset), 공격 표면(attack surface), 위험도 세 용어를 정의하고 실습에 적용한다
- nmap의
-oN출력 파일을 파이썬으로 파싱해 구조화된 목록으로 바꾼다 - 규칙 기반으로 각 서비스에 위험도를 매기고 근거를 단다
- "정체 불명" 항목을 조사 목록으로 분리하는 습관을 갖는다
- 한 장짜리 "자산 목록 + 위험도 평가표"라는 첫 보안 진단 문서를 완성한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 리눅스 터미널 + 파이썬 3 (스크립트 파일) |
| 오늘의 명령·기능 | nmap -oN(재료 수집), 파이썬 re.match()(줄 파싱), 딕셔너리 리스트, 규칙 기반 분류 |
| 필요한 개념 | 자산, 공격 표면, 위험도(영향 × 가능성), Step 81의 스캔 출력 형식 |
| 오늘의 산출물 | riskmap.py — 스캔 파일을 위험도 표로 바꾸는 변환기 + 완성된 평가표 한 장 |
2-1. 자산, 공격 표면, 위험도
오늘 쓸 세 용어를 정리합니다.
- 자산(asset): 지켜야 할 대상의 총목록. 네트워크에 연결된 모든 기기와, 그 위에서 돌아가는 모든 서비스가 자산입니다.
- 공격 표면(attack surface): 그 자산이 밖으로 열어 놓은 접점의 총합. 열린 포트 하나하나가 공격 표면의 한 칸입니다. 열린 것이 적을수록 표면이 좁고, 좁을수록 지키기 쉽습니다.
- 위험도(risk): "뚫리면 얼마나 아픈가(영향)"와 "뚫릴 가능성은 얼마나 되는가(가능성)"를 함께 본 감각입니다. 오늘은 상/중/하 세 등급으로 충분합니다.
2-2. 왜 파싱인가 — 파일이 곧 데이터다
Step 81의 스캔 출력은 사람이 읽기 좋은 표이지만, 사실 규칙적인 텍스트입니다. 규칙적인 텍스트는 프로그램이 읽을 수 있습니다. 스캔 파일을 파이썬으로 파싱하면 이런 것이 가능해집니다.
- 기기가 백 대여도 같은 처리를 자동으로 반복
- 지난달 파일과 비교해 "새로 열린 것" 자동 감지
- 위험도 규칙을 코드로 적어 판단의 재현성 확보
오늘의 재료는 127.0.0.1 스캔 파일 하나지만, 이 코드는 그대로 백 대짜리 진단으로 확장됩니다.
2-3. 위험도 평가의 사고법
위험도는 감이 아니라 비교입니다. 이런 질문들을 던져 보세요.
- 이 서비스는 암호화되어 오가는가, 평문인가? (telnet/ftp/http는 평문)
- 인증이 걸려 있는가? 기본 암호는 아닌가?
- 이 포트가 왜 열려 있는지 내가 아는가? 모르는 것이 제일 위험합니다.
- 이 서비스가 뚫리면 무엇이 새어 나가는가?
답이 나쁠수록 위험도는 올라갑니다. 정밀한 점수가 아니라, "어디부터 손볼까"의 순서를 정하는 것이 오늘의 목표입니다.
3. 따라 하기
3-1. 재료 준비 — 스캔 파일 다시 보기
Step 81에서 저장한 전수 스캔 파일을 열어 봅니다. 없다면 다시 만듭니다.
입력
nmap -sV --version-intensity 2 -p- -oN scan_full.txt 127.0.0.1
cat scan_full.txt
출력 (2026-09-09 실측, 핵심 부분):
# Nmap 7.94SVN scan initiated Wed Sep 9 13:37:10 2026 as: nmap -sV ... -p- -oN scan_full.txt 127.0.0.1
Nmap scan report for localhost (127.0.0.1)
Host is up (0.0000010s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE VERSION
8000/tcp open http SimpleHTTPServer 0.6 (Python 3.12.3)
33211/tcp open unknown
읽는 법: 이 파일에서 우리가 파이썬에게 읽게 할 것은 숫자/규약 상태 서비스 버전 형태의 줄뿐입니다. 나머지 줄(머리말, 안내 문구)은 걸러내야 합니다 — 그것이 파싱의 일입니다.
3-2. 파서 만들기 — 규칙적인 줄만 골라내기
입력 (riskmap.py 1단계)
import re
def parse_nmap(path):
"""-oN 스캔 파일에서 '포트/규약 상태 서비스 버전' 줄을 뽑는다."""
rows = []
with open(path, encoding="utf-8") as f:
for line in f:
m = re.match(r"^(\d+)/(tcp|udp)\s+(\S+)\s+(\S+)\s*(.*)$", line.strip())
if m:
rows.append({
"port": int(m.group(1)),
"proto": m.group(2),
"state": m.group(3),
"service": m.group(4),
"version": m.group(5).strip(),
})
return rows
rows = parse_nmap("scan_full.txt")
for r in rows:
print(r)
출력 (2026-09-09 실측):
{'port': 8000, 'proto': 'tcp', 'state': 'open', 'service': 'http', 'version': 'SimpleHTTPServer 0.6 (Python 3.12.3)'}
{'port': 33211, 'proto': 'tcp', 'state': 'open', 'service': 'unknown', 'version': ''}
읽는 법: 정규표현식 ^(\d+)/(tcp|udp)\s+(\S+)\s+(\S+)\s*(.*)$는 "숫자/규약으로 시작하고 단어가 셋 이상 이어지는 줄"에만 맞습니다. 머리말(#로 시작)이나 Not shown: 같은 줄은 패턴에 안 맞아 자동으로 걸러집니다. 각 줄이 딕셔너리 하나 — 이제 이 데이터를 마음대로 요리할 수 있습니다.
왜: "문서를 데이터로 바꾸는 것"이 모든 자동화의 첫걸음입니다. Step 48의 정규표현식이 여기서 실전 데뷔합니다.
3-3. 위험도 규칙 심기 — 판단을 코드로
입력 (riskmap.py 완성형)
import re
def parse_nmap(path):
rows = []
with open(path, encoding="utf-8") as f:
for line in f:
m = re.match(r"^(\d+)/(tcp|udp)\s+(\S+)\s+(\S+)\s*(.*)$", line.strip())
if m:
rows.append({
"port": int(m.group(1)), "proto": m.group(2),
"state": m.group(3), "service": m.group(4),
"version": m.group(5).strip(),
})
return rows
RISK_RULES = [
(["telnet", "ftp"], "상", "암호화 없는 구형 프로토콜"),
(["http", "http-alt"], "중", "평문 웹 서비스 — 내용이 노출될 수 있음"),
(["ssh"], "하", "암호화됨 — 버전만 최신으로 유지"),
]
def assess(service):
for names, grade, reason in RISK_RULES:
if service in names:
return grade, reason
return "?", "정체 불명 — 확인 필요"
rows = parse_nmap("scan_full.txt")
print(f"{'포트':<10}{'상태':<8}{'서비스':<12}{'위험도':<6}판단 근거")
for r in rows:
grade, reason = assess(r["service"])
print(f"{r['port']}/{r['proto']:<6}{r['state']:<8}{r['service']:<12}{grade:<6}{reason}")
unknown = [r for r in rows if assess(r["service"])[0] == "?"]
print(f"\n정체 불명 서비스: {len(unknown)}개 — 조사 목록에 추가")
출력 (2026-09-09 실측):
포트 상태 서비스 위험도 판단 근거
8000/tcp open http 중 평문 웹 서비스 — 내용이 노출될 수 있음
33211/tcp open unknown ? 정체 불명 — 확인 필요
정체 불명 서비스: 1개 — 조사 목록에 추가
읽는 법: RISK_RULES가 "판단의 명문화"입니다. 규칙에 맞는 서비스는 등급과 근거를 받고, 어디에도 안 맞는 unknown은 ?와 함께 조사 목록으로 분리됩니다. 표 아래의 "정체 불명 1개" 문장이 이 도구의 진짜 주인공입니다 — 지도에서 제일 위험한 칸은 "모르는 칸"이니까요.
왜: 위험도를 코드로 매기면, 다음 달에 같은 파일을 넣어도 같은 판단이 나옵니다. 재현 가능한 판단이 보고서의 신뢰입니다.
3-4. 예측해 보기 — unknown을 어떻게 처리해야 할까
예측입니다. unknown 서비스에 위험도를 매긴다면 ① 하(알 수 없으니 낮게) ② 중 ③ 상(모르니 높게) ④ 매기지 않고 조사 목록으로 분리 — 어느 쪽이 맞을까요?
직접 확인: 우리 코드는 ④를 선택했습니다. 그리고 Step 81 3-7절에서 이 포트의 정체를 추적해 봤습니다 — -sV 지문에서 HTTP/1.0 404 Not Found 응답을 확인하고, ss -tlnp로 컨테이너 관리 프로그램이 연 포트임을 최종 확인했죠 (2026-09-09 실측).
읽는 법: "모른다"를 등급으로 얼버무리지 않고 조사 행동으로 연결하는 것이 ④의 힘입니다. 위험도 표의 ?는 빈칸이 아니라 할 일입니다.
왜: 실제 침해 사고는 대개 "누가 열었는지 아무도 모르는 서비스"에서 시작됩니다. 모르는 것을 모른다고 기록하고 끝까지 쫓는 습관이 지도의 정확도를 만듭니다.
3-5. 지도 완성 — 표를 문서로
마지막으로 파이썬의 출력을 사람이 읽는 평가표로 옮깁니다. 스프레드시트나 마크다운에 이런 형식으로요.
| 자산 | 포트 | 서비스/버전 | 위험도 | 조치 |
|---|---|---|---|---|
| 내 PC (127.0.0.1) | 8000 | SimpleHTTPServer 0.6 (Python 3.12.3) | 중 | 실습용 — 끝나면 종료 |
| 내 PC (127.0.0.1) | 33211 | unknown → 조사 결과 컨테이너 관리 서비스 | 하(확인 후) | 정체 확인 완료, 로컬 전용 유지 |
읽는 법: 조치 칸이 비어 있으면 그 지도는 벽걸이입니다. 위험도가 아무리 정교해도 "그래서 무엇을 할 것인가"가 있어야 진단입니다. unknown이던 포트가 조사를 거쳐 "하(확인 후)"로 정정된 과정 자체가 좋은 보고서 행입니다.
왜: 발견(스캔) → 정리(표) → 우선순위(위험도) → 행동(조치)의 흐름은 기업의 보안 점검과 정확히 같은 뼈대입니다. 오늘의 한 장이 그 축소판입니다.
4. 미션과 연습문제
미션 — 우리 집 네트워크 지도 (동의를 구한 뒤)
준비가 된 분만, 가족의 양해를 구하고 진행하세요. 랩만 있다면 랩 네트워크로 해도 됩니다.
ip a(리눅스) 또는ipconfig(윈도우)로 내 주소와 대역을 파악합니다 (예:192.168.0.10/24).- 핑 스캔으로 자산 목록 초안을 만듭니다:
sudo nmap -sn 192.168.0.0/24 -oN step82_ping.txt - 발견된 기기 각각을
sudo nmap -sV -oN step82_기기이름.txt 주소로 조사합니다. - riskmap.py를 확장해 여러 파일을 한 번에 읽게 하고(파일 경로를 리스트로 받기), 기기별·포트별 위험도 표를 출력하게 합니다.
- 완성된 표에 "조치" 칸을 채우고, 정체 불명(
?)이 하나도 남지 않을 때까지 조사합니다. 모르는 채로 두지 마세요.
연습문제
문제 1. 자산, 공격 표면, 위험도의 관계를 한 문장씩으로 설명해 보세요.
문제 2. riskmap.py의 정규표현식이 Not shown: 65533 closed tcp ports (reset) 같은 줄을 건너뛰는 이유를 패턴의 관점에서 설명해 보세요.
문제 3. 위험도 규칙에서 unknown에 ?를 주고 조사 목록으로 분리하는 설계가 하를 주는 설계보다 나은 이유는 무엇인가요?
문제 4. 같은 네트워크를 한 달 뒤 다시 스캔했습니다. riskmap.py에 어떤 기능을 추가하면 "지난달과 비교해 새로 열린 포트"를 자동으로 찾을 수 있을까요? 코드 없이 아이디어로 설명해 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
여러 파일을 읽는 확장 뼈대:
import re, sys
def parse_nmap(path):
rows = []
with open(path, encoding="utf-8") as f:
for line in f:
m = re.match(r"^(\d+)/(tcp|udp)\s+(\S+)\s+(\S+)\s*(.*)$", line.strip())
if m:
rows.append({"port": int(m.group(1)), "proto": m.group(2),
"state": m.group(3), "service": m.group(4),
"version": m.group(5).strip()})
return rows
for path in sys.argv[1:]: # python3 riskmap.py 파일1 파일2 ...
print(f"=== {path} ===")
for r in parse_nmap(path):
... # 3-3의 평가 코드 그대로
실측 기준 검증 예: 127.0.0.1 전수 스캔 파일을 넣으면 8000번(http, 중)과 33211번(unknown, ?) 두 행이 나오고, 마지막 줄에 "정체 불명 서비스: 1개"가 떠야 합니다 (2026-09-09 실측). 집 네트워크 미션에서는 기기 파일마다 같은 출력이 반복되면 성공입니다.
검증하는 법: ① python3 riskmap.py scan_full.txt 한 방에 표가 출력되는가. ② unknown 항목이 ?로 표시되고 조사 목록 카운트에 잡히는가. ③ 최종 표의 모든 행에 조치가 채워져 있는가. ④ 가족 동의 여부가 노트 첫 줄에 기록돼 있는가.
연습문제 해답
문제 1 해답. 자산은 지켜야 할 대상의 총목록이고, 공격 표면은 그 자산이 밖으로 열어 놓은 접점(열린 포트)의 총합이며, 위험도는 자산별로 "뚫렸을 때의 영향"과 "뚫릴 가능성"을 함께 본 우선순위 감각입니다. 지도는 자산 목록에 공격 표면과 위험도를 붙인 문서입니다.
문제 2 해답. 패턴이 ^(\d+)/(tcp|udp)로 시작하기 때문입니다. Not shown:은 숫자로 시작하지 않고, 8000/tcp 형태의 "숫자/규약" 머리가 없으므로 첫 글자에서 매치가 실패합니다. 헤더의 # 줄들도 마찬가지입니다.
문제 3 해답. 하를 주면 "확인 끝난 안전한 항목"처럼 보여 조사가 멈춥니다. 모르는 것은 위험도 평가가 불가능한 상태이지 위험도가 낮은 상태가 아닙니다. ?는 평가 보류 + 행동(조사) 예약이라서, 지도에 빈틈이 명시적으로 남고 반드시 후속 행동으로 이어집니다. 실제 사고가 "누가 열었는지 모르는 서비스"에서 시작한다는 점에서, 모르는 칸의 명시가 곧 방어입니다.
문제 4 해답. 두 파일을 각각 파싱해 (포트, 서비스) 쌍의 집합(set)을 만든 뒤, 이번 달 집합에서 지난달 집합을 뺀 차집합을 출력하면 됩니다. 파이썬의 set과 - 연산으로 구현할 수 있고, "사라진 포트"(지난달 – 이번 달)도 같은 방법입니다. 이것이 변화 감시의 알고리즘입니다.
완료 기준 체크리스트
- [ ] 자산/공격 표면/위험도를 자기 말로 정의할 수 있다
- [ ] nmap
-oN파일을 파이썬 정규표현식으로 파싱할 수 있다 - [ ] 위험도 규칙을 코드로 표현하고 근거를 붙일 수 있다
- [ ] unknown 항목을
?로 분리하고 조사로 연결했다 - [ ] 조치 칸이 채워진 위험도 평가표 한 장을 완성했다
- [ ] (미션 수행 시) 가족 동의를 구하고 대역 전체의 지도를 완성했다
6. 흔한 실수와 해결
벽 1. 파싱 결과가 텅 비어 있어요
증상: rows가 빈 리스트로 나옵니다.
원인: 파일 경로가 틀렸거나, -oN이 아닌 화면 출력을 그대로 복사해 저장한 경우 줄 모양이 미묘하게 다를 수 있습니다.
해결: cat scan_full.txt로 파일 내용을 눈으로 확인하고, 정규표현식을 한 단계씩 줄이며(^(\d+)부터) 어디서 안 맞는지 찾으세요. 디버깅도 정찰입니다.
벽 2. 한글이 들어간 출력이 깨져요
증상: 표의 한글 열 정렬이 어긋나거나 글자가 깨집니다.
원인: 한글은 화면에서 두 칸을 차지하는데 f-문자열의 폭 맞추기는 글자 수만 셉니다. 저장 인코딩이 cp949인 파일을 UTF-8로 열어도 깨집니다 (Step 50 복습).
해결: 정렬은 예쁘게 안 맞아도 데이터가 맞으면 됩니다. 파일 열기는 encoding="utf-8"을 명시하세요.
벽 3. 위험도를 매기는 기준이 애매해요
증상: 상/중/하를 어디에 둬야 할지 매번 망설여집니다.
원인: 정상입니다. 위험도는 경험으로 다듬어지는 감각입니다.
해결: 세 가지만 물으세요. ① 평문으로 오가는가 ② 인증이 없거나 기본 암호인가 ③ 내가 이 포트를 모르는가. 셋 중 둘 이상이 나쁘면 "상"입니다. 그리고 규칙을 코드(RISK_RULES)에 적어 두면 다음 달에도 같은 기준이 유지됩니다.
벽 4. 집에서 스캔했더니 기기가 예상보다 적게 잡혀요
증상: 분명 TV도 켜져 있는데 목록에 없습니다 (출력 예시 상황).
원인: 절전 중이거나, 그 기기가 핑에 답하지 않거나, 게스트 네트워크처럼 다른 대역에 있습니다.
해결: 기기를 깨우고 재스캔하세요. 공유기 관리 페이지의 접속자 목록과 교차 확인하면 빠진 기기를 찾을 수 있습니다. "안 잡힘"은 "없음"이 아니라 "대답 안 함"입니다.
벽 5. 스캔하다 가족 기기에 경고가 떴어요
증상: 스캔한 기기에서 보안 경고가 울렸습니다.
원인: 요즘 기기들은 포트 스캔을 감지하고 알립니다. 여러분의 스캔이 "보인" 것입니다.
해결: 사전에 말씀드렸다면 걱정 없습니다 — 그래서 미션의 0번 조건이 동의였습니다. 이 감지가 바로 방어자의 눈입니다. 여러분의 스캔이 어떻게 보이는지를 배운 좋은 기회이며, Step 84에서 이 발자국을 패킷 수준에서 다시 봅니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 자산(asset) | 지켜야 할 대상의 총목록 — 기기와 그 위의 서비스 |
| 공격 표면 | 자산이 밖으로 연 접점의 총합 — 열린 포트 한 칸씩 |
| 위험도 | 영향 × 가능성의 비교 감각 — 상/중/하로 충분 |
?의 의미 |
평가 보류 + 조사 예약. 지도에서 제일 위험한 칸 |
| 재현 가능한 판단 | 규칙을 코드로 적어, 다음 달에도 같은 결론이 나오게 하는 것 |
오늘의 명령어와 코드
| 명령·코드 | 하는 일 |
|---|---|
nmap -sV -p- -oN 파일 대상 |
재료(스캔 리포트) 수집 |
| `re.match(r"^(\d+)/(tcp | udp)\s+(\S+)\s+(\S+)\s*(.*)$", 줄)` |
| 딕셔너리 리스트 + 규칙 리스트 | 발견을 위험도 표로 변환 |
sys.argv[1:] |
여러 스캔 파일을 한 번에 처리 |
| 집합 차집합(이번 달 − 지난 달) | "새로 열린 포트" 변화 감시 (미션 확장) |
명령어보다 중요한 감각
진단은 완벽한 문서가 아니라 "다음 행동을 정하는 회의"로 끝납니다. 오늘의 표에서 가장 중요한 칸은 조치 칸이고, 가장 위험한 칸은 ? 칸입니다. 그리고 지도는 한 번 그리고 끝이 아니라 정기적으로 다시 그려 변화를 감시하는 문서입니다 — 한 달 뒤 다시 그린 지도에서 ?가 하나 줄어 있다면, 그것이 여러분의 첫 보안 성과 보고서입니다.
마지막으로, 이 지도는 양면입니다. 같은 지도가 침입자에게는 약점 목록이 되므로, 지도 파일 자체도 보호 대상입니다. 그리고 그릴 권한이 없는 네트워크의 지도는 처음부터 그리지 않는 것 — 그것이 이 기술의 사용 면허입니다.
전부 체크되면 Step 82 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.