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 | 취약점 | 핵심 키워드 |
|---|---|---|
| A01 | Broken Access Control | IDOR, 인가 검증 누락 |
| A02 | Security Misconfiguration | debug=True, 하드코딩 시크릿 |
| A03 | Supply Chain Failures | yaml.load(), 버전 미고정 |
| A04 | Cryptographic Failures | MD5/평문 vs bcrypt |
| A05 | Injection | SQLi, 파라미터 바인딩 |
| A06 | Insecure Design | 브루트포스 방어 없음 |
| A07 | Identification & Auth Failures | 예측 가능한 세션 토큰 |
| A08 | Data Integrity Failures | pickle 역직렬화, HMAC |
| A09 | Logging Failures | 로그 없음, 민감정보 로그 |
| A10 | System 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 강제 |
| A03 | yaml.safe_load(), pip freeze로 의존성 버전 고정 |
| A04 | 비밀번호는 bcrypt/argon2, 전송 데이터는 TLS |
| A05 | 사용자 입력은 쿼리에 직접 연결 금지, Prepared Statement 사용 |
| A06 | 설계 단계에서 Threat Modeling, Rate Limit + 계정 잠금 구현 |
| A07 | 세션 ID는 secrets.token_urlsafe(32), JWT alg 명시 + 검증 |
| A08 | pickle 금지, HMAC 서명 검증 후 역직렬화 |
| A09 | 인증/오류 이벤트 로깅, 민감정보는 로그 제외 |
| A10 | 예외 무시 금지, DB 트랜잭션 + 롤백 구현 |
다음 포스팅 | A01~A10 각각을 개별 포스팅으로 더 깊게 다룰 예정입니다. bad.py 직접 실행해서 공격 확인 → good.py로 방어 결과 비교하는 실습 위주로 진행합니다.
'개발 일지' 카테고리의 다른 글
| 스텔라이브 라이브 대시보드 (1) | 2026.04.20 |
|---|---|
| [마인크래프트] Paper 플러그인으로 RPG 서버 만들기 - Day 1 (0) | 2026.01.01 |