Split View: 애플리케이션 보안 엔지니어링 가이드 — OWASP Top 10, 인증, 암호화, DevSecOps
애플리케이션 보안 엔지니어링 가이드 — OWASP Top 10, 인증, 암호화, DevSecOps
애플리케이션 보안 엔지니어링 가이드
소프트웨어가 비즈니스의 핵심이 된 시대, 보안은 선택이 아니라 생존의 문제입니다. 이 가이드는 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 공격도 함께 방어해야 합니다.
Application Security Engineering Guide — OWASP Top 10, Authentication, Encryption, DevSecOps
Application Security Engineering Guide
In an era where software is at the heart of business, security is not a choice but a matter of survival. This guide covers the full spectrum of application security, from OWASP Top 10 to DevSecOps pipelines.
1. Why Security Matters
The Reality of Data Breaches
According to IBM's "Cost of a Data Breach Report," the average cost of a single data breach in 2024 was approximately 4.88 million dollars. The leading causes of breaches are as follows:
| Cause | Percentage | Average Cost |
|---|---|---|
| Phishing attacks | 16% | 4.76M |
| Stolen/compromised credentials | 15% | 4.63M |
| Cloud misconfiguration | 12% | 4.14M |
| Zero-day vulnerabilities | 10% | 5.18M |
Developer Responsibility for Security
Security is no longer solely the responsibility of the security team. Under the "Shift Left" paradigm, security must be embedded from the earliest stages of development.
- Preventing vulnerabilities during the coding phase is 6 to 100 times cheaper than patching after deployment
- Security must be considered throughout the entire SDLC (Software Development Life Cycle)
- Security is not a feature; it is part of quality
The CIA Triad
- Confidentiality: Only authorized users can access the data
- Integrity: Data has not been tampered with without authorization
- Availability: Systems and data are accessible when needed
2. OWASP Top 10 (2021)
The OWASP Top 10 is a catalog of the ten most critical security vulnerabilities in web applications. Let us examine each vulnerability and its defenses.
A01: Broken Access Control
The most common vulnerability. Unauthorized users can access other users' data or perform administrative functions.
Attack Scenario: Changing URL parameters to view another user's order history.
Defenses:
# Access control decorator in 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()
- Default-deny: deny all access and explicitly allow via whitelist
- Always validate permissions on the server side
- Use UUIDs to prevent IDOR (Insecure Direct Object References)
A02: Cryptographic Failures
Sensitive data is transmitted in plaintext or protected with weak cryptography.
Defenses:
- Data in transit: use TLS 1.3
- Data at rest: encrypt with AES-256-GCM
- Passwords: hash with bcrypt or Argon2
- Never use weak algorithms such as MD5 or SHA-1
A03: Injection
Injection attacks targeting SQL, NoSQL, OS commands, LDAP, and more.
Vulnerable code:
# Never do this - vulnerable to SQL Injection
query = "SELECT * FROM users WHERE id = " + user_input
Safe code:
# Use parameterized queries
cursor.execute("SELECT * FROM users WHERE id = %s", (user_input,))
# Use an ORM (SQLAlchemy)
user = User.query.filter_by(id=user_input).first()
A04: Insecure Design
Security flaws at the design stage. These are architecture-level issues, not code-level bugs.
Defenses:
- Perform Threat Modeling (STRIDE, PASTA methodologies)
- Apply secure design principles (least privilege, defense in depth, fail secure)
- Test business logic against security scenarios
A05: Security Misconfiguration
This includes unchanged default accounts, unnecessary features left enabled, and debug mode exposed in production.
Checklist:
- Have default passwords been changed
- Are unnecessary ports and services disabled
- Do error messages avoid exposing stack traces
- Are security headers (X-Frame-Options, X-Content-Type-Options) configured
A06: Vulnerable and Outdated Components
Using libraries or frameworks with known vulnerabilities.
Defenses:
# npm security audit
npm audit
# Python dependency audit
pip-audit
# Go vulnerability check
govulncheck ./...
A07: Identification and Authentication Failures
Weak password policies, poor session management, and brute-force attacks left unchecked. Covered in detail in Chapter 3.
A08: Software and Data Integrity Failures
Lack of integrity verification in CI/CD pipelines, software updates, and serialization.
Defenses:
- Verify software integrity via digital signatures
- Manage SBOM (Software Bill of Materials)
- Secure the CI/CD pipeline (code signing, access control)
A09: Security Logging and Monitoring Failures
Security events are not recorded, or if they are, they are not monitored.
Essential logging items:
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))
# Usage
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)
An attack that tricks the server into sending requests to attacker-specified URLs.
Defenses:
- Apply an allowed URL whitelist
- Block requests to internal network IP ranges (10.x, 172.16.x, 192.168.x)
- Defend against DNS Rebinding
3. Authentication
Authentication is the process of verifying that a user is who they claim to be.
Password Hashing
Passwords must never be stored in plaintext or with reversible encryption.
# Argon2 - currently the most recommended hashing algorithm
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # iterations
memory_cost=65536, # 64MB memory
parallelism=4, # parallel threads
hash_len=32, # hash length
salt_len=16 # salt length
)
# Hashing
hashed = ph.hash("user_password")
# Example output: $argon2id$v=19$m=65536,t=3,p=4$...
# Verification
try:
ph.verify(hashed, "user_password")
print("Password matches")
except Exception:
print("Password does not match")
# bcrypt - a widely used alternative
import bcrypt
password = b"user_password"
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password, salt)
# Verification
if bcrypt.checkpw(password, hashed):
print("Password matches")
Multi-Factor Authentication (MFA)
Passwords alone are not enough. Combine at least two authentication factors.
| Factor | Description | Examples |
|---|---|---|
| Knowledge (Something you know) | Information the user knows | Password, PIN |
| Possession (Something you have) | An object the user possesses | Smartphone, security key |
| Biometric (Something you are) | Physical characteristics of the user | Fingerprint, face recognition |
# TOTP (Time-based One-Time Password) implementation
import pyotp
# Generate secret key
secret = pyotp.random_base32()
# Generate QR code URI
totp_uri = pyotp.totp.TOTP(secret).provisioning_uri(
name="user@example.com",
issuer_name="MyApp"
)
# Verification
totp = pyotp.TOTP(secret)
is_valid = totp.verify("123456") # user-entered code
Passkeys (WebAuthn/FIDO2)
An authentication standard for a password-free future.
- Based on public-key cryptography, making it resistant to phishing
- After local authentication via biometrics or PIN, only the public key is sent to the server
- Supported by Apple, Google, and Microsoft
// Passkey registration (browser side)
const credential = await navigator.credentials.create({
publicKey: {
challenge: serverChallenge,
rp: { name: "MyApp", id: "myapp.com" },
user: {
id: userId,
name: "user@example.com",
displayName: "User"
},
pubKeyCredParams: [
{ alg: -7, type: "public-key" }, // ES256
{ alg: -257, type: "public-key" } // RS256
],
authenticatorSelection: {
authenticatorAttachment: "platform",
residentKey: "required",
userVerification: "required"
}
}
});
4. Authorization
Authorization determines which resources an authenticated user can access.
RBAC (Role-Based Access Control)
The most widely used authorization model. Assign roles to users and map permissions to roles.
# RBAC implementation example
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
# Usage
if check_permission(current_user.role, "delete"):
delete_resource(resource_id)
else:
raise PermissionError("You do not have delete permissions")
ABAC (Attribute-Based Access Control)
Provides fine-grained access control based on user attributes, resource attributes, and environmental attributes.
# ABAC policy example
def evaluate_policy(user, resource, action, environment):
# Allow access only during business hours
if environment["hour"] < 9 or environment["hour"] > 18:
return False
# Only allow access to documents in the same department
if user["department"] != resource["department"]:
return False
# Managers can perform all actions, regular employees can only read
if action == "write" and user["level"] < 3:
return False
return True
OAuth 2.0 Scopes
In OAuth 2.0, scopes limit the range of resources a client can access.
# OAuth 2.0 scope definition example
scopes:
read:profile: "Read user profile"
write:profile: "Update user profile"
read:orders: "View order history"
admin:users: "User management (admin only)"
JWT Security Best Practices
JWT is convenient but can cause serious security issues if misused.
import jwt
from datetime import datetime, timedelta, timezone
# JWT creation (RS256 recommended)
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 verification
def verify_token(token):
try:
payload = jwt.decode(
token,
PUBLIC_KEY,
algorithms=["RS256"], # Explicitly specify algorithm (prevent none attack)
issuer="myapp.com",
audience="myapp.com"
)
return payload
except jwt.ExpiredSignatureError:
raise AuthError("Token has expired")
except jwt.InvalidTokenError:
raise AuthError("Invalid token")
JWT Security Checklist:
- Use RS256 instead of HS256 (asymmetric signing)
- Set short expiration times (under 15 minutes)
- Manage Refresh Tokens on the server side
- Explicitly validate the algorithm to prevent alg: none attacks
5. Encryption in Practice
Symmetric Encryption: AES-256-GCM
Uses the same key for encryption and decryption. Primarily used for data at rest.
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
# Key generation (256-bit)
key = AESGCM.generate_key(bit_length=256)
# Encryption
aesgcm = AESGCM(key)
nonce = os.urandom(12) # 96-bit nonce
plaintext = b"Sensitive data here"
aad = b"additional authenticated data"
ciphertext = aesgcm.encrypt(nonce, plaintext, aad)
# Decryption
decrypted = aesgcm.decrypt(nonce, ciphertext, aad)
Asymmetric Encryption: RSA and ECDSA
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes, serialization
# ECDSA key pair generation
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
# Signing
from cryptography.hazmat.primitives.asymmetric import utils
signature = private_key.sign(
b"message to sign",
ec.ECDSA(hashes.SHA256())
)
# Verification
public_key.verify(
signature,
b"message to sign",
ec.ECDSA(hashes.SHA256())
)
TLS Configuration Best Practices
# Nginx TLS configuration
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 header
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
}
Key Management and HSM
- Never hardcode keys in source code
- Use managed services like AWS KMS, Google Cloud KMS, or Azure Key Vault
- HSM (Hardware Security Module) processes keys within the hardware to prevent extraction
- Establish and automate key rotation policies
6. Secure Coding
Input Validation
All external input is untrusted.
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('Invalid email format')
return v
@validator('age')
def validate_age(cls, v):
if v < 0 or v > 150:
raise ValueError('Invalid age')
return v
Output Encoding (XSS Prevention)
# Escape HTML output
from markupsafe import escape
user_input = '<script>alert("XSS")</script>'
safe_output = escape(user_input)
# Result: <script>alert("XSS")</script>
Parameterized Queries
# Using SQLAlchemy ORM
from sqlalchemy import text
# Safe approach
result = db.session.execute(
text("SELECT * FROM users WHERE email = :email"),
{"email": user_email}
)
CORS Configuration
# Flask-CORS configuration
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 header configuration
@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 Security
Rate Limiting
# Using 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():
# login logic
pass
@app.route("/api/data")
@limiter.limit("100 per hour")
def get_data():
# data retrieval logic
pass
API Key Management
- Pass API keys via headers (never in URL parameters)
- Limit the permission scope per key
- Maintain a history of key issuance and revocation
- Monitor usage and detect anomalous patterns
mTLS (Mutual TLS Authentication)
A mechanism where both server and client verify each other's certificates.
# mTLS configuration on Kubernetes Ingress
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 Signing
Guarantees both integrity and authentication of requests simultaneously.
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
# Verification (with timestamp check)
def verify_request(secret_key, method, path, body, timestamp, signature):
# Only allow requests within 5 minutes (replay attack prevention)
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. Container Security
Image Scanning
# Scan container image vulnerabilities with Trivy
trivy image myapp:latest
# Filter by severity
trivy image --severity HIGH,CRITICAL myapp:latest
# Use in CI/CD (fail build if vulnerabilities found)
trivy image --exit-code 1 --severity CRITICAL myapp:latest
Minimal-Privilege Dockerfile
# Multi-stage build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Production image
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
# Run as non-root user
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]
Rootless Containers
- Run containers without root privileges
- Set runAsNonRoot and readOnlyRootFilesystem in securityContext
- Drop unnecessary 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
Manage Kubernetes cluster policies as code.
# Policy to deny privileged containers
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
name: deny-privileged
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-system
9. DevSecOps Pipeline
Pipeline Overview
DevSecOps is an approach that integrates Development (Dev), Security (Sec), and Operations (Ops).
| Phase | Tool | Purpose |
|---|---|---|
| Coding | IDE security plugins | Real-time vulnerability detection |
| Commit | Pre-commit hooks | Prevent secret leaks |
| Build | SAST | Static code analysis |
| Test | DAST | Dynamic security testing |
| Dependencies | SCA | Third-party library vulnerabilities |
| Deploy | Image scanning | Container vulnerabilities |
| Operations | RASP, WAF | Runtime security |
SAST (Static Application Security Testing)
# Run SonarQube in GitHub Actions
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)
# Dynamic security scanning with OWASP ZAP
docker run -t zaproxy/zap-stable zap-baseline.py \
-t https://myapp.com \
-r report.html
SCA (Software Composition Analysis)
# Dependabot configuration (.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)
# Generate SBOM with Syft
syft myapp:latest -o spdx-json > sbom.json
# Scan vulnerabilities based on SBOM with Grype
grype sbom:sbom.json
Secret Detection in 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. Penetration Testing Fundamentals
Penetration Testing Methodology
Testing phases based on the OWASP Testing Guide:
- Reconnaissance: Identify the target system's tech stack and network architecture
- Mapping: Identify the Attack Surface
- Discovery: Combine automated tools and manual testing
- Exploitation: Verify the real-world risk of discovered vulnerabilities
- Reporting: Report technical details and business impact
Useful Tools
| Tool | Purpose |
|---|---|
| Burp Suite | Web proxy, vulnerability scanner |
| Nmap | Network scanning |
| sqlmap | SQL Injection automation |
| Nuclei | Template-based vulnerability scanning |
| Metasploit | Penetration testing framework |
Bug Bounty Programs
Programs that reward external security researchers for finding vulnerabilities in your services.
- HackerOne: The world's largest bug bounty platform
- Bugcrowd: Crowdsourced security testing
- Self-managed: Clearly defining scope and reward criteria is essential
CTF (Capture The Flag) Recommendations
CTF platforms for building security skills:
- OverTheWire: Step-by-step wargames from beginner to advanced
- Hack The Box: Hands-on penetration testing environments
- PicoCTF: Educational CTF suitable for beginners
- PortSwigger Web Security Academy: Free web security learning platform
11. Incident Response
Incident Response Procedure (NIST SP 800-61)
Phase 1: Preparation
- Form an Incident Response Team (IRT)
- Develop and rehearse a response plan
- Define communication channels and escalation procedures
Phase 2: Detection and Analysis
- Detect anomalies via SIEM (Security Information and Event Management)
- Analyze logs and identify IoCs (Indicators of Compromise)
- Determine the scope and impact of the incident
Phase 3: Containment, Eradication, Recovery
# Emergency response example: revoke leaked API keys
# 1. Immediately revoke affected keys
aws iam delete-access-key --user-name compromised-user --access-key-id AKIA...
# 2. Invalidate all related sessions
aws iam put-user-policy --user-name compromised-user \
--policy-name DenyAll --policy-document file://deny-all.json
# 3. Issue new credentials and distribute securely
Phase 4: Post-Incident Activity
- Write a post-mortem report
- Reconstruct the timeline
- Perform Root Cause Analysis
- Develop and implement measures to prevent recurrence
Digital Forensics Basics
- Principle of evidence preservation: analyze copies, never the originals
- Chain of Custody: document the handling history of all evidence
- Understand collection methods: memory dumps, disk imaging, network traffic capture
- Key tools: Volatility (memory analysis), Autopsy (disk analysis), Wireshark (network analysis)
Incident Report Structure
A thorough incident report includes the following:
- Incident Overview: date and time, type, and impact scope
- Timeline: chronological record from initial breach to discovery
- Technical Analysis: attack vector, exploited vulnerabilities, and attacker behavior
- Impact Analysis: type and volume of compromised data, business impact
- Response Actions: measures taken and their effectiveness
- Prevention: short-term and long-term improvements
- Lessons Learned: improvements identified during the response process
Conclusion
Application security is not a single tool or process. It is a culture and a journey. Here are the core principles:
- Defense in Depth: If one security layer fails, other layers continue to defend
- Least Privilege: Grant only the minimum permissions necessary
- Fail Secure: Design systems so that failures maintain security
- Zero Trust: Verify every request regardless of network location
- Security is Culture: Every team member maintains security awareness and continues learning
Security threats evolve constantly. Use this guide as a starting point and combine continuous learning with practical application.
Quiz: Application Security Knowledge Check
Q1. Which vulnerability ranked first in OWASP Top 10 (2021)?
A: Broken Access Control. A vulnerability where unauthorized users can access other users' data or perform administrative functions.
Q2. Why should you not use MD5 for password hashing?
A: MD5 is too fast, making it vulnerable to brute-force attacks. Use algorithms intentionally designed to be slow, such as bcrypt or Argon2.
Q3. How can you prevent the alg: none attack in JWT?
A: Explicitly specify the allowed algorithms during token verification. Do not rely on library defaults; explicitly specify algorithms=["RS256"].
Q4. What is the difference between SAST and DAST?
A: SAST (Static) analyzes source code without executing it to find vulnerabilities. DAST (Dynamic) simulates attacks from the outside against a running application. Both should be used complementarily.
Q5. What is the most effective way to defend against SSRF attacks?
A: Apply a whitelist of allowed URLs and block requests to internal network IP ranges (10.x, 172.16.x, 192.168.x, 127.0.0.1). DNS Rebinding attacks should also be defended against.