security
CHAPTER 68 / 84
읽기 약 2분
FUNCTION
포트 스캐닝: 열린 문 찾기
핵심 개념
TCP Connect 스캔 + 멀티스레딩으로 빠른 포트 스캐너를 만든다.
본문
TCP Connect 스캔 — 가장 기본
# ⚠️ 이 코드는 허가된 환경에서만 사용하세요.
import socket
def scan_port(host: str, port: int, timeout: float = 1.0) -> tuple[int, bool, str]:
"""단일 포트 상태 확인."""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
try:
# connect_ex는 예외 대신 errno 반환 — 더 빠름
result = s.connect_ex((host, port))
if result == 0:
return port, True, 'open'
return port, False, f'closed({result})'
except socket.timeout:
return port, False, 'timeout'
finally:
s.close()단일 스레드 — 느림
import time
def scan_serial(host: str, ports: list[int]) -> list[int]:
open_ports = []
start = time.time()
for p in ports:
port, is_open, _ = scan_port(host, p, 0.5)
if is_open:
open_ports.append(port)
print(f'{len(ports)}개 포트, {time.time() - start:.1f}초')
return open_ports
# 1024개 포트 → 약 8분 (timeout 0.5초 × 1024)멀티스레딩 — 100배 빠름
# ⚠️ 이 코드는 허가된 환경에서만 사용하세요.
from concurrent.futures import ThreadPoolExecutor, as_completed
def scan_parallel(host: str, ports: list[int], workers: int = 100) -> list[int]:
"""병렬 포트 스캔."""
open_ports = []
with ThreadPoolExecutor(max_workers=workers) as ex:
futures = {ex.submit(scan_port, host, p, 1.0): p for p in ports}
for fut in as_completed(futures):
port, is_open, _ = fut.result()
if is_open:
open_ports.append(port)
return sorted(open_ports)
# 1024개 포트 → 약 10초 (100 worker)일반적인 포트 셋
COMMON_PORTS = {
'top10': [21, 22, 23, 25, 53, 80, 110, 143, 443, 3306],
'top100_web': list(range(80, 90)) + [443, 8080, 8443, 8888],
'all_well_known': list(range(1, 1024)),
}
WELL_KNOWN_SERVICES = {
21: 'FTP',
22: 'SSH',
23: 'Telnet',
25: 'SMTP',
53: 'DNS',
80: 'HTTP',
110: 'POP3',
143: 'IMAP',
443: 'HTTPS',
3306: 'MySQL',
5432: 'PostgreSQL',
6379: 'Redis',
8080: 'HTTP-Alt',
27017: 'MongoDB',
}
def label_port(port: int) -> str:
return WELL_KNOWN_SERVICES.get(port, 'Unknown')실습: 자신의 로컬 서버 포트 스캔
# ⚠️ 이 코드는 허가된 환경에서만 사용하세요.
import argparse
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
log = logging.getLogger('portscan')
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--host', default='127.0.0.1')
parser.add_argument('--start', type=int, default=1)
parser.add_argument('--end', type=int, default=1024)
parser.add_argument('--workers', type=int, default=100)
args = parser.parse_args()
# 안전 검증 — 자기 호스트만 허용
import ipaddress
ip = ipaddress.ip_address(socket.gethostbyname(args.host))
if not (ip.is_private or ip.is_loopback):
raise PermissionError(f'공용 IP 스캔 금지: {ip}')
ports = list(range(args.start, args.end + 1))
log.info(f'{args.host} 포트 {args.start}~{args.end} 스캔 시작')
open_ports = []
with ThreadPoolExecutor(max_workers=args.workers) as ex:
futures = {ex.submit(scan_port, args.host, p, 1.0): p for p in ports}
for fut in as_completed(futures):
port, is_open, _ = fut.result()
if is_open:
open_ports.append(port)
log.warning(f'OPEN: {port}/tcp ({label_port(port)})')
print(f'\n결과: {len(open_ports)}개 열린 포트')
for p in sorted(open_ports):
print(f' {p:5d}/tcp {label_port(p)}')
if __name__ == '__main__':
main()⚠️ 윤리적 경계
- ✅ 자기 서버, 로컬 환경(127.0.0.1, 192.168.x.x)
- ✅ TryHackMe, HackTheBox 같은 허가 랩
- ❌ 임의의 외부 IP 스캔 → 명백한 불법
- ❌ Rate Limit 무시 (서버 마비 우려)
SYN 스캔(Half-Open)은 다음 단계
이 챕터의 TCP Connect 스캔은 완전한 3-way handshake를 수행해 로그에 남습니다. SYN 스캔(scapy 사용)은 더 은밀하지만 root 권한이 필요해서 후속 트랙에서 다룹니다.
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 포트 스캐너 코드에서 동시성·예외 처리·리소스 누수를 분석하고 프로덕션 수준으로 개선해줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
TCP Connect 스캔 vs SYN 스캔 vs UDP 스캔의 속도·은밀성·권한 요구사항을 실전 코드로 비교해줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 스캔 결과 로그를 분석해서 포트별 서비스·응답 시간·이상 패턴을 종합 리포트로 만들어줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 포트 스캐닝 도구 — Python 자체 vs Nmap vs masscan 각각의 적합한 케이스를 솔직히 알려줘.
⭐ 이것만 기억하세요
포트 스캐닝: 열린 문 찾기는 이 3가지만 확실히 잡으세요
1.TCP Connect 스캔 + ThreadPoolExecutor로 1024개 포트를 10초 안에 스캔할 수 있다
2.connect_ex()는 예외 대신 errno를 반환해서 try/except 오버헤드를 줄여준다
3.다음 챕터에서 열린 포트의 서비스를 식별하는 배너 그래빙 기법을 배운다
공유하기
진행도 68 / 84