본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 27. · 0 Views
Phase 1 보안 사고방식 구축 완벽 가이드
초급 개발자가 보안 전문가로 성장하기 위한 첫걸음입니다. 해커의 관점에서 시스템을 바라보는 방법부터 OWASP Top 10, 포트 스캐너 구현, 실제 침해사고 분석까지 보안의 기초 체력을 다집니다.
목차
1. 해커의 관점으로 시스템 바라보기
김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 어느 날 회사에서 보안 교육을 받게 되었는데, 강사가 던진 첫 번째 질문에 머리가 멍해졌습니다.
"여러분이 만든 시스템을 해커 입장에서 본 적 있나요?"
보안 사고방식이란 개발자가 아닌 공격자의 눈으로 시스템을 바라보는 관점입니다. 마치 도둑이 집을 털기 전에 어떤 창문이 열려 있는지, 어떤 문이 약한지 살펴보는 것과 같습니다.
이 관점을 갖추면 취약점을 미리 발견하고 방어할 수 있습니다.
다음 코드를 살펴봅시다.
# 해커의 관점: 입력값을 통한 공격 시뮬레이션
def check_user_input(user_input):
# 공격자는 항상 예상치 못한 입력을 시도합니다
dangerous_patterns = [
"' OR '1'='1", # SQL Injection
"<script>", # XSS 공격
"../../../etc/passwd", # Path Traversal
"; rm -rf /", # Command Injection
]
for pattern in dangerous_patterns:
if pattern.lower() in user_input.lower():
return {"safe": False, "attack_type": pattern}
return {"safe": True, "attack_type": None}
# 테스트: 공격자처럼 생각하기
test_inputs = ["정상입력", "' OR '1'='1", "<script>alert('xss')</script>"]
for inp in test_inputs:
result = check_user_input(inp)
print(f"입력: {inp[:20]}... -> 안전: {result['safe']}")
김개발 씨는 보안 교육에서 충격적인 사실을 알게 되었습니다. 자신이 만든 로그인 시스템이 SQL Injection 공격에 무방비 상태였던 것입니다.
분명히 기능은 완벽하게 동작했는데, 왜 이런 일이 생긴 걸까요? 문제는 바로 관점에 있었습니다.
개발자는 사용자가 정상적인 입력만 할 것이라고 가정합니다. 하지만 해커는 정반대로 생각합니다.
"이 입력창에 이상한 값을 넣으면 어떻게 될까?" 이것이 바로 보안 사고방식의 핵심입니다. 쉽게 비유하자면, 집을 지을 때 건축가는 문과 창문이 잘 열리는지 확인합니다.
하지만 보안 전문가는 도둑이 어떻게 침입할 수 있을지를 먼저 생각합니다. 잠금장치가 약한 창문, 뒷문의 경첩, 지하실 환기구까지 모든 가능한 침입 경로를 점검합니다.
이런 관점이 없던 시절에는 어땠을까요? 개발자들은 기능 구현에만 집중했습니다.
로그인이 되면 성공, 안 되면 실패. 이렇게 단순하게 생각했습니다.
그 결과, 수많은 웹사이트가 해킹당했고, 개인정보가 유출되었습니다. 해커의 관점을 이해하면 완전히 다른 세상이 보입니다.
예를 들어, 로그인 폼을 볼 때 개발자는 "아이디와 비밀번호가 맞으면 로그인시켜주자"고 생각합니다. 하지만 해커는 이렇게 생각합니다.
"SQL 쿼리에 직접 입력값이 들어가나? 비밀번호 오류 횟수 제한이 있나?
세션 토큰은 예측 가능한가?" 위 코드를 살펴보겠습니다. dangerous_patterns 리스트에는 가장 흔한 공격 패턴들이 담겨 있습니다.
SQL Injection, XSS, Path Traversal, Command Injection 등 이 네 가지는 모든 개발자가 반드시 알아야 할 공격 유형입니다. check_user_input 함수는 사용자 입력을 받아 위험한 패턴이 포함되어 있는지 검사합니다.
물론 실제 보안 검사는 이보다 훨씬 정교해야 합니다. 하지만 핵심 개념은 동일합니다.
모든 입력은 잠재적인 공격이라고 가정하는 것입니다. 실제 현업에서는 이런 관점을 방어적 프로그래밍이라고 부릅니다.
네이버, 카카오, 토스 같은 대형 서비스들은 모두 이런 사고방식을 기반으로 시스템을 설계합니다. 사용자 입력을 절대 신뢰하지 않고, 항상 검증하고 필터링합니다.
주의할 점이 있습니다. 보안 사고방식을 갖추었다고 해서 실제로 해킹을 시도하면 안 됩니다.
허가 없이 다른 시스템을 공격하는 것은 명백한 범죄입니다. 보안 사고방식은 자신이 만든 시스템의 취약점을 찾는 데만 사용해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 보안 교육을 마친 김개발 씨는 자신의 코드를 다시 살펴보았습니다.
이제는 모든 입력창이 잠재적인 공격 통로로 보였습니다. "여기도 위험하네, 저기도 확인해봐야겠어." 이것이 바로 보안 전문가로 가는 첫걸음입니다.
실전 팁
💡 - 코드 리뷰할 때 "이 입력값에 악의적인 데이터가 들어오면?"이라고 항상 질문하세요
- OWASP Testing Guide를 참고하여 자신의 시스템을 점검하는 습관을 들이세요
- 모든 외부 입력은 검증될 때까지 신뢰하지 마세요
2. OWASP Top 10 분석
박시니어 씨가 김개발 씨에게 질문했습니다. "혹시 OWASP Top 10 알아요?" 김개발 씨는 어디선가 들어본 것 같은데 정확히 뭔지 몰랐습니다.
"웹 보안의 교과서 같은 거예요. 이거 모르면 보안 얘기하기 힘들어요."
OWASP Top 10은 웹 애플리케이션에서 가장 심각한 보안 위험 10가지를 정리한 목록입니다. 마치 의사가 가장 흔한 질병 목록을 정리해둔 것처럼, 보안 전문가들이 가장 자주 발생하는 취약점을 분류해놓은 것입니다.
이 목록을 이해하면 대부분의 웹 보안 문제를 예방할 수 있습니다.
다음 코드를 살펴봅시다.
# OWASP Top 10 (2021) 취약점 시뮬레이션
class OWASPDemo:
def __init__(self):
self.users_db = {"admin": "1234", "user": "password"}
# A01: Broken Access Control - 잘못된 접근 제어
def get_user_data_vulnerable(self, user_id, requester_id):
# 위험: 권한 확인 없이 데이터 반환
return f"User {user_id}의 개인정보"
def get_user_data_secure(self, user_id, requester_id):
# 안전: 본인 또는 관리자만 접근 가능
if user_id == requester_id or requester_id == "admin":
return f"User {user_id}의 개인정보"
return "접근 권한이 없습니다"
# A03: Injection - 인젝션 공격
def login_vulnerable(self, username, password):
# 위험: SQL Injection에 취약
query = f"SELECT * FROM users WHERE name='{username}' AND pw='{password}'"
return query # 실제로는 이 쿼리가 실행됨
def login_secure(self, username, password):
# 안전: 파라미터 바인딩 사용
query = "SELECT * FROM users WHERE name=? AND pw=?"
params = (username, password)
return query, params
demo = OWASPDemo()
print("취약한 쿼리:", demo.login_vulnerable("admin", "' OR '1'='1"))
print("안전한 쿼리:", demo.login_secure("admin", "' OR '1'='1"))
김개발 씨는 그날 저녁 집에 돌아와 OWASP Top 10을 검색해보았습니다. 생각보다 방대한 내용에 놀랐지만, 하나씩 읽어가다 보니 "아, 이래서 선배가 그렇게 강조했구나"라는 생각이 들었습니다.
OWASP는 Open Web Application Security Project의 약자로, 웹 보안을 위해 활동하는 비영리 단체입니다. 이들이 발표하는 Top 10 목록은 전 세계 보안 전문가들이 가장 중요하게 여기는 취약점 순위입니다.
2021년 기준 최신 목록을 살펴보겠습니다. 1위는 **Broken Access Control(취약한 접근 제어)**입니다.
쉽게 말해, 권한이 없는 사용자가 남의 정보에 접근할 수 있는 문제입니다. 마치 호텔에서 다른 사람의 방 카드키가 작동하는 것과 같습니다.
위 코드의 get_user_data_vulnerable 함수를 보면, 요청자가 누구인지 확인하지 않고 바로 데이터를 반환합니다. 이렇게 하면 누구나 남의 정보를 볼 수 있습니다.
2위는 **Cryptographic Failures(암호화 실패)**입니다. 비밀번호를 평문으로 저장하거나, 약한 암호화 알고리즘을 사용하는 경우입니다.
많은 초보 개발자들이 MD5나 SHA1을 사용하는데, 이들은 이미 안전하지 않은 것으로 알려져 있습니다. 3위는 **Injection(인젝션)**입니다.
SQL Injection이 가장 대표적인데, 사용자 입력이 데이터베이스 쿼리에 직접 포함되면서 발생합니다. 위 코드의 login_vulnerable 함수를 보세요.
사용자가 비밀번호 대신 ' OR '1'='1을 입력하면, 쿼리가 **SELECT * FROM users WHERE name='admin' AND pw='' OR '1'='1'**이 됩니다. 조건이 항상 참이 되어 비밀번호 없이 로그인됩니다.
4위부터 10위까지도 모두 중요합니다. Insecure Design(안전하지 않은 설계), Security Misconfiguration(보안 설정 오류), Vulnerable Components(취약한 구성요소), Authentication Failures(인증 실패), Integrity Failures(무결성 실패), Logging Failures(로깅 실패), SSRF(서버 측 요청 위조) 순입니다.
실제 현업에서는 OWASP Top 10을 체크리스트처럼 사용합니다. 새로운 기능을 개발할 때, 코드 리뷰할 때, 보안 점검할 때 이 목록을 하나씩 확인합니다.
대기업 보안 팀에서는 이 목록을 기반으로 보안 가이드라인을 만들기도 합니다. 주의할 점은 Top 10이 모든 보안 문제를 다루지는 않는다는 것입니다.
이것은 가장 흔하고 심각한 문제들의 요약본일 뿐입니다. 하지만 초보 개발자가 보안을 시작하기에는 최고의 출발점입니다.
김개발 씨는 OWASP 공식 사이트에서 각 취약점에 대한 상세 설명과 예방 방법을 읽었습니다. "이거 하나만 제대로 공부해도 웬만한 보안 실수는 안 하겠는데?" 맞습니다.
OWASP Top 10만 제대로 이해해도 웹 보안의 80%는 해결됩니다.
실전 팁
💡 - OWASP 공식 사이트(owasp.org)에서 각 취약점의 상세 설명과 예방법을 꼭 읽어보세요
- 새로운 프로젝트를 시작할 때 Top 10 체크리스트를 먼저 검토하세요
- 매년 업데이트되는 내용을 확인하여 최신 트렌드를 파악하세요
3. Attack Surface 분석 실습
보안 교육 2일차, 강사가 화이트보드에 시스템 구성도를 그렸습니다. "자, 이 시스템에서 해커가 공격할 수 있는 지점이 몇 개나 될까요?" 김개발 씨는 로그인 페이지 정도만 떠올랐는데, 강사는 빨간 펜으로 무려 15개의 지점에 표시를 했습니다.
**Attack Surface(공격 표면)**는 시스템에서 공격자가 침투하거나 데이터를 추출할 수 있는 모든 지점의 총합입니다. 마치 성벽의 모든 문, 창문, 배수구, 환기구를 합친 것과 같습니다.
Attack Surface가 넓을수록 방어해야 할 곳이 많아지고, 그만큼 보안 위험이 증가합니다.
다음 코드를 살펴봅시다.
import json
from dataclasses import dataclass
from typing import List
@dataclass
class AttackVector:
name: str
category: str
risk_level: str # low, medium, high, critical
description: str
class AttackSurfaceAnalyzer:
def __init__(self, system_name: str):
self.system_name = system_name
self.attack_vectors: List[AttackVector] = []
def add_vector(self, name, category, risk_level, description):
vector = AttackVector(name, category, risk_level, description)
self.attack_vectors.append(vector)
return self
def analyze(self):
# 위험도별 분류
risk_count = {"critical": 0, "high": 0, "medium": 0, "low": 0}
for v in self.attack_vectors:
risk_count[v.risk_level] += 1
print(f"\n=== {self.system_name} Attack Surface 분석 ===")
print(f"총 공격 벡터: {len(self.attack_vectors)}개")
print(f"Critical: {risk_count['critical']}, High: {risk_count['high']}")
print(f"Medium: {risk_count['medium']}, Low: {risk_count['low']}")
# Critical, High 취약점 상세 출력
print("\n[즉시 조치 필요]")
for v in self.attack_vectors:
if v.risk_level in ["critical", "high"]:
print(f" - [{v.risk_level.upper()}] {v.name}: {v.description}")
# 웹 서비스 Attack Surface 분석 예시
analyzer = AttackSurfaceAnalyzer("쇼핑몰 웹서비스")
analyzer.add_vector("로그인 API", "Authentication", "high", "브루트포스 공격 가능")
analyzer.add_vector("파일 업로드", "Input", "critical", "악성 파일 업로드 가능")
analyzer.add_vector("검색 기능", "Input", "medium", "SQL Injection 가능성")
analyzer.add_vector("결제 API", "Business Logic", "critical", "가격 조작 가능성")
analyzer.add_vector("관리자 페이지", "Access Control", "high", "접근 제어 미흡")
analyzer.analyze()
김개발 씨는 강사가 표시한 15개의 공격 지점을 보며 깜짝 놀랐습니다. 자신이 만든 쇼핑몰 서비스도 다시 보니 생각보다 많은 곳에서 공격이 가능해 보였습니다.
파일 업로드, 검색창, 결제 페이지, 관리자 로그인... 어디서부터 손을 대야 할지 막막했습니다.
Attack Surface를 이해하려면 먼저 성을 상상해보세요. 적군이 성을 공격할 때 정문만 노리지 않습니다.
후문, 성벽의 균열, 지하 수로, 심지어 성 안으로 들어가는 상인의 마차까지 모든 경로를 탐색합니다. 웹 서비스도 마찬가지입니다.
로그인만 보호한다고 안전한 것이 아닙니다. Attack Surface는 크게 세 가지로 분류할 수 있습니다.
첫째는 네트워크 Attack Surface입니다. 열려 있는 포트, API 엔드포인트, 웹소켓 연결 등이 여기에 해당합니다.
둘째는 소프트웨어 Attack Surface입니다. 사용하는 라이브러리, 프레임워크, 운영체제의 취약점입니다.
셋째는 휴먼 Attack Surface입니다. 직원들의 실수, 피싱 공격에 대한 취약성 등입니다.
위 코드는 시스템의 Attack Surface를 체계적으로 분석하는 도구입니다. AttackSurfaceAnalyzer 클래스는 공격 벡터들을 수집하고, 위험도별로 분류하여 우선순위를 정해줍니다.
실제 보안 팀에서도 이런 방식으로 취약점을 관리합니다. 코드를 실행하면 각 공격 벡터가 위험도와 함께 정리됩니다.
Critical과 High 등급은 즉시 조치가 필요한 항목입니다. 파일 업로드가 Critical로 분류된 이유는 악성 파일(웹쉘 등)이 업로드되면 서버 전체가 장악될 수 있기 때문입니다.
실제 현업에서 Attack Surface 분석은 보안 감사의 첫 단계입니다. 새로운 기능을 추가할 때도 "이 기능이 Attack Surface를 얼마나 늘리는가?"를 고려해야 합니다.
불필요한 기능은 과감히 제거하고, 필요한 기능은 최소한의 권한으로 구현합니다. Attack Surface를 줄이는 방법은 의외로 간단합니다.
사용하지 않는 포트는 닫고, 불필요한 API는 제거하고, 관리자 페이지는 IP 제한을 걸고, 파일 업로드는 화이트리스트 방식으로 검증합니다. 이렇게 하나씩 줄여가면 방어해야 할 곳이 줄어듭니다.
주의할 점은 Attack Surface를 0으로 만들 수는 없다는 것입니다. 서비스가 존재하는 한 어느 정도의 노출은 불가피합니다.
목표는 완전한 제거가 아니라 최소화와 관리입니다. 어떤 공격 지점이 있는지 파악하고, 각각에 적절한 방어책을 마련하는 것이 핵심입니다.
김개발 씨는 자신의 프로젝트에 대해 Attack Surface 분석을 해보기로 했습니다. 화이트보드에 시스템 구성도를 그리고, 빨간 펜으로 공격 가능 지점을 하나씩 표시했습니다.
생각보다 많은 지점이 표시되었지만, 이제는 어디를 먼저 보강해야 할지 명확해졌습니다.
실전 팁
💡 - 새 기능을 추가할 때마다 "Attack Surface가 늘어나는가?"를 자문하세요
- 정기적으로 사용하지 않는 API, 포트, 기능을 정리하세요
- 외부에 노출되는 지점과 내부 지점을 구분하여 우선순위를 정하세요
4. 포트 스캐너 구현
박시니어 씨가 김개발 씨에게 과제를 주었습니다. "간단한 포트 스캐너를 직접 만들어봐요.
해커들이 시스템을 정찰할 때 제일 먼저 하는 게 포트 스캔이거든요." 김개발 씨는 네트워크 프로그래밍은 처음이라 걱정됐지만, 도전해보기로 했습니다.
포트 스캐너는 대상 시스템에서 어떤 포트가 열려 있는지 확인하는 도구입니다. 마치 건물의 모든 문을 두드려보며 어디가 열려 있는지 확인하는 것과 같습니다.
열린 포트는 곧 실행 중인 서비스를 의미하며, 이 정보를 통해 시스템의 구조를 파악할 수 있습니다.
다음 코드를 살펴봅시다.
import socket
import concurrent.futures
from datetime import datetime
class PortScanner:
# 잘 알려진 포트와 서비스 매핑
COMMON_PORTS = {
21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP",
53: "DNS", 80: "HTTP", 443: "HTTPS", 3306: "MySQL",
5432: "PostgreSQL", 6379: "Redis", 27017: "MongoDB"
}
def __init__(self, target: str, timeout: float = 1.0):
self.target = target
self.timeout = timeout
self.open_ports = []
def scan_port(self, port: int) -> dict:
"""단일 포트 스캔"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self.timeout)
result = sock.connect_ex((self.target, port))
sock.close()
if result == 0:
service = self.COMMON_PORTS.get(port, "Unknown")
return {"port": port, "status": "open", "service": service}
return {"port": port, "status": "closed", "service": None}
except socket.error:
return {"port": port, "status": "error", "service": None}
def scan_range(self, start_port: int, end_port: int, workers: int = 50):
"""포트 범위 스캔 (멀티스레드)"""
print(f"[*] {self.target} 스캔 시작: {start_port}-{end_port}")
start_time = datetime.now()
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
futures = {executor.submit(self.scan_port, p): p
for p in range(start_port, end_port + 1)}
for future in concurrent.futures.as_completed(futures):
result = future.result()
if result["status"] == "open":
self.open_ports.append(result)
print(f" [+] Port {result['port']}: {result['service']}")
elapsed = (datetime.now() - start_time).total_seconds()
print(f"[*] 스캔 완료: {len(self.open_ports)}개 포트 열림 ({elapsed:.2f}초)")
return self.open_ports
# 사용 예시 (localhost 스캔)
if __name__ == "__main__":
scanner = PortScanner("127.0.0.1", timeout=0.5)
# 주요 포트만 빠르게 스캔
results = scanner.scan_range(1, 1024, workers=100)
김개발 씨는 처음에 포트가 뭔지도 헷갈렸습니다. 박시니어 씨가 쉽게 설명해주었습니다.
"컴퓨터가 아파트라고 생각해봐요. 포트는 각 호실의 문이에요.
80번 문에는 웹서버가 살고, 22번 문에는 SSH가 살고, 3306번 문에는 MySQL이 살아요." 이 비유를 듣고 나니 포트 스캔의 목적이 명확해졌습니다. 해커가 시스템을 공격하려면 먼저 어떤 "문"이 열려 있는지 알아야 합니다.
웹서버가 돌아가고 있는지, 데이터베이스에 직접 접근할 수 있는지, SSH로 원격 접속이 가능한지. 이 정보가 없으면 공격 전략을 세울 수 없습니다.
위 코드의 PortScanner 클래스를 살펴보겠습니다. 핵심은 scan_port 메서드입니다.
소켓을 생성하고, 대상 IP와 포트에 연결을 시도합니다. 연결이 성공하면(result == 0) 해당 포트가 열려 있다는 뜻입니다.
연결이 실패하면 닫혀 있거나 방화벽에 막혀 있는 것입니다. COMMON_PORTS 딕셔너리는 잘 알려진 포트 번호와 서비스 이름을 매핑합니다.
80번 포트가 열려 있다면 웹서버가 돌아가고 있을 가능성이 높고, 22번이 열려 있다면 SSH 접속이 가능할 것입니다. 이런 매핑 정보가 있으면 스캔 결과를 해석하기 쉬워집니다.
포트 범위를 스캔할 때는 멀티스레딩이 필수입니다. 1번부터 65535번까지 순차적으로 스캔하면 시간이 너무 오래 걸립니다.
각 포트당 1초의 타임아웃만 잡아도 18시간이 걸립니다. 하지만 100개의 스레드로 동시에 스캔하면 몇 분 안에 끝납니다.
concurrent.futures.ThreadPoolExecutor를 사용하면 멀티스레딩을 쉽게 구현할 수 있습니다. workers 파라미터로 동시 실행 스레드 수를 조절합니다.
너무 많으면 네트워크에 부하가 걸리고, 너무 적으면 시간이 오래 걸립니다. 보통 50~100개가 적당합니다.
실제 보안 업계에서는 Nmap이라는 도구를 가장 많이 사용합니다. Nmap은 단순한 포트 스캔을 넘어 운영체제 탐지, 서비스 버전 확인, 취약점 스크립트 실행까지 가능합니다.
하지만 직접 포트 스캐너를 만들어보면 원리를 깊이 이해할 수 있습니다. 매우 중요한 주의사항이 있습니다.
허가 없이 다른 사람의 시스템을 스캔하면 불법입니다. 포트 스캔은 침입 시도로 간주될 수 있습니다.
반드시 자신이 소유한 시스템이나, 명시적으로 허가받은 시스템에서만 테스트하세요. 실습할 때는 localhost(127.0.0.1)를 사용하는 것이 가장 안전합니다.
김개발 씨는 자신의 개발 서버에서 포트 스캔을 돌려보았습니다. 예상대로 80, 443, 22번이 열려 있었는데, 놀랍게도 3306번(MySQL)도 외부에 열려 있었습니다.
"이거 큰일 날 뻔했네!" 데이터베이스 포트가 외부에 노출되면 해커가 직접 접근을 시도할 수 있습니다. 김개발 씨는 즉시 방화벽 설정을 수정했습니다.
실전 팁
💡 - 실습은 반드시 자신의 시스템 또는 허가받은 환경에서만 진행하세요
- 스캔 속도와 정확도는 트레이드오프 관계입니다. timeout 값을 조절해보세요
- 실무에서는 Nmap, masscan 등 전문 도구를 활용하되, 원리를 이해하고 사용하세요
5. 실제 침해사고 케이스 스터디
어느 금요일 오후, 회사에 비상이 걸렸습니다. 고객 데이터가 다크웹에 유출되었다는 제보가 들어온 것입니다.
모두가 패닉에 빠진 가운데, 보안팀장이 차분하게 말했습니다. "자, 침착하게 하나씩 살펴봅시다.
어디서부터 뚫렸는지 찾아야 해요."
침해사고 분석은 보안 사고가 발생했을 때 원인을 찾고, 피해 범위를 파악하며, 재발 방지책을 수립하는 과정입니다. 마치 범죄 현장에서 증거를 수집하고 범인의 행적을 추적하는 수사관처럼, 해커가 남긴 흔적을 따라가며 공격 경로를 밝혀냅니다.
다음 코드를 살펴봅시다.
import json
from datetime import datetime
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass
class IncidentEvent:
timestamp: str
event_type: str # initial_access, execution, persistence, exfiltration
description: str
evidence: str
severity: str # low, medium, high, critical
@dataclass
class IncidentReport:
incident_id: str
title: str
attack_vector: str
timeline: List[IncidentEvent] = field(default_factory=list)
affected_systems: List[str] = field(default_factory=list)
data_compromised: Optional[str] = None
def add_event(self, timestamp, event_type, description, evidence, severity):
event = IncidentEvent(timestamp, event_type, description, evidence, severity)
self.timeline.append(event)
return self
def generate_report(self):
print(f"\n{'='*60}")
print(f"침해사고 보고서: {self.incident_id}")
print(f"{'='*60}")
print(f"제목: {self.title}")
print(f"공격 벡터: {self.attack_vector}")
print(f"영향 시스템: {', '.join(self.affected_systems)}")
print(f"유출 데이터: {self.data_compromised or '확인 중'}")
print(f"\n[공격 타임라인]")
for i, event in enumerate(self.timeline, 1):
print(f"\n{i}. [{event.timestamp}] {event.event_type.upper()}")
print(f" 설명: {event.description}")
print(f" 증거: {event.evidence}")
print(f" 심각도: {event.severity}")
# 실제 사례 기반 시나리오 (가상)
report = IncidentReport(
incident_id="INC-2024-001",
title="SQL Injection을 통한 고객 DB 유출",
attack_vector="검색 기능 SQL Injection",
affected_systems=["web-server-01", "db-server-01"],
data_compromised="고객 이메일 50,000건, 해시된 비밀번호"
)
report.add_event("2024-01-15 02:30", "initial_access",
"검색 페이지에서 SQL Injection 시도 탐지",
"access.log: GET /search?q=' UNION SELECT * FROM users--", "critical")
report.add_event("2024-01-15 02:35", "execution",
"users 테이블 전체 덤프 실행",
"slow_query.log: SELECT * FROM users 쿼리 50,000행 반환", "critical")
report.add_event("2024-01-15 02:40", "exfiltration",
"대량 데이터 외부 전송",
"netflow: 15MB 데이터 외부 IP로 전송", "critical")
report.generate_report()
김개발 씨는 그날의 소동을 평생 잊지 못할 것입니다. 다행히 자신이 담당한 시스템은 아니었지만, 옆자리 동료가 밤새 로그를 분석하는 모습을 보며 보안의 중요성을 뼈저리게 느꼈습니다.
침해사고는 생각보다 자주 일어납니다. 뉴스에 나오는 대형 사고는 빙산의 일각일 뿐입니다.
많은 기업들이 해킹을 당하고도 모르고 있거나, 알아도 공개하지 않습니다. 2023년 통계에 따르면, 기업이 침해 사실을 인지하기까지 평균 277일이 걸린다고 합니다.
침해사고 분석의 첫 단계는 타임라인 구성입니다. 해커가 언제 처음 시스템에 접근했는지, 어떤 경로로 침투했는지, 무엇을 했는지, 언제 데이터를 빼갔는지를 시간순으로 정리합니다.
이 과정이 마치 추리 소설 같다고 해서 디지털 포렌식이라고 부릅니다. 위 코드의 IncidentReport 클래스는 침해사고 보고서의 구조를 보여줍니다.
사고 ID, 제목, 공격 벡터, 영향받은 시스템, 유출 데이터 등의 기본 정보와 함께, 시간순으로 정렬된 이벤트 목록이 핵심입니다. 예시 시나리오를 살펴보겠습니다.
새벽 2시 30분, 검색 페이지에서 SQL Injection 시도가 탐지되었습니다. 공격자는 ' UNION SELECT * FROM users-- 같은 페이로드를 사용했습니다.
5분 뒤, users 테이블 전체가 덤프되었고, 10분 뒤에는 15MB의 데이터가 외부로 전송되었습니다. 불과 10분 만에 5만 명의 고객 정보가 유출된 것입니다.
이 시나리오에서 주목할 점은 모든 증거가 로그에 남았다는 것입니다. access.log에는 공격 쿼리가, slow_query.log에는 대량 조회가, netflow에는 데이터 전송이 기록되었습니다.
만약 이 로그들이 없었다면 무슨 일이 있었는지조차 알 수 없었을 것입니다. 실제 침해사고에서 가장 흔한 공격 벡터는 무엇일까요?
2023년 Verizon DBIR 보고서에 따르면, 웹 애플리케이션 공격(SQL Injection, XSS 등)이 가장 많고, 그 다음이 피싱을 통한 계정 탈취, 취약한 원격 접속 서비스 순입니다. 우리가 만드는 웹 서비스가 가장 주요한 공격 대상인 셈입니다.
침해사고가 발생하면 무엇을 해야 할까요? 첫째, 격리입니다.
감염된 시스템을 네트워크에서 분리하여 추가 피해를 막습니다. 둘째, 증거 보존입니다.
로그, 메모리 덤프, 디스크 이미지 등을 안전하게 복사합니다. 셋째, 분석입니다.
공격 경로와 피해 범위를 파악합니다. 넷째, 복구 및 개선입니다.
취약점을 패치하고 시스템을 정상화합니다. 주의할 점은 증거를 함부로 건드리면 안 된다는 것입니다.
급한 마음에 서버를 재부팅하거나 파일을 삭제하면 중요한 증거가 사라집니다. 침해사고 대응은 반드시 정해진 절차에 따라 진행해야 합니다.
김개발 씨는 그날 이후로 자신의 코드에 로깅을 꼼꼼히 추가하기 시작했습니다. "언젠가 사고가 나도 원인을 찾을 수 있어야 해." 맞습니다.
로그는 사고가 나기 전에 미리 남겨두어야 합니다. 사고 후에는 이미 늦습니다.
실전 팁
💡 - 침해사고 대응 절차(Incident Response Plan)를 미리 수립해두세요
- 중요 시스템의 로그는 별도 서버에 실시간으로 백업하세요
- 사고 발생 시 증거 보존이 최우선입니다. 급하게 시스템을 건드리지 마세요
6. 로그 분석 기초
박시니어 씨가 김개발 씨에게 서버 접속 권한을 주며 말했습니다. "이제 로그 보는 법을 배워야 해요.
로그는 시스템의 블랙박스예요. 무슨 일이 있었는지 다 기록되어 있거든요." 김개발 씨는 처음 보는 로그 파일의 양에 압도되었습니다.
로그 분석은 시스템이 남긴 기록을 읽고 해석하여 정상 동작과 이상 징후를 구분하는 능력입니다. 마치 의사가 환자의 검사 결과를 보고 건강 상태를 판단하는 것처럼, 개발자는 로그를 보고 시스템의 상태를 진단합니다.
보안 관점에서 로그는 공격 탐지와 사고 조사의 핵심 자료입니다.
다음 코드를 살펴봅시다.
import re
from collections import defaultdict
from datetime import datetime
class LogAnalyzer:
# 의심스러운 패턴 정의
SUSPICIOUS_PATTERNS = [
(r"' OR '1'='1", "SQL Injection 시도"),
(r"<script>", "XSS 공격 시도"),
(r"\.\./\.\./", "Path Traversal 시도"),
(r"(union|select|insert|update|delete)\s", "SQL 키워드 탐지"),
(r"/admin|/wp-admin|/phpmyadmin", "관리자 페이지 스캔"),
]
def __init__(self):
self.ip_counter = defaultdict(int)
self.status_counter = defaultdict(int)
self.alerts = []
def parse_access_log(self, log_line: str) -> dict:
"""Apache/Nginx 공통 로그 포맷 파싱"""
pattern = r'(\d+\.\d+\.\d+\.\d+).*\[(.+)\]\s"(\w+)\s(.+?)\s'
match = re.search(pattern, log_line)
if match:
return {
"ip": match.group(1),
"timestamp": match.group(2),
"method": match.group(3),
"path": match.group(4)
}
return None
def detect_threats(self, log_line: str) -> list:
"""위협 패턴 탐지"""
threats = []
for pattern, description in self.SUSPICIOUS_PATTERNS:
if re.search(pattern, log_line, re.IGNORECASE):
threats.append(description)
return threats
def analyze_logs(self, log_lines: list):
"""로그 분석 실행"""
print("[*] 로그 분석 시작...")
for line in log_lines:
parsed = self.parse_access_log(line)
if parsed:
self.ip_counter[parsed["ip"]] += 1
threats = self.detect_threats(line)
if threats:
self.alerts.append({"log": line[:80], "threats": threats})
# 결과 출력
print(f"\n[통계]")
print(f" 총 요청: {sum(self.ip_counter.values())}건")
print(f" 고유 IP: {len(self.ip_counter)}개")
print(f"\n[상위 요청 IP]")
for ip, count in sorted(self.ip_counter.items(), key=lambda x: -x[1])[:5]:
print(f" {ip}: {count}건")
print(f"\n[보안 경고: {len(self.alerts)}건]")
for alert in self.alerts[:5]:
print(f" - {alert['threats']}")
print(f" {alert['log']}...")
# 샘플 로그 분석
sample_logs = [
'192.168.1.1 - - [15/Jan/2024:10:00:01] "GET /index.html HTTP/1.1" 200',
'10.0.0.5 - - [15/Jan/2024:10:00:02] "GET /search?q=normal HTTP/1.1" 200',
"10.0.0.99 - - [15/Jan/2024:10:00:03] \"GET /search?q=' OR '1'='1 HTTP/1.1\" 200",
'10.0.0.99 - - [15/Jan/2024:10:00:04] "GET /admin HTTP/1.1" 403',
'10.0.0.99 - - [15/Jan/2024:10:00:05] "GET /../../../etc/passwd HTTP/1.1" 400',
]
analyzer = LogAnalyzer()
analyzer.analyze_logs(sample_logs)
김개발 씨는 처음으로 서버에 접속해서 로그 파일을 열어보았습니다. 화면을 가득 채운 텍스트의 홍수에 눈이 어지러웠습니다.
"이걸 어떻게 다 봐요?" 박시니어 씨가 웃으며 말했습니다. "다 볼 필요 없어요.
중요한 건 패턴을 찾는 거예요." 로그는 시스템이 남기는 일기장 같은 것입니다. 누가 언제 접속했는지, 어떤 페이지를 요청했는지, 에러가 발생했는지 모든 것이 기록됩니다.
문제는 이 기록이 너무 많다는 것입니다. 하루에 수백만 줄의 로그가 쌓이는 서비스도 흔합니다.
위 코드의 LogAnalyzer 클래스는 로그 분석의 기본 원리를 보여줍니다. 크게 세 가지 기능이 있습니다.
첫째는 파싱입니다. 로그의 각 줄에서 IP, 시간, 요청 경로 등 의미 있는 정보를 추출합니다.
둘째는 통계입니다. IP별 요청 횟수, 상태 코드별 분포 등을 집계합니다.
셋째는 위협 탐지입니다. 의심스러운 패턴을 찾아냅니다.
SUSPICIOUS_PATTERNS 리스트를 보면 우리가 앞서 배운 공격들이 보입니다. SQL Injection 시도(' OR '1'='1), XSS 공격(<script>), Path Traversal(../) 등입니다.
이런 패턴이 로그에서 발견되면 누군가 공격을 시도하고 있다는 신호입니다. 샘플 로그를 분석한 결과를 보겠습니다.
총 5건의 요청 중 3건이 10.0.0.99에서 왔고, 이 IP는 SQL Injection, 관리자 페이지 접근, Path Traversal을 시도했습니다. 전형적인 해커의 정찰 활동입니다.
이런 IP는 즉시 차단해야 합니다. 실무에서는 ELK Stack(Elasticsearch, Logstash, Kibana)이나 Splunk 같은 도구를 사용합니다.
이 도구들은 대량의 로그를 수집, 저장, 검색, 시각화하는 기능을 제공합니다. 하지만 도구가 아무리 좋아도 어떤 패턴을 찾아야 하는지 모르면 소용없습니다.
그래서 기본 원리를 이해하는 것이 중요합니다. 로그 분석에서 가장 중요한 것은 정상 상태를 아는 것입니다.
평소에 어떤 IP가 접속하는지, 하루 평균 요청량이 얼마인지, 주로 어떤 페이지가 조회되는지를 알아야 비정상을 감지할 수 있습니다. 갑자기 특정 IP에서 요청이 폭증하거나, 존재하지 않는 페이지로의 접근이 늘어나면 이상 징후입니다.
자주 하는 실수 중 하나는 로그를 너무 적게 남기는 것입니다. 용량이 아깝다고, 성능에 영향을 준다고 로깅을 최소화하면 나중에 사고가 났을 때 분석할 데이터가 없습니다.
반대로 너무 많이 남기면 정작 중요한 정보가 묻혀버립니다. 적절한 균형이 필요합니다.
또 하나 주의할 점은 로그에 민감 정보를 남기면 안 된다는 것입니다. 비밀번호, 신용카드 번호, 주민등록번호 같은 정보가 로그에 찍히면 그 자체가 보안 취약점이 됩니다.
로그는 공격자에게도 유용한 정보가 될 수 있음을 기억하세요. 김개발 씨는 박시니어 씨의 조언대로 자신의 서비스 로그를 분석해보았습니다.
놀랍게도 매일 수십 건의 SQL Injection 시도가 있었습니다. 대부분 자동화된 봇의 공격이었지만, 그동안 전혀 모르고 있었다는 사실에 등골이 서늘해졌습니다.
"로그를 안 봤으면 영영 몰랐을 거야."
실전 팁
💡 - 로그는 별도 서버에 백업하세요. 해커는 흔적을 지우기 위해 로그를 삭제합니다
- 정상 상태의 기준(baseline)을 먼저 파악한 후 이상 탐지에 활용하세요
- 민감 정보는 로그에 남기지 말고, 필요하다면 마스킹 처리하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Context Degradation 컨텍스트 저하 패턴 진단
LLM의 컨텍스트 윈도우에서 발생하는 다양한 정보 손실과 왜곡 패턴을 진단하고, 이를 완화하는 실전 전략을 학습합니다. 프롬프트 엔지니어링의 핵심 난제를 풀어봅니다.
Phase 2 공격 기법 이해와 방어 실전 가이드
웹 애플리케이션 보안의 핵심인 공격 기법과 방어 전략을 실습 중심으로 배웁니다. 인증 우회부터 SQL Injection, XSS, CSRF까지 실제 공격 시나리오를 이해하고 방어 코드를 직접 작성해봅니다.
프로덕션 워크플로 배포 완벽 가이드
LLM 기반 애플리케이션을 실제 운영 환경에 배포하기 위한 워크플로 최적화, 캐싱 전략, 비용 관리 방법을 다룹니다. Airflow와 서버리스 아키텍처를 활용한 실습까지 포함하여 초급 개발자도 프로덕션 수준의 배포를 할 수 있도록 안내합니다.
워크플로 모니터링과 디버깅 완벽 가이드
LLM 기반 워크플로의 실행 상태를 추적하고, 문제를 진단하며, 성능을 최적화하는 방법을 다룹니다. LangSmith 통합부터 커스텀 모니터링 시스템 구축까지 실무에서 바로 적용할 수 있는 내용을 담았습니다.
LlamaIndex Workflow 완벽 가이드
LlamaIndex의 워크플로 시스템을 활용하여 복잡한 RAG 파이프라인을 구축하는 방법을 알아봅니다. 이벤트 기반 워크플로부터 멀티 인덱스 쿼리까지 단계별로 학습합니다.