
이 글은 "Python으로 나만의 SIEM 만들기" 시리즈의 마지막 편입니다.
- 1편: 시작편 - 30분만에 SIEM 구축하기
- 2편: MITRE ATT&CK 기반 위협 탐지 룰 구현
- 3편: FastAPI로 실시간 보안 이벤트 처리하기
- 4편: Elasticsearch로 대용량 로그 저장하고 검색하기
- [현재] 5편: 보안 설계 원칙을 코드로 구현하기
"보안은 기능이 아니라 설계입니다."
많은 개발자들이 기능 구현 후 "보안 기능"을 추가하려 합니다.
하지만 이것은 틀렸습니다.
보안은 처음부터 설계에 녹아들어야 합니다.
이번 글에서는 제가 Mini-SIEM을 개발하면서 적용한 5가지 보안 설계 원칙을 코드와 함께 설명합니다.
모든 원칙은 Saltzer & Schroeder의 보안 설계 원칙 (1975년 MIT 논문)을 기반으로 합니다.
이 중 5가지를 실제 코드로 구현합니다.
"단일 방어선이 뚫려도 시스템이 안전하도록 여러 계층의 방어를 구축하라"
비유:
성 (Castle) 방어 시스템
├─ Layer 1: 해자 (Moat)
├─ Layer 2: 외벽 (Outer Wall)
├─ Layer 3: 문지기 (Gatekeeper)
├─ Layer 4: 내벽 (Inner Wall)
└─ Layer 5: 성 내부 경비 (Guards)
하나가 뚫려도 다음 방어선이 존재!
2017년 Equifax 침해 사고
공격 경로:
1. Apache Struts 취약점 (CVE-2017-5638) ❌ 패치 미적용
2. 웹 방화벽 우회 ❌ 규칙 미설정
3. 데이터베이스 접근 ❌ 네트워크 분리 없음
4. 민감 데이터 암호화 없음 ❌ 평문 저장
5. 이상 트래픽 탐지 실패 ❌ 모니터링 부재
→ 1.43억 명 개인정보 유출
만약 다층 방어가 있었다면:
1. Struts 취약점 ❌ → 2. WAF 차단 ✅ → 공격 실패!
┌─────────────────────────────────────────────────────────┐
│ Layer 1: API 인증 (Authentication) │
│ - X-API-Key 헤더 검증 │
│ - 401/403 반환 │
├─────────────────────────────────────────────────────────┤
│ Layer 2: 입력 검증 (Input Validation) │
│ - Pydantic 타입 검증 │
│ - 필드 범위 검증 (count >= 1) │
│ - 422 Unprocessable Entity 반환 │
├─────────────────────────────────────────────────────────┤
│ Layer 3: 위협 탐지 (Threat Detection) │
│ - 7가지 독립적 탐지 룰 │
│ - SQL Injection, Brute Force 차단 │
├─────────────────────────────────────────────────────────┤
│ Layer 4: 로그 저장 (Audit Trail) │
│ - 모든 요청 로깅 │
│ - Elasticsearch 영구 보관 │
├─────────────────────────────────────────────────────────┤
│ Layer 5: 실시간 알림 (Alerting) │
│ - Slack 즉시 통보 │
│ - 인시던트 자동 생성 │
└─────────────────────────────────────────────────────────┘
Layer 1: API 인증
# app/utils/auth.py
from fastapi import HTTPException, Security, status
from fastapi.security import APIKeyHeader
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
API_KEY = os.getenv("API_KEY")
def verify_api_key(api_key: str = Security(api_key_header)) -> str:
"""Layer 1: 인증 계층"""
if api_key is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="API Key is missing"
)
if api_key != API_KEY:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid API Key"
)
return api_key
Layer 2: 입력 검증
# app/models/log.py
from pydantic import BaseModel, Field, validator
class LogEvent(BaseModel):
"""Layer 2: 입력 검증 계층"""
event_type: str = Field(..., description="이벤트 타입")
count: Optional[int] = Field(1, ge=1) # 최소값 검증
@validator('event_type')
def validate_event_type(cls, v):
"""허용된 이벤트 타입만 통과"""
allowed_types = [e.value for e in EventType]
if v.lower() not in allowed_types:
# 알 수 없는 타입은 UNKNOWN으로 처리 (거부하지 않음)
return EventType.UNKNOWN.value
return v.lower()
@validator('count')
def validate_count(cls, v):
"""비정상적으로 큰 값 차단"""
if v > 10000: # 한 번에 10,000회 이상은 비정상
raise ValueError("count must be <= 10000")
return v
Layer 3: 위협 탐지
# app/utils/detector.py
class ThreatDetector:
"""Layer 3: 위협 탐지 계층"""
@classmethod
def analyze(cls, log: NormalizedLog) -> NormalizedLog:
"""7가지 독립적 탐지 룰 실행"""
threats = []
# 각 탐지 룰이 독립적으로 동작
# 하나가 우회되어도 다른 룰로 탐지 가능!
detectors = [
cls.detect_brute_force,
cls.detect_suspicious_time_access,
cls.detect_sql_injection,
cls.detect_privilege_escalation,
cls.detect_botnet_activity,
cls.detect_malicious_ip,
cls.detect_file_access_anomaly,
]
for detector in detectors:
is_threat, details = detector(log)
if is_threat and details:
threats.append(details)
if threats:
log.is_threat = True
log.threat_details = " | ".join(threats)
return log
Layer 4: 감사 추적 (Audit Trail)
# app/main.py
@app.post("/log")
async def receive_log(
log_event: LogEvent,
api_key: str = Depends(verify_api_key)
):
# ... 처리 ...
# Layer 4: 모든 이벤트를 로깅 (증거 보존)
logger.info(
f"[EVENT] {analyzed_log.event_type.value} | "
f"IP={analyzed_log.source_ip} | "
f"Severity={analyzed_log.severity.value} | "
f"Threat={analyzed_log.is_threat}"
)
# Elasticsearch에도 영구 저장
# (재부팅해도 데이터 유지)
Layer 5: 실시간 알림
# Layer 5: 위협 발견 시 즉시 알림
if analyzed_log.is_threat:
# 인시던트 생성
incident = incident_manager.create_incident(analyzed_log)
# Slack 알림
alert_message = (
f"🚨 *[{analyzed_log.severity.value.upper()}]* "
f"Security Threat Detected\n"
f"• *Type*: {analyzed_log.event_type.value}\n"
f"• *Source IP*: {analyzed_log.source_ip}\n"
f"• *Details*: {analyzed_log.threat_details}\n"
f"• *Incident ID*: {incident.id}"
)
send_slack_alert(alert_message)
공격 시나리오: SQL Injection 시도
# 공격자 요청
curl -X POST http://localhost:8000/log \
-H "X-API-Key: WRONG_KEY" \
-d '{
"event_type": "sql_injection",
"source_ip": "203.0.113.50",
"raw_log": "SELECT * FROM users WHERE id=1 OR 1=1--"
}'
방어 과정:
Layer 1 (API 인증): ❌ FAILED
→ 403 Forbidden 반환
→ 공격 차단!
(만약 Layer 1 우회 시)
Layer 2 (입력 검증): ✅ PASS (유효한 JSON)
Layer 3 (위협 탐지): ❌ DETECTED!
→ SQL Injection 패턴 탐지
→ 인시던트 생성
Layer 4 (로그 저장): ✅ 증거 보존
→ /app/logs/app.log
→ Elasticsearch
Layer 5 (알림): ✅ Slack 즉시 알림
→ SOC 팀 인지
결과: 5개 계층 중 4개 작동 → 시스템 안전!
"시스템은 기본적으로 거부(Deny)하고, 명시적으로 허용(Allow)하라"
원칙:
2019년 Capital One 침해 사고
AWS S3 버킷 설정:
{
"public_access": "default" // ❌ 기본값이 공개!
}
→ 1억 명 신용카드 정보 유출
만약 Fail-Safe Defaults였다면:
{
"public_access": "deny_all" // ✅ 기본값 거부
}
명시적으로 허용해야만 접근 가능
→ 침해 방지!
# ❌ 나쁜 예: 기본값 허용
API_KEY = os.getenv("API_KEY", None) # None이면 인증 안 함?
def verify_api_key(api_key: str = Security(api_key_header)):
if API_KEY is None:
return "OK" # ⚠️ 위험! 인증 없이 통과
# ✅ 좋은 예: 기본값 거부
API_KEY = os.getenv("API_KEY") # 설정 안 되면 None
def verify_api_key(api_key: str = Security(api_key_header)):
if api_key is None:
raise HTTPException(401, "API Key missing") # ✅ 즉시 거부
if API_KEY is None:
raise HTTPException(500, "Server not configured") # ✅ 서버 설정 오류
if api_key != API_KEY:
raise HTTPException(403, "Invalid API Key") # ✅ 잘못된 키 거부
return api_key # 모든 검증 통과해야 허용
# app/models/log.py
@validator('event_type', pre=True)
def normalize_event_type(cls, v):
"""알 수 없는 타입은 UNKNOWN으로 처리"""
if isinstance(v, str):
try:
return EventType(v.lower())
except ValueError:
# ✅ Fail-Safe: 거부하지 않고 UNKNOWN으로
# (로그는 받되, 특별 처리)
return EventType.UNKNOWN
return v
설계 근거:
Option A: 알 수 없는 타입 → 거부 (422 Error)
❌ 새로운 공격 유형을 탐지 못함
❌ 로그 소스 추가 시 호환성 문제
Option B: 알 수 없는 타입 → UNKNOWN으로 수용 ✅
✅ 모든 로그 수집 (증거 보존)
✅ UNKNOWN 타입만 따로 분석 가능
✅ 유연한 확장
# app/utils/detector.py
@staticmethod
def assign_severity(log, is_threat, threat_details) -> SeverityLevel:
"""위협 심각도 자동 할당"""
if not is_threat:
# ✅ Fail-Safe: 위협 아니면 무조건 INFO
return SeverityLevel.INFO
# Critical: SQL Injection, 악성 IP
if log.event_type in [EventType.SQL_INJECTION, EventType.MALWARE_DETECTED]:
return SeverityLevel.CRITICAL
# ... (중략) ...
# ✅ Fail-Safe: 알 수 없는 위협은 LOW (보수적)
# (과탐 > 미탐)
return SeverityLevel.LOW
# ❌ 나쁜 예: 위험한 기본값
DEBUG = os.getenv("DEBUG", "True") # 프로덕션에서 디버그 모드?!
# ✅ 좋은 예: 안전한 기본값
DEBUG = os.getenv("DEBUG", "False").lower() == "true"
ALLOW_ORIGINS = os.getenv("ALLOW_ORIGINS", "").split(",") # 빈 리스트 (모두 차단)
MAX_REQUEST_SIZE = int(os.getenv("MAX_REQUEST_SIZE", "1048576")) # 1MB (작게)
# ❌ 나쁜 예: 에러 시 계속 진행
try:
send_slack_alert(message)
except Exception:
pass # ⚠️ 알림 실패해도 무시? 위험!
# ✅ 좋은 예: 에러 로깅 + Fail-Safe
try:
send_slack_alert(message)
except Exception as e:
logger.error(f"Slack alert failed: {e}")
# 알림 실패해도 인시던트는 생성됨 (데이터 유실 방지)
# 운영자가 로그에서 확인 가능
# 설계 시 자문 (Self-Assessment)
□ API 기본값이 "거부"인가?
□ 알 수 없는 입력을 안전하게 처리하는가?
□ 에러 발생 시 보수적으로 동작하는가?
□ 환경 변수 누락 시 안전한가?
□ 권한 부여가 명시적인가?
"모든 접근을 매번 검증하라. 캐싱이나 우회 경로를 허용하지 마라."
원칙:
IDOR (Insecure Direct Object Reference)
# ❌ 취약한 코드
@app.get("/incidents/{incident_id}")
def get_incident(incident_id: str):
# 인증 확인 안 함!
incident = db.get(incident_id)
return incident
# 공격:
# GET /incidents/INC-20251111-0001 ✅ 자기 것
# GET /incidents/INC-20251111-0002 ✅ 남의 것도 조회됨!
# app/main.py
# ✅ 읽기 API: 인증 불필요 (공개 정보)
@app.get("/dashboard")
def get_dashboard():
return stats_service.get_dashboard_stats()
@app.get("/incidents")
def list_incidents():
return incident_manager.list_incidents()
# ✅ 쓰기 API: 인증 필수 (데이터 변경)
@app.post("/log")
async def receive_log(
log_event: LogEvent,
api_key: str = Depends(verify_api_key) # 매번 검증!
):
...
@app.post("/incidents/{incident_id}/status", dependencies=[Depends(verify_api_key)])
def update_incident_status(...):
# dependencies로 전역 적용
...
설계 철학:
읽기 (Read):
- 대시보드, 통계, 인시던트 목록 → 인증 불필요
- 이유: 내부 팀만 접근 가능한 네트워크
- 사용성 우선
쓰기 (Write):
- 로그 전송, 인시던트 변경 → 인증 필수
- 이유: 데이터 무결성 보호
- 보안 우선
# ❌ 나쁜 예: 함수 내부 검증 (우회 가능)
def process_log(log_event: LogEvent):
# 함수 호출자가 검증을 건너뛸 수 있음
if not verify_api_key():
raise Exception("Unauthorized")
...
# 다른 곳에서 직접 호출 시 우회됨
process_log(malicious_log) # 인증 건너뛰기!
# ✅ 좋은 예: FastAPI 의존성 주입 (우회 불가)
@app.post("/log")
async def receive_log(
log_event: LogEvent,
api_key: str = Depends(verify_api_key) # FastAPI가 자동 실행
):
# 이 함수에 도달했다면 이미 인증 통과
...
# 직접 호출 불가 (FastAPI 라우터를 통해서만 호출됨)
# 현재: HTTP (개발 환경)
app = FastAPI()
# 프로덕션: HTTPS 강제
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
if not DEBUG:
app.add_middleware(HTTPSRedirectMiddleware)
# HTTP → HTTPS 자동 리다이렉트
# app/main.py
@app.post("/log")
async def receive_log(
log_event: LogEvent,
api_key: str = Depends(verify_api_key),
request: Request # 요청 객체 주입
):
# Complete Mediation: 모든 접근 기록
logger.info(
f"[ACCESS] "
f"Endpoint=/log | "
f"Client={request.client.host} | "
f"API_Key={api_key[:8]}... | " # 앞 8자만 로깅 (보안)
f"Event={log_event.event_type}"
)
# ... 처리 ...
"사용자/프로세스에게 필요한 최소한의 권한만 부여하라"
원칙:
2013년 Target 침해 사고
HVAC 업체 계정:
- 원래 필요한 권한: HVAC 시스템 접근
- 실제 부여된 권한: 전체 네트워크 접근 ❌
→ HVAC 업체 계정 탈취
→ POS 시스템 침투
→ 4천만 개 신용카드 정보 유출
만약 Least Privilege였다면:
HVAC 업체 계정:
- 권한: HVAC 시스템만 ✅
- POS 시스템 접근 불가 ✅
→ 침해 실패!
# app/main.py
# Level 0: 공개 (인증 불필요)
@app.get("/")
def home():
return {"message": "Mini-SIEM", "version": "2.0.0"}
# Level 1: 읽기 권한 (인증 불필요)
@app.get("/dashboard")
def get_dashboard():
return stats_service.get_dashboard_stats()
@app.get("/incidents")
def list_incidents():
return incident_manager.list_incidents()
# Level 2: 쓰기 권한 (API 키 필수)
@app.post("/log")
async def receive_log(
log_event: LogEvent,
api_key: str = Depends(verify_api_key)
):
...
@app.post("/incidents/{incident_id}/status", dependencies=[Depends(verify_api_key)])
def update_incident_status(...):
...
권한 매트릭스:
| 엔드포인트 | 공개 | 읽기 | 쓰기 | 관리자 |
|---|---|---|---|---|
| GET / | ✅ | ✅ | ✅ | ✅ |
| GET /dashboard | ❌ | ✅ | ✅ | ✅ |
| GET /incidents | ❌ | ✅ | ✅ | ✅ |
| POST /log | ❌ | ❌ | ✅ | ✅ |
| POST /incidents/.../status | ❌ | ❌ | ✅ | ✅ |
# 현재: 단일 API 키
API_KEY = os.getenv("API_KEY")
# 향후: 역할별 API 키
class Role(str, Enum):
VIEWER = "viewer" # 읽기만
ANALYST = "analyst" # 읽기 + 인시던트 변경
ADMIN = "admin" # 모든 권한
API_KEYS = {
"viewer-key-123": Role.VIEWER,
"analyst-key-456": Role.ANALYST,
"admin-key-789": Role.ADMIN,
}
def verify_api_key_rbac(
required_role: Role,
api_key: str = Security(api_key_header)
) -> str:
"""역할 기반 인증"""
if api_key not in API_KEYS:
raise HTTPException(403, "Invalid API Key")
user_role = API_KEYS[api_key]
# 권한 계층: ADMIN > ANALYST > VIEWER
role_hierarchy = {
Role.VIEWER: 1,
Role.ANALYST: 2,
Role.ADMIN: 3,
}
if role_hierarchy[user_role] < role_hierarchy[required_role]:
raise HTTPException(403, f"Requires {required_role} role")
return api_key
# 사용 예시
@app.post("/log")
async def receive_log(
log_event: LogEvent,
api_key: str = Depends(lambda: verify_api_key_rbac(Role.ANALYST))
):
# ANALYST 이상만 로그 전송 가능
...
@app.delete("/incidents/{id}")
async def delete_incident(
incident_id: str,
api_key: str = Depends(lambda: verify_api_key_rbac(Role.ADMIN))
):
# ADMIN만 삭제 가능
...
# Dockerfile
# ❌ 나쁜 예: root로 실행
USER root
CMD ["uvicorn", "main:app"]
# ✅ 좋은 예: 전용 사용자
RUN useradd -m -u 1000 siem
USER siem
# 읽기 전용 파일 시스템 (docker-compose.yml)
services:
fastapi_app:
read_only: true # 파일 시스템 읽기 전용
tmpfs:
- /tmp # 임시 파일만 허용
# PostgreSQL 권한 설정
CREATE ROLE siem_read WITH LOGIN PASSWORD 'xxx';
CREATE ROLE siem_write WITH LOGIN PASSWORD 'yyy';
-- 읽기 전용
GRANT SELECT ON ALL TABLES IN SCHEMA public TO siem_read;
-- 쓰기 가능
GRANT SELECT, INSERT ON ALL TABLES IN SCHEMA public TO siem_write;
-- DROP, DELETE 권한 없음! (데이터 보호)
# 애플리케이션
class DBConnection:
def __init__(self, mode: str):
if mode == "read":
self.user = "siem_read"
elif mode == "write":
self.user = "siem_write"
else:
raise ValueError("Invalid mode")
# 읽기 전용 연결
read_db = DBConnection("read")
# 쓰기 전용 연결
write_db = DBConnection("write")
대응:
@app.post("/log", dependencies=[Depends(verify_api_key)])
async def receive_log(...):
# 인증 없이 접근 불가
...
대응:
SQL_INJECTION_PATTERNS = [
r"(\bor\b\s+\d+\s*=\s*\d+)",
r"(\bunion\b\s+\bselect\b)",
r"(';?\s*drop\s+table)",
# ...
]
def detect_sql_injection(log):
for pattern in SQL_INJECTION_PATTERNS:
if re.search(pattern, log.raw_log, re.IGNORECASE):
return True, "SQL Injection detected"
대응:
.env 파일 (Git 제외)# .env.example (안전한 템플릿)
API_KEY=your_secure_api_key_here
SLACK_WEBHOOK_URL=https://hooks.slack.com/...
DEBUG=False # 프로덕션 기본값
# .gitignore
.env # 실제 설정은 Git에 커밋 안 됨
대응:
def detect_brute_force(log):
if log.event_type == EventType.LOGIN_FAILED and log.count >= 5:
return True, f"Brute force: {log.count} attempts"
대응:
logger.info(
f"[EVENT] {event_type} | "
f"IP={source_ip} | "
f"Threat={is_threat}"
)
# Filebeat → Elasticsearch (영구 보존)
현재 상태:
개선 방안:
# 1. HTTPS 강제
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
app.add_middleware(HTTPSRedirectMiddleware)
# 2. 민감 데이터 암호화
from cryptography.fernet import Fernet
class LogEncryption:
def __init__(self):
self.key = os.getenv("ENCRYPTION_KEY").encode()
self.cipher = Fernet(self.key)
def encrypt(self, data: str) -> str:
return self.cipher.encrypt(data.encode()).decode()
def decrypt(self, data: str) -> str:
return self.cipher.decrypt(data.encode()).decode()
# 사용
encryptor = LogEncryption()
encrypted_log = encryptor.encrypt(log.raw_log)
# Elasticsearch 저장 시 암호화
POST /siem-logs/_doc
{
"raw_log": "encrypted_data_here",
"encrypted": true
}
대응:
requirements.txt로 버전 고정# requirements.txt (버전 고정)
fastapi==0.115.0
uvicorn==0.32.0
pydantic==2.9.2
# 취약점 스캔
$ pip-audit
# 또는
$ safety check
공격자 목표: 관리자 계정 탈취 후 데이터 유출
# 공격자 시도
for i in {1..100}; do
curl -X POST http://localhost:8000/log \
-H "X-API-Key: attacker-key" \
-d '{
"event_type": "login_failed",
"source_ip": "203.0.113.100",
"username": "admin",
"count": 1
}'
done
시스템 방어:
Layer 1 (인증): ✅ PASS (유효한 API 키)
Layer 2 (검증): ✅ PASS (유효한 JSON)
Layer 3 (탐지): ❌ DETECTED!
탐지 룰: detect_brute_force()
→ 5회째에서 탐지
→ 인시던트 생성: INC-20251111-0001
→ Slack 알림: "🚨 Brute Force from 203.0.113.100"
Layer 4 (로깅): ✅ 모든 시도 기록
→ /app/logs/app.log
→ Elasticsearch: siem-logs-2025.11.11
Layer 5 (알림): ✅ SOC 팀 인지
→ IP 차단 결정
# 공격자 시도 (Brute Force 차단 후 다른 공격)
curl -X POST http://localhost:8000/log \
-H "X-API-Key: attacker-key" \
-d '{
"event_type": "sql_injection",
"source_ip": "203.0.113.100",
"raw_log": "admin' OR '1'='1'--"
}'
시스템 방어:
Layer 3 (탐지): ❌ DETECTED!
탐지 룰: detect_sql_injection()
→ 패턴 매칭: r"(\bor\b\s+\d+\s*=\s*\d+)" 근사 매칭
→ 심각도: CRITICAL
→ 인시던트: INC-20251111-0002
→ Slack: "🚨 [CRITICAL] SQL Injection detected"
→ 공격 실패!
# 공격자 시도 (다른 취약점 탐색)
curl -X POST http://localhost:8000/log \
-H "X-API-Key: attacker-key" \
-d '{
"event_type": "privilege_escalation",
"source_ip": "203.0.113.100",
"raw_log": "sudo -i",
"username": "user123"
}'
시스템 방어:
Layer 3 (탐지): ❌ DETECTED!
탐지 룰: detect_privilege_escalation()
→ 키워드 매칭: "sudo"
→ 심각도: HIGH
→ 인시던트: INC-20251111-0003
→ 공격 실패!
공격자: 3가지 공격 시도
시스템: 3건 모두 탐지 및 차단 ✅
생성된 인시던트:
- INC-20251111-0001: Brute Force (MEDIUM)
- INC-20251111-0002: SQL Injection (CRITICAL)
- INC-20251111-0003: Privilege Escalation (HIGH)
SOC 팀 조치:
1. IP 203.0.113.100 방화벽 차단
2. 관련 계정 비밀번호 재설정
3. 침해 지표 (IOC) 공유
□ Defense in Depth
□ 최소 3개 이상의 방어 계층?
□ 각 계층이 독립적으로 동작?
□ 하나 뚫려도 시스템 안전?
□ Fail-Safe Defaults
□ 기본값이 "거부"?
□ 환경 변수 누락 시 안전?
□ 에러 발생 시 보수적?
□ Complete Mediation
□ 모든 요청마다 인증?
□ 캐싱으로 우회 불가?
□ 감사 로그 기록?
□ Least Privilege
□ 필요한 최소 권한만?
□ 역할 기반 접근 제어?
□ 기본 권한은 없음?
□ OWASP Top 10
□ Injection 대응?
□ 인증 실패 탐지?
□ 보안 로깅?
□ 코드 리뷰
□ API 인증 확인
□ 입력 검증 확인
□ 에러 처리 확인
□ 테스트
□ 인증 우회 시도 (401/403 확인)
□ SQL Injection 테스트
□ Brute Force 시뮬레이션
□ 문서화
□ 보안 설계 문서
□ 인증 방법 가이드
□ 침해 대응 플레이북
□ 모니터링
□ 실시간 위협 탐지
□ 인시던트 대시보드
□ 알림 정상 작동
□ 정기 점검
□ 의존성 취약점 스캔 (weekly)
□ API 키 교체 (monthly)
□ 침투 테스트 (quarterly)
□ 사고 대응
□ 인시던트 대응 절차
□ 백업 및 복구 계획
□ 침해 지표 (IOC) 수집
이 시리즈를 통해 우리는:
SIEM의 본질을 이해했습니다
실무 수준의 기술을 적용했습니다
오픈소스로 구현했습니다
포트폴리오를 완성했습니다
"보안은 기능이 아니라 설계다"
학습:
프로젝트 확장:
커리어:
이 시리즈를 끝까지 읽어주신 여러분께 감사드립니다.
질문, 피드백, 개선 아이디어가 있다면:
함께 더 안전한 세상을 만들어갑시다! 🛡️
app/ 디렉토리 참조⭐ GitHub Star와 좋아요 부탁드립니다!
💡 "보안은 마라톤입니다. 끝이 없습니다. 하지만 포기하지 마세요."
여러분의 보안 여정을 응원합니다! 🚀