애플리케이션 보안 엔지니어링 가이드
소프트웨어가 비즈니스의 핵심이 된 시대, 보안은 선택이 아니라 생존의 문제입니다. 이 가이드는 OWASP Top 10부터 DevSecOps 파이프라인까지 애플리케이션 보안의 전 범위를 다룹니다.
1. 왜 보안이 중요한가
데이터 유출의 현실
IBM의 "Cost of a Data Breach Report"에 따르면, 2024년 기준 데이터 유출 한 건의 평균 비용은 약 488만 달러에 달합니다. 유출 사고의 주요 원인은 다음과 같습니다.
| 원인 | 비율 | 평균 비용 |
|---|---|---|
| 피싱 공격 | 16% | 476만 달러 |
| 도난/유출된 인증정보 | 15% | 463만 달러 |
| 클라우드 설정 오류 | 12% | 414만 달러 |
| 제로데이 취약점 | 10% | 518만 달러 |
개발자의 보안 책임
보안은 더 이상 보안팀만의 책임이 아닙니다. "Shift Left" 패러다임에 따라, 개발 초기 단계에서 보안을 내재화해야 합니다.
- 코드 작성 단계에서 취약점을 방지하는 것이 사후 패치보다 6~100배 저렴합니다
- SDLC(소프트웨어 개발 수명 주기) 전체에서 보안을 고려해야 합니다
- 보안은 기능(feature)이 아니라 품질(quality)의 일부입니다
보안의 3대 원칙: CIA Triad
- Confidentiality (기밀성): 인가된 사용자만 데이터에 접근 가능
- Integrity (무결성): 데이터가 무단으로 변경되지 않음을 보장
- Availability (가용성): 필요할 때 시스템과 데이터에 접근 가능
2. OWASP Top 10 (2021)
OWASP Top 10은 웹 애플리케이션에서 가장 치명적인 10가지 보안 취약점을 정리한 것입니다. 각각의 취약점과 방어법을 살펴봅니다.
A01: Broken Access Control (접근 제어 실패)
가장 흔한 취약점입니다. 인가되지 않은 사용자가 다른 사용자의 데이터에 접근하거나 관리자 기능을 수행할 수 있습니다.
공격 시나리오: URL 파라미터를 변경하여 다른 사용자의 주문 내역을 조회
방어법:
# Flask에서 접근 제어 데코레이터
from functools import wraps
from flask import abort, g
def require_role(role):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not g.current_user or g.current_user.role != role:
abort(403)
return f(*args, **kwargs)
return decorated_function
return decorator
@app.route('/admin/users')
@require_role('admin')
def admin_users():
return get_all_users()
- 기본적으로 모든 접근을 거부하고, 명시적으로 허용하는 화이트리스트 방식을 사용합니다
- 서버 사이드에서 반드시 권한을 검증합니다
- IDOR(Insecure Direct Object References) 방지를 위해 UUID를 사용합니다
A02: Cryptographic Failures (암호화 실패)
민감 데이터가 평문으로 전송되거나 약한 암호화로 보호되는 경우입니다.
방어법:
- 전송 중 데이터: TLS 1.3 사용
- 저장 데이터: AES-256-GCM으로 암호화
- 비밀번호: bcrypt 또는 Argon2로 해싱
- MD5, SHA-1 같은 취약한 알고리즘을 사용하지 않습니다
A03: Injection (인젝션)
SQL, NoSQL, OS 명령어, LDAP 등의 인젝션 공격입니다.
취약한 코드:
# 절대 이렇게 하면 안 됩니다 - SQL Injection에 취약
query = "SELECT * FROM users WHERE id = " + user_input
안전한 코드:
# 파라미터화 쿼리 사용
cursor.execute("SELECT * FROM users WHERE id = %s", (user_input,))
# ORM 사용 (SQLAlchemy)
user = User.query.filter_by(id=user_input).first()
A04: Insecure Design (안전하지 않은 설계)
설계 단계에서의 보안 결함입니다. 코드 수준이 아닌 아키텍처 수준의 문제입니다.
방어법:
- Threat Modeling 수행 (STRIDE, PASTA 방법론)
- 보안 설계 원칙 적용 (최소 권한, 심층 방어, 실패 안전)
- 비즈니스 로직에 대한 보안 시나리오 테스트
A05: Security Misconfiguration (보안 설정 오류)
기본 계정 미변경, 불필요한 기능 활성화, 디버그 모드 운영 환경 노출 등이 해당됩니다.
체크리스트:
- 기본 비밀번호를 변경했는가
- 불필요한 포트와 서비스를 비활성화했는가
- 에러 메시지에 스택 트레이스가 노출되지 않는가
- 보안 헤더(X-Frame-Options, X-Content-Type-Options)가 설정되었는가
A06: Vulnerable and Outdated Components (취약하고 오래된 컴포넌트)
알려진 취약점이 있는 라이브러리나 프레임워크를 사용하는 경우입니다.
방어법:
# npm 보안 감사
npm audit
# Python 의존성 검사
pip-audit
# Go 취약점 검사
govulncheck ./...
A07: Identification and Authentication Failures (인증 실패)
약한 비밀번호 정책, 세션 관리 미흡, 브루트포스 공격 방치 등이 해당됩니다. 자세한 내용은 3장에서 다룹니다.
A08: Software and Data Integrity Failures (무결성 실패)
CI/CD 파이프라인, 소프트웨어 업데이트, 직렬화에서의 무결성 검증 부족입니다.
방어법:
- 디지털 서명으로 소프트웨어 무결성 검증
- SBOM(Software Bill of Materials) 관리
- CI/CD 파이프라인 보안 (코드 서명, 접근 제어)
A09: Security Logging and Monitoring Failures (로깅 및 모니터링 실패)
보안 이벤트를 기록하지 않거나, 기록하더라도 모니터링하지 않는 경우입니다.
필수 로깅 항목:
import logging
import json
from datetime import datetime, timezone
security_logger = logging.getLogger('security')
def log_security_event(event_type, user_id, details):
event = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"event_type": event_type,
"user_id": user_id,
"ip_address": request.remote_addr,
"user_agent": request.headers.get('User-Agent'),
"details": details
}
security_logger.warning(json.dumps(event))
# 사용 예시
log_security_event("LOGIN_FAILED", "user123", "Invalid password attempt 3")
log_security_event("PRIVILEGE_ESCALATION", "user456", "Attempted admin access")
A10: Server-Side Request Forgery (SSRF)
서버가 공격자가 지정한 URL로 요청을 보내도록 유도하는 공격입니다.
방어법:
- 허용된 URL 화이트리스트 적용
- 내부 네트워크 IP 대역(10.x, 172.16.x, 192.168.x)으로의 요청 차단
- DNS Rebinding 방어
3. 인증(Authentication)
인증은 사용자가 자신이 주장하는 사람인지 확인하는 과정입니다.
비밀번호 해싱
비밀번호는 절대 평문이나 가역 암호화로 저장하면 안 됩니다.
# Argon2 - 현재 가장 권장되는 해싱 알고리즘
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # 반복 횟수
memory_cost=65536, # 64MB 메모리
parallelism=4, # 병렬 스레드 수
hash_len=32, # 해시 길이
salt_len=16 # 솔트 길이
)
# 해싱
hashed = ph.hash("user_password")
# 결과 예시: $argon2id$v=19$m=65536,t=3,p=4$...
# 검증
try:
ph.verify(hashed, "user_password")
print("비밀번호 일치")
except Exception:
print("비밀번호 불일치")
# bcrypt - 널리 사용되는 대안
import bcrypt
password = b"user_password"
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password, salt)
# 검증
if bcrypt.checkpw(password, hashed):
print("비밀번호 일치")
다중 인증(MFA)
비밀번호만으로는 충분하지 않습니다. 최소 두 가지 인증 요소를 조합합니다.
| 인증 요소 | 설명 | 예시 |
|---|---|---|
| 지식 (Something you know) | 사용자가 알고 있는 정보 | 비밀번호, PIN |
| 소유 (Something you have) | 사용자가 소유한 물건 | 스마트폰, 보안 키 |
| 생체 (Something you are) | 사용자의 신체 특징 | 지문, 얼굴 인식 |
# TOTP (Time-based One-Time Password) 구현
import pyotp
# 비밀키 생성
secret = pyotp.random_base32()
# QR 코드 URI 생성
totp_uri = pyotp.totp.TOTP(secret).provisioning_uri(
name="user@example.com",
issuer_name="MyApp"
)
# 검증
totp = pyotp.TOTP(secret)
is_valid = totp.verify("123456") # 사용자 입력 코드
패스키(WebAuthn/FIDO2)
비밀번호 없는 미래를 위한 인증 표준입니다.
- 공개키 암호화 기반으로 피싱에 강합니다
- 생체 인증 또는 PIN으로 로컬 인증 후, 서버에는 공개키만 전달됩니다
- Apple, Google, Microsoft 모두 지원합니다
// 패스키 등록 (브라우저 측)
const credential = await navigator.credentials.create({
publicKey: {
challenge: serverChallenge,
rp: { name: "MyApp", id: "myapp.com" },
user: {
id: userId,
name: "user@example.com",
displayName: "사용자"
},
pubKeyCredParams: [
{ alg: -7, type: "public-key" }, // ES256
{ alg: -257, type: "public-key" } // RS256
],
authenticatorSelection: {
authenticatorAttachment: "platform",
residentKey: "required",
userVerification: "required"
}
}
});
4. 인가(Authorization)
인가는 인증된 사용자가 어떤 리소스에 접근할 수 있는지 결정하는 과정입니다.
RBAC (역할 기반 접근 제어)
가장 보편적인 인가 모델입니다. 사용자에게 역할을 부여하고, 역할에 권한을 매핑합니다.
# RBAC 구현 예시
PERMISSIONS = {
"admin": ["read", "write", "delete", "manage_users"],
"editor": ["read", "write"],
"viewer": ["read"],
}
def check_permission(user_role, required_permission):
role_permissions = PERMISSIONS.get(user_role, [])
return required_permission in role_permissions
# 사용
if check_permission(current_user.role, "delete"):
delete_resource(resource_id)
else:
raise PermissionError("삭제 권한이 없습니다")
ABAC (속성 기반 접근 제어)
사용자 속성, 리소스 속성, 환경 속성을 기반으로 세밀한 접근 제어를 수행합니다.
# ABAC 정책 예시
def evaluate_policy(user, resource, action, environment):
# 근무 시간에만 접근 허용
if environment["hour"] < 9 or environment["hour"] > 18:
return False
# 같은 부서의 문서만 접근 가능
if user["department"] != resource["department"]:
return False
# 매니저는 모든 작업 가능, 일반 직원은 읽기만
if action == "write" and user["level"] < 3:
return False
return True
OAuth 2.0 스코프
OAuth 2.0에서 스코프는 클라이언트가 접근할 수 있는 리소스의 범위를 제한합니다.
# OAuth 2.0 스코프 정의 예시
scopes:
read:profile: "사용자 프로필 읽기"
write:profile: "사용자 프로필 수정"
read:orders: "주문 내역 조회"
admin:users: "사용자 관리 (관리자 전용)"
JWT 보안 모범 사례
JWT는 편리하지만, 잘못 사용하면 심각한 보안 문제가 발생합니다.
import jwt
from datetime import datetime, timedelta, timezone
# JWT 생성 (RS256 권장)
def create_token(user_id, role):
payload = {
"sub": user_id,
"role": role,
"iat": datetime.now(timezone.utc),
"exp": datetime.now(timezone.utc) + timedelta(minutes=15),
"iss": "myapp.com",
"aud": "myapp.com"
}
return jwt.encode(payload, PRIVATE_KEY, algorithm="RS256")
# JWT 검증
def verify_token(token):
try:
payload = jwt.decode(
token,
PUBLIC_KEY,
algorithms=["RS256"], # 알고리즘 명시적 지정 (none 공격 방지)
issuer="myapp.com",
audience="myapp.com"
)
return payload
except jwt.ExpiredSignatureError:
raise AuthError("토큰이 만료되었습니다")
except jwt.InvalidTokenError:
raise AuthError("유효하지 않은 토큰입니다")
JWT 보안 체크리스트:
- HS256 대신 RS256 사용 (비대칭 서명)
- 만료 시간(exp)을 짧게 설정 (15분 이내)
- Refresh Token은 서버 사이드에서 관리
- alg: none 공격을 방지하기 위해 알고리즘을 명시적으로 검증
5. 암호화 실전
대칭 암호화: AES-256-GCM
동일한 키로 암호화와 복호화를 수행합니다. 데이터 저장 시 주로 사용됩니다.
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
# 키 생성 (256비트)
key = AESGCM.generate_key(bit_length=256)
# 암호화
aesgcm = AESGCM(key)
nonce = os.urandom(12) # 96비트 논스
plaintext = b"Sensitive data here"
aad = b"additional authenticated data" # 추가 인증 데이터
ciphertext = aesgcm.encrypt(nonce, plaintext, aad)
# 복호화
decrypted = aesgcm.decrypt(nonce, ciphertext, aad)
비대칭 암호화: RSA와 ECDSA
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes, serialization
# ECDSA 키 쌍 생성
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
# 서명
from cryptography.hazmat.primitives.asymmetric import utils
signature = private_key.sign(
b"message to sign",
ec.ECDSA(hashes.SHA256())
)
# 검증
public_key.verify(
signature,
b"message to sign",
ec.ECDSA(hashes.SHA256())
)
TLS 설정 모범 사례
# Nginx TLS 설정
server {
listen 443 ssl http2;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# HSTS 헤더
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
}
키 관리와 HSM
- 키를 소스 코드에 하드코딩하면 안 됩니다
- AWS KMS, Google Cloud KMS, Azure Key Vault 같은 관리형 서비스를 사용합니다
- HSM(Hardware Security Module)은 키를 하드웨어 내부에서만 처리하여 추출을 방지합니다
- 키 로테이션 정책을 수립하고 자동화합니다
6. 시큐어 코딩
입력 검증
모든 외부 입력은 신뢰할 수 없습니다.
import re
from pydantic import BaseModel, validator, constr
class UserInput(BaseModel):
username: constr(min_length=3, max_length=30, pattern=r'^[a-zA-Z0-9_]+$')
email: str
age: int
@validator('email')
def validate_email(cls, v):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, v):
raise ValueError('유효하지 않은 이메일 형식입니다')
return v
@validator('age')
def validate_age(cls, v):
if v < 0 or v > 150:
raise ValueError('유효하지 않은 나이입니다')
return v
출력 인코딩 (XSS 방지)
# HTML 출력 시 이스케이프
from markupsafe import escape
user_input = '<script>alert("XSS")</script>'
safe_output = escape(user_input)
# 결과: <script>alert("XSS")</script>
파라미터화 쿼리
# SQLAlchemy ORM 사용
from sqlalchemy import text
# 안전한 방법
result = db.session.execute(
text("SELECT * FROM users WHERE email = :email"),
{"email": user_email}
)
CORS 설정
# Flask-CORS 설정
from flask_cors import CORS
CORS(app, resources={
r"/api/*": {
"origins": ["https://myapp.com", "https://admin.myapp.com"],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": ["Content-Type", "Authorization"],
"max_age": 86400
}
})
CSP (Content Security Policy)
# CSP 헤더 설정
@app.after_request
def set_csp(response):
response.headers['Content-Security-Policy'] = (
"default-src 'self'; "
"script-src 'self' 'nonce-abc123'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:; "
"connect-src 'self' https://api.myapp.com; "
"frame-ancestors 'none'; "
"base-uri 'self'"
)
return response
7. API 보안
Rate Limiting
# Flask-Limiter 사용
from flask_limiter import Limiter
limiter = Limiter(
app,
default_limits=["200 per day", "50 per hour"]
)
@app.route("/api/login", methods=["POST"])
@limiter.limit("5 per minute")
def login():
# 로그인 로직
pass
@app.route("/api/data")
@limiter.limit("100 per hour")
def get_data():
# 데이터 조회 로직
pass
API Key 관리
- API Key는 헤더로 전달합니다 (URL 파라미터에 넣지 않습니다)
- 키별 권한 범위(scope)를 제한합니다
- 키 발급/폐기 이력을 관리합니다
- 사용량을 모니터링하고 이상 패턴을 감지합니다
mTLS (상호 TLS 인증)
서버와 클라이언트 양쪽 모두 인증서를 검증하는 방식입니다.
# Kubernetes Ingress에서 mTLS 설정 예시
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret"
spec:
tls:
- hosts:
- api.myapp.com
secretName: tls-secret
HMAC 서명
요청의 무결성과 인증을 동시에 보장합니다.
import hmac
import hashlib
import time
def create_hmac_signature(secret_key, method, path, body, timestamp):
message = f"{method}\n{path}\n{timestamp}\n{body}"
signature = hmac.new(
secret_key.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
return signature
# 검증 (타임스탬프 체크 포함)
def verify_request(secret_key, method, path, body, timestamp, signature):
# 5분 이내의 요청만 허용 (Replay 공격 방지)
if abs(time.time() - float(timestamp)) > 300:
return False
expected = create_hmac_signature(secret_key, method, path, body, timestamp)
return hmac.compare_digest(expected, signature)
8. 컨테이너 보안
이미지 스캔
# Trivy로 컨테이너 이미지 취약점 스캔
trivy image myapp:latest
# 심각도별 필터링
trivy image --severity HIGH,CRITICAL myapp:latest
# CI/CD에서 사용 (취약점 발견 시 빌드 실패)
trivy image --exit-code 1 --severity CRITICAL myapp:latest
최소 권한 Dockerfile
# 멀티 스테이지 빌드
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# 프로덕션 이미지
FROM node:20-alpine
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
# 루트가 아닌 사용자로 실행
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]
Rootless 컨테이너
- 루트 권한 없이 컨테이너를 실행합니다
- securityContext에서 runAsNonRoot, readOnlyRootFilesystem을 설정합니다
- 불필요한 Linux Capabilities를 제거합니다
# Kubernetes Pod Security Context
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
OPA/Gatekeeper
Kubernetes 클러스터의 정책을 코드로 관리합니다.
# 특권 컨테이너 차단 정책
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
name: deny-privileged
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-system
9. DevSecOps 파이프라인
전체 파이프라인 개요
DevSecOps는 개발(Dev), 보안(Sec), 운영(Ops)을 통합하는 접근법입니다.
| 단계 | 도구 | 목적 |
|---|---|---|
| 코드 작성 | IDE 보안 플러그인 | 실시간 취약점 감지 |
| 커밋 | Pre-commit hooks | 비밀키 유출 방지 |
| 빌드 | SAST | 정적 코드 분석 |
| 테스트 | DAST | 동적 보안 테스트 |
| 의존성 | SCA | 서드파티 라이브러리 취약점 |
| 배포 | 이미지 스캔 | 컨테이너 취약점 |
| 운영 | RASP, WAF | 런타임 보안 |
SAST (Static Application Security Testing)
# GitHub Actions에서 SonarQube 실행
name: Security Scan
on: [push, pull_request]
jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: SONAR_TOKEN_PLACEHOLDER
SONAR_HOST_URL: SONAR_HOST_PLACEHOLDER
DAST (Dynamic Application Security Testing)
# OWASP ZAP을 사용한 동적 보안 스캔
docker run -t zaproxy/zap-stable zap-baseline.py \
-t https://myapp.com \
-r report.html
SCA (Software Composition Analysis)
# Dependabot 설정 (.github/dependabot.yml)
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
SBOM (Software Bill of Materials)
# Syft로 SBOM 생성
syft myapp:latest -o spdx-json > sbom.json
# Grype로 SBOM 기반 취약점 스캔
grype sbom:sbom.json
Pre-commit에서 비밀키 감지
# .pre-commit-config.yaml
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
- repo: https://github.com/zricethezav/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
10. 침투 테스트 기초
모의해킹 방법론
OWASP Testing Guide 기반의 테스트 단계:
- 정보 수집 (Reconnaissance): 대상 시스템의 기술 스택, 네트워크 구조 파악
- 매핑 (Mapping): 공격 표면(Attack Surface) 식별
- 취약점 발견 (Discovery): 자동화 도구와 수동 테스트 병행
- 취약점 검증 (Exploitation): 발견된 취약점의 실제 위험도 검증
- 보고 (Reporting): 기술적 상세 내용과 비즈니스 영향도 보고
유용한 도구들
| 도구 | 용도 |
|---|---|
| Burp Suite | 웹 프록시, 취약점 스캐너 |
| Nmap | 네트워크 스캔 |
| sqlmap | SQL Injection 자동화 |
| Nuclei | 템플릿 기반 취약점 스캔 |
| Metasploit | 침투 테스트 프레임워크 |
버그 바운티 프로그램
자사 서비스의 취약점을 외부 보안 연구자가 발견하도록 보상하는 프로그램입니다.
- HackerOne: 세계 최대의 버그 바운티 플랫폼
- Bugcrowd: 크라우드소싱 보안 테스트
- 자체 운영: 명확한 범위(scope)와 보상 기준 설정이 중요합니다
CTF (Capture The Flag) 추천
보안 실력을 키울 수 있는 CTF 플랫폼입니다.
- OverTheWire: 기초부터 고급까지 단계별 워게임
- Hack The Box: 실전형 침투 테스트 환경
- PicoCTF: 초보자에게 적합한 교육용 CTF
- PortSwigger Web Security Academy: 웹 보안 무료 학습 플랫폼
11. 인시던트 대응
보안 사고 대응 절차 (NIST SP 800-61)
1단계: 준비 (Preparation)
- 인시던트 대응 팀(IRT) 구성
- 대응 계획 수립 및 훈련
- 연락 체계와 에스컬레이션 절차 정의
2단계: 탐지 및 분석 (Detection and Analysis)
- SIEM(Security Information and Event Management)으로 이상 탐지
- 로그 분석 및 IoC(Indicators of Compromise) 식별
- 사고의 범위와 영향도 파악
3단계: 억제, 근절, 복구 (Containment, Eradication, Recovery)
# 비상 대응 예시: 유출된 API 키 무효화
# 1. 영향받은 키 즉시 폐기
aws iam delete-access-key --user-name compromised-user --access-key-id AKIA...
# 2. 관련 세션 모두 무효화
aws iam put-user-policy --user-name compromised-user \
--policy-name DenyAll --policy-document file://deny-all.json
# 3. 새 인증 정보 발급 후 안전하게 배포
4단계: 사후 활동 (Post-Incident Activity)
- 사후 보고서(Post-mortem) 작성
- 타임라인 재구성
- 근본 원인 분석 (Root Cause Analysis)
- 재발 방지 대책 수립 및 이행
디지털 포렌식 기초
- 증거 보전의 원칙: 원본을 훼손하지 않고 사본으로 분석합니다
- Chain of Custody: 증거의 취급 이력을 문서화합니다
- 메모리 덤프, 디스크 이미징, 네트워크 트래픽 캡처 등 수집 방법을 이해합니다
- 주요 도구: Volatility (메모리 분석), Autopsy (디스크 분석), Wireshark (네트워크 분석)
보안 사고 보고서 구조
좋은 보안 사고 보고서는 다음 항목을 포함합니다.
- 사고 개요: 발생 일시, 유형, 영향 범위
- 타임라인: 최초 침투부터 발견까지의 시간순 기록
- 기술적 분석: 공격 벡터, 악용된 취약점, 공격자의 행위
- 영향 분석: 유출된 데이터의 종류와 규모, 비즈니스 영향
- 대응 조치: 취한 조치와 효과
- 재발 방지: 단기/장기 개선 사항
- 교훈: 대응 과정에서의 개선점
마무리
애플리케이션 보안은 단일 도구나 프로세스가 아닌, 문화이자 여정입니다. 핵심 원칙을 정리합니다.
- 심층 방어 (Defense in Depth): 하나의 보안 계층이 실패해도 다른 계층이 방어합니다
- 최소 권한 (Least Privilege): 필요한 최소한의 권한만 부여합니다
- 실패 안전 (Fail Secure): 시스템 오류 시 보안이 유지되는 방향으로 설계합니다
- 제로 트러스트 (Zero Trust): 네트워크 위치에 관계없이 모든 요청을 검증합니다
- 보안은 문화: 모든 팀원이 보안 의식을 가지고, 지속적으로 학습합니다
보안 위협은 끊임없이 진화합니다. 이 가이드를 출발점으로 삼아 지속적인 학습과 실무 적용을 병행하시기 바랍니다.
퀴즈: 애플리케이션 보안 지식 점검
Q1. OWASP Top 10 (2021)에서 1위를 차지한 취약점은 무엇인가요?
A: Broken Access Control (접근 제어 실패). 인가되지 않은 사용자가 다른 사용자의 데이터에 접근하거나 관리자 기능을 수행할 수 있는 취약점입니다.
Q2. 비밀번호 해싱에 MD5를 사용하면 안 되는 이유는?
A: MD5는 속도가 너무 빠르기 때문에 무차별 대입 공격(Brute-force)에 취약합니다. bcrypt나 Argon2처럼 의도적으로 느리게 설계된 알고리즘을 사용해야 합니다.
Q3. JWT에서 alg: none 공격을 방지하려면 어떻게 해야 하나요?
A: 토큰 검증 시 허용할 알고리즘을 명시적으로 지정해야 합니다. 라이브러리의 기본 설정에 의존하지 않고 algorithms=["RS256"]처럼 구체적으로 지정합니다.
Q4. SAST와 DAST의 차이점은 무엇인가요?
A: SAST(Static)는 소스 코드를 실행 없이 분석하여 취약점을 찾고, DAST(Dynamic)는 실행 중인 애플리케이션을 대상으로 외부에서 공격을 시뮬레이션합니다. 둘 다 상호 보완적으로 사용해야 합니다.
Q5. SSRF 공격을 방어하는 가장 효과적인 방법은?
A: 허용된 URL의 화이트리스트를 적용하고, 내부 네트워크 IP 대역(10.x, 172.16.x, 192.168.x, 127.0.0.1)으로의 요청을 차단합니다. DNS Rebinding 공격도 함께 방어해야 합니다.
현재 단락 (1/506)
소프트웨어가 비즈니스의 핵심이 된 시대, 보안은 선택이 아니라 생존의 문제입니다. 이 가이드는 OWASP Top 10부터 DevSecOps 파이프라인까지 애플리케이션 보안의 전 범...