flawopen.com/Vulnerabilities/세션 고정 및 쿠키 보안
세션 고정: 로그인 시 ID 교체 (CWE-384)
공격자가 이미 알고 있는 수하물표 번호를 여행자가 쓰도록 속였다고 상상해 보세요. 항공사가 수하물을 맡긴 뒤에도 같은 번호를 유지하면 공격자가 가방을 찾아갈 수 있습니다. 세션 고정은 애플리케이션이 공격자가 피해자에게 사용하게 만들 수 있는 식별자를 받아들인 뒤, 그 식별자를 바꾸지 않고 인증된 세션으로 승격하는 문제입니다. 두 조건이 모두 필요합니다. 애플리케이션이 해당 식별자를 받아들이거나 설정할 수 있게 하지 않는다면 일반적인 피싱 링크만으로 세션을 고정할 수 없습니다.
핵심 개념과 하위 시스템 용어
- 세션 고정
- 애플리케이션이 공격자가 아는 식별자를 받아들이고 피해자가 인증한 뒤에도 유지하는 세션 관리 결함입니다.
- 세션 ID 교체
- 인증으로 세션 권한이 상승할 때 새롭고 예측하기 어려운 식별자를 발급합니다. 애플리케이션이 유지해야 하는 세션 데이터만 보존합니다.
- 쿠키 속성
Secure는 HTTPS 전송만 허용하고,HttpOnly는 JavaScript의 쿠키 읽기를 막으며,SameSite는 일부 교차 사이트 전송을 제어합니다. 각각 다른 위험에 대응하므로 ID 교체를 대신할 수 없습니다.__Host-접두사- 지원 브라우저에서는 HTTPS를 통해 설정한 Secure 쿠키,
Path=/,Domain속성 없음이 요구됩니다. 인접 하위 도메인이 같은 이름으로 상위 도메인 쿠키를 설정하기 어렵게 합니다.
단계별 공격 흐름
공격자가 세션 식별자를 얻거나 선택
별도의 취약점이나 기능으로 공격자가 ID를 얻고 피해자 브라우저가 이를 보내게 합니다. 방법은 애플리케이션마다 다릅니다. URL 세션 ID를 허용하지 않는다면 일반적인 피싱 링크만으로는 이 단계를 만들 수 없습니다.
피해자가 공격자가 아는 식별자를 사용
브라우저는 해당 ID를 애플리케이션에 보내고, 애플리케이션은 유효한 익명 세션으로 처리합니다. 공격자는 같은 식별자를 알아보거나 다시 사용할 수 있어야 합니다.
인증으로 세션 권한이 상승
피해자가 로그인합니다. 애플리케이션이 기존 ID를 교체하지 않고 인증된 신원을 연결하면 공격자가 아는 식별자가 인증된 세션을 가리킵니다.
공격자가 바뀌지 않은 식별자를 재사용
공격자가 여전히 ID를 보낼 수 있고 서버가 이를 무효화하지 않았다면 피해자 세션에 접근할 수 있습니다. 권한 변경 시 ID를 교체하면 이 공격 경로를 끊을 수 있습니다.
메커니즘과 근본 원인
세션 고정에는 공격자가 피해자가 사용할 식별자에 영향을 미칠 수 있고, 로그인 성공 후 애플리케이션이 그 식별자를 교체하지 않는다는 두 조건이 필요합니다. 쿠키 속성은 심층 방어 수단이지만 ID 교체를 대신하지 않습니다. HttpOnly는 스크립트 읽기를 제한하고, Secure는 HTTPS를 요구하며, SameSite는 일부 교차 사이트 전송을 줄이고, __Host-는 지원 브라우저에서 쿠키 범위를 제한합니다.
# VULNERABLE: custom session code keeps the pre-authentication ID.
def login_view(request):
user = authenticate(request, request.POST["username"],
request.POST["password"])
if user is not None:
# BUG: privilege changes, but the session ID does not.
request.session["user_id"] = user.pk
request.session["authenticated"] = True
return redirect("/account/")
return redirect("/login/")
# HARDENED: Django login() rotates the session key on authentication.
from django.contrib.auth import authenticate, login
def login_view(request):
user = authenticate(request, request.POST["username"],
request.POST["password"])
if user is not None:
# Uses Django's supported authentication/session integration.
login(request, user)
return redirect("/account/")
return redirect("/login/")
# settings.py: use HTTPS, host-only scope, and a deliberate SameSite policy.
SESSION_COOKIE_NAME = "__Host-sessionid"
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_PATH = "/"
SESSION_COOKIE_DOMAIN = None
공격 순서와 개념 예시
- 1전제 조건: 별도의 취약점이나 기능으로 공격자가 피해자 브라우저가 보낼 ID에 영향을 미칠 수 있습니다.
- 2알려진 ID: 공격자가 같은 익명 세션 ID를 알아보거나 다시 사용할 수 있습니다.
- 3인증: 브라우저가 알려진 ID를 계속 보내는 동안 피해자가 로그인합니다.
- 4ID 교체 누락: 애플리케이션은 식별자를 바꾸지 않고 기존 세션의 권한을 높입니다.
- 5조건부 영향: 알려진 ID가 유효하고 사용 가능한 동안에만 공격자가 인증 세션에 접근할 수 있습니다.
엔지니어링 방어 및 예방 규칙
- ✓권한 변경 시 ID를 교체하세요: 로그인 및 권한 상승 시 예측하기 어려운 새 ID를 발급하고 프레임워크의 문서화된 방식에 따라 이전 ID를 무효화하세요.
- ✓프레임워크 인증 API를 사용하세요: 세션에 신원 정보를 직접 쓰지 말고 지원되는 로그인/세션 통합을 사용하세요(Django에서는
django.contrib.auth.login()). - ✓공격자가 제어하는 ID를 거부하세요: URL에 세션 ID를 넣지 마세요. 인접 하위 도메인의 쿠키 주입을 막고 사용자 정의 쿠키/세션 처리를 테스트하세요.
- ✓쿠키 속성을 용도에 맞게 설정하세요:
Secure와HttpOnly를 사용하고,Path=/이며Domain이 없는__Host-이름을 고려하세요. 정상적인 교차 사이트 로그인 흐름에 따라SameSite=Lax또는Strict를 선택하세요. 필요한 CSRF 토큰을 SameSite로 대체하지 마세요. - ✓전환을 검증하세요: 자동화 테스트에서 익명 ID를 기록하고 인증한 뒤 ID가 바뀌었는지, 이전 ID로 인증 세션에 접근할 수 없는지 확인하세요.