OWASP Top 10:2025A01~A10 완전 정리

Phase 2  |  Application Security

OWASP Top 10:2025
A01~A10 완전 정리

취약한 코드(bad.py) vs 안전한 코드(good.py) 비교로 배우는 웹 보안

Python 3.11 · 직접 실행 가능한 예제 코드

▶ GitHub: bird8696/owasp-study
00 OWASP Top 10:2025 한눈에 보기
ID취약점핵심 키워드
A01Broken Access ControlIDOR, 인가 검증 누락
A02Security Misconfigurationdebug=True, 하드코딩 시크릿
A03Supply Chain Failuresyaml.load(), 버전 미고정
A04Cryptographic FailuresMD5/평문 vs bcrypt
A05InjectionSQLi, 파라미터 바인딩
A06Insecure Design브루트포스 방어 없음
A07Identification & Auth Failures예측 가능한 세션 토큰
A08Data Integrity Failurespickle 역직렬화, HMAC
A09Logging Failures로그 없음, 민감정보 로그
A10System Integrity Failures예외 무시, 롤백 없음
2025 버전 변경 포인트  |  2021 대비 Supply Chain(A03)이 신규 진입. XSS는 A05 Injection 하위로 통합. 취약한 컴포넌트(SCA)는 A03 Supply Chain으로 재분류. 인증 실패(A07)는 Identification 개념 강화.
A01 Broken Access Control — 취약한 접근 제어
A01:2025
IDOR (Insecure Direct Object Reference)인가 검증 없이 ID만으로 타인 리소스 접근 가능
CRITICAL
URL 파라미터의 ID 값만 바꾸면 다른 사용자의 데이터를 조회/수정할 수 있는 취약점. 서버가 "이 리소스가 요청자 것인지" 확인하지 않는 것이 원인이다.
✗ bad.py
# 요청자가 누구든 ID만 맞으면 반환
@app.get("/user/{user_id}")
def get_user(user_id: int):
    return db.query(user_id)  # 인가 검증 없음
✓ good.py
# 본인 리소스만 접근 허용
@app.get("/user/{user_id}")
def get_user(user_id: int, me=Depends(get_current_user)):
    if me.id != user_id:
        raise HTTPException(403)
    return db.query(user_id)
방어 핵심 : 리소스 접근 시 항상 "요청자 == 소유자" 검증. 관리자 기능은 Role 기반 별도 확인.
A02 Security Misconfiguration — 보안 설정 오류
A02:2025
Debug 모드 활성화 & 하드코딩 시크릿개발 설정이 운영 환경에 그대로 노출
HIGH
debug=True 상태로 배포하면 스택 트레이스, 설정 정보, DB 쿼리가 외부에 노출된다. 시크릿 키를 소스코드에 하드코딩하면 GitHub에 올리는 순간 유출된다.
✗ bad.py
SECRET_KEY = "super-secret-123"
DEBUG = True
app.run(debug=True)  # 운영에 그대로
✓ good.py
import os
SECRET_KEY = os.environ["SECRET_KEY"]
DEBUG = os.getenv("DEBUG", "false") == "true"
# .env 또는 Secrets Manager 사용
방어 핵심 : 환경 변수 또는 Secrets Manager로 분리. .env는 반드시 .gitignore에 추가. 운영 배포 시 debug=False 강제 확인.
A03 Supply Chain Failures — 소프트웨어 공급망 취약점
A03:2025
yaml.load() & 버전 미고정서드파티 라이브러리를 통한 코드 실행
HIGH
yaml.load()는 임의 Python 객체를 역직렬화할 수 있어 RCE(원격 코드 실행)로 이어진다. 버전을 고정하지 않으면 악성 업데이트가 자동 적용될 수 있다.
✗ bad.py
import yaml
data = yaml.load(user_input)  # RCE 가능
# requirements.txt: requests (버전 없음)
✓ good.py
import yaml
data = yaml.safe_load(user_input)  # 안전
# requirements.txt: requests==2.31.0
방어 핵심 : yaml.safe_load() 사용. pip freeze로 버전 고정. pip audit / Dependabot으로 취약 패키지 자동 감지.
A04 Cryptographic Failures — 암호화 실패
A04:2025
MD5 / 평문 저장 vs bcrypt취약한 해시 알고리즘으로 비밀번호 저장
HIGH
MD5, SHA1은 GPU로 초당 수십억 번 해시 계산이 가능해 레인보우 테이블 공격에 취약하다. bcrypt/argon2는 salt + 반복 연산으로 무차별 대입을 어렵게 만든다.
✗ bad.py
import hashlib
pw_hash = hashlib.md5(password.encode()).hexdigest()
# 또는 DB에 평문 저장
✓ good.py
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
bcrypt.checkpw(password.encode(), hashed)  # 검증
방어 핵심 : 비밀번호는 bcrypt / argon2 / scrypt 사용. MD5/SHA1은 비밀번호 저장에 절대 사용 금지. 전송 중 데이터는 TLS 필수.
A05 Injection — 인젝션
A05:2025
SQL Injection — 파라미터 바인딩사용자 입력이 SQL 쿼리에 직접 삽입
CRITICAL
사용자 입력을 검증 없이 쿼리에 문자열 연결하면 공격자가 SQL 구문을 주입해 DB 전체를 읽거나 삭제할 수 있다. XSS, Command Injection도 같은 원리다.
✗ bad.py
query = f"SELECT * FROM users WHERE id={user_id}"
# user_id = "1 OR 1=1" → 전체 조회
cursor.execute(query)
✓ good.py
query = "SELECT * FROM users WHERE id = ?"
cursor.execute(query, (user_id,))  # 파라미터 바인딩
# ORM 사용 시 자동 처리
방어 핵심 : 파라미터 바인딩(Prepared Statement) 또는 ORM 사용. 사용자 입력은 절대 쿼리에 직접 연결 금지. WAF로 2차 방어.
A06 Insecure Design — 안전하지 않은 설계
A06:2025
브루트포스 방어 없음로그인 실패 횟수 제한 미구현
MEDIUM
로그인 실패 횟수 제한이 없으면 공격자가 수백만 개의 비밀번호를 자동으로 시도할 수 있다. 설계 단계에서 고려되지 않으면 나중에 추가하기 어렵다.
✗ bad.py
@app.post("/login")
def login(creds):
    user = db.get(creds.username)
    if user.password == creds.password:
        return {"token": create_token(user)}
✓ good.py
fail_count = redis.get(f"fail:{ip}")
if int(fail_count or 0) >= 5:
    raise HTTPException(429, "Too Many Requests")
# 실패 시 redis.incr() + TTL 설정
방어 핵심 : IP 기반 실패 횟수 제한 + 잠금. CAPTCHA 연동. 계정 잠금 시 이메일 알림. 설계 단계 Threat Modeling 수행.
A07 Identification & Auth Failures — 인증 실패
A07:2025
예측 가능한 세션 토큰순차적/짧은 토큰으로 세션 탈취 가능
HIGH
세션 ID가 순차적이거나 짧으면 공격자가 유효한 세션을 추측할 수 있다. JWT를 사용하더라도 서명 검증을 생략하거나 alg:none을 허용하면 무효화된다.
✗ bad.py
session_id = str(user.id) + "_session"
# 예: "42_session" → 순차 예측 가능
token = jwt.encode(payload, alg="none")
✓ good.py
import secrets
session_id = secrets.token_urlsafe(32)  # 암호학적 난수
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
방어 핵심 : 세션 ID는 secrets.token_urlsafe(32) 이상 사용. JWT는 alg 명시 + 서명 검증 필수. 로그인 성공 시 세션 재생성.
A08 Data Integrity Failures — 데이터 무결성 실패
A08:2025
pickle 역직렬화 & HMAC 미검증신뢰할 수 없는 데이터를 검증 없이 역직렬화
HIGH
pickle은 역직렬화 시 임의 코드를 실행할 수 있어 외부 입력에 절대 사용하면 안 된다. 데이터 무결성 검증 없이 처리하면 변조된 데이터가 그대로 실행된다.
✗ bad.py
import pickle
data = pickle.loads(request.body)  # RCE 가능
# 무결성 검증 없이 바로 처리
✓ good.py
import hmac, json
sig = hmac.new(SECRET.encode(), body, "sha256").hexdigest()
if not hmac.compare_digest(sig, request.headers["X-Sig"]):
    raise HTTPException(400)
data = json.loads(body)  # pickle 대신 JSON
방어 핵심 : pickle 사용 금지 — JSON 또는 protobuf 대체. 외부 데이터는 HMAC으로 서명 검증 후 처리. CI/CD 파이프라인 무결성 검증(코드 서명).
A09 Logging Failures — 로깅 실패
A09:2025
로그 없음 & 민감정보 로그 노출공격 탐지 불가 또는 로그 자체가 취약점
MEDIUM
로그가 없으면 침해사고 후 원인을 파악할 수 없다. 반대로 비밀번호, 토큰, 개인정보를 로그에 그대로 기록하면 로그 파일 자체가 공격 대상이 된다.
✗ bad.py
# 로그 없이 처리 — 공격 흔적 남지 않음
logger.info(f"login: {username} pw={password}")
# 비밀번호가 로그에 평문 기록
✓ good.py
logger.warning("login_failed", extra={
    "username": username, "ip": client_ip,
    "timestamp": datetime.utcnow().isoformat()
})  # 비밀번호는 절대 로그 X
방어 핵심 : 인증 성공/실패, 권한 오류, 예외는 반드시 로그 기록. 비밀번호/토큰/카드번호는 로그 제외. SIEM 연동으로 이상 패턴 자동 탐지.
A10 System Integrity Failures — 시스템 무결성 실패
A10:2025
예외 무시 & 롤백 없음오류 발생 시 시스템이 불일치 상태로 방치
MEDIUM
예외를 무시하거나 DB 트랜잭션 롤백을 구현하지 않으면 일부만 처리된 상태가 지속된다. 금융 거래에서 롤백 없이 오류가 발생하면 데이터 불일치가 생긴다.
✗ bad.py
try:
    db.execute("UPDATE balance SET amount=amount-100")
    external_api.pay()  # 실패해도 DB는 이미 차감됨
except:
    pass  # 예외 무시
✓ good.py
try:
    with db.transaction():
        db.execute("UPDATE balance SET amount=amount-100")
        external_api.pay()
except Exception as e:
    db.rollback()
    logger.error("payment_failed", exc_info=e)
    raise
방어 핵심 : 모든 DB 작업은 트랜잭션 + 롤백 구현. 예외를 pass로 무시 금지. 외부 API 실패 시 보상 트랜잭션(Saga 패턴) 고려.
총정리 A01~A10 방어 핵심 한 줄 요약
ID방어 핵심 한 줄
A01리소스 접근 시 "요청자 == 소유자" 서버에서 직접 검증
A02시크릿은 환경 변수, 운영 환경 debug=False 강제
A03yaml.safe_load(), pip freeze로 의존성 버전 고정
A04비밀번호는 bcrypt/argon2, 전송 데이터는 TLS
A05사용자 입력은 쿼리에 직접 연결 금지, Prepared Statement 사용
A06설계 단계에서 Threat Modeling, Rate Limit + 계정 잠금 구현
A07세션 ID는 secrets.token_urlsafe(32), JWT alg 명시 + 검증
A08pickle 금지, HMAC 서명 검증 후 역직렬화
A09인증/오류 이벤트 로깅, 민감정보는 로그 제외
A10예외 무시 금지, DB 트랜잭션 + 롤백 구현
다음 포스팅  |  A01~A10 각각을 개별 포스팅으로 더 깊게 다룰 예정입니다. bad.py 직접 실행해서 공격 확인 → good.py로 방어 결과 비교하는 실습 위주로 진행합니다.