Step 134. 쿠키와 세션 공격 — 서버가 믿는 신분증을 흔들다
Level 2 — 보안 입문과 공격 스킬 기초 | 난이도 ★★★☆☆ | 예상 소요 시간 3시간
전제: Step 92~93의 SQL과 파이썬 연동, Step 102~103의 쿠키 조작, Step 131의 Flask 웹앱 만들기를 마쳤다.
- 준비물: 파이썬 3 + Flask, curl, 그리고 브라우저 개발자 도구(Application 탭). 오늘 실습은 전부
localhost에서 돌아갑니다. - ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다.
- 주의: 오늘 우리가 직접 띄우는 서버는 일부러 취약하게 만든 교육용입니다. 코드 최상단의 경고 주석처럼, 어디에도 배포하면 안 됩니다.
웹 서버는 여러분을 기억하지 못합니다. HTTP는 요청 하나하나가 독립이라, 서버 입장에서 매번 "처음 온 사람"입니다. 그래서 등장한 것이 쿠키(cookie) — 로그인에 성공하면 서버가 브라우저에 쥐여 주는 신분증입니다. 이후 요청에는 이 신분증이 자동으로 붙고, 서버는 그것만 보고 "아, alice구나"라고 판단합니다. 문제는 이 신분증이 클라이언트의 손에 있다는 것. 오늘 여러분은 내 컴퓨터에 일부러 취약한 세션 서버를 띄우고, 그 신분증을 복사하고, 위조하고, 번호를 예측해서 남의 계정에 들어가는 전 과정을 직접 재현합니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 쿠키와 세션이 로그인 상태를 유지하는 구조를 요청·응답 헤더 수준에서 설명한다
- 예측 가능한 세션 ID의 위험을 curl 실험으로 시연한다
- 세션 고정(session fixation) 공격이 성립하는 조건을 설명한다
- HttpOnly / Secure / SameSite 플래그가 각각 무엇을 막는지 응답 헤더로 구분한다
- 서명된 쿠키(Flask 세션)가 왜 단순 변조에 안전한지 변조 실험으로 확인한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 파이썬 3 + Flask(취약 실습 서버), curl(요청 재현), 브라우저 개발자 도구 |
| 오늘의 명령어·코드 | curl -i(응답 헤더 보기), curl -H "Cookie: ..."(쿠키 붙여 보내기), resp.set_cookie()의 httponly/secure/samesite 옵션, base64 디코딩 |
| 필요한 개념 | 쿠키 vs 세션, 세션 ID, 쿠키 변조·탈취·고정 3종 공격, 쿠키 플래그, 서명(signature) |
| 오늘의 산출물 | lab134.py(취약 세션 서버) + 세 가지 공격의 재현 기록 |
2-1. 쿠키와 세션 — 신분증과 명단
둘은 자주 섞여 불리지만 역할이 다릅니다. 쿠키는 서버가 응답의 Set-Cookie 헤더로 브라우저에 심어 두는 값이고, 세션(session) 은 서버가 "누가 로그인했는가"를 기억하는 장치입니다. 정석 구조는 이렇습니다.
- 로그인 성공 → 서버가 세션 저장소(명단)에
세션ID → alice를 기록 - 응답에
Set-Cookie: session_id=랜덤한값— 브라우저는 신분증을 받음 - 이후 요청마다 브라우저가
Cookie: session_id=랜덤한값을 자동으로 첨부 - 서버는 명단에서 그 번호를 찾아 "alice의 요청"으로 처리
즉 서버가 신뢰하는 것은 비밀번호가 아니라 세션 ID 문자열 하나입니다. 그래서 세션 ID를 훔치거나(탈취), 바꿔치거나(변조), 미리 정해 두거나(고정) 하는 것이 공격의 중심입니다.
2-2. 세션 공격 3종 세트
- 변조(tampering): 내 쿠키의 값을 고쳐 씁니다. 값이 서버의 검증을 통과하면 남의 권한이 생깁니다. Step 102~103에서 개발자 도구로 쿠키를 고쳐 통과한 것이 이 계열입니다.
- 탈취(theft): 남의 쿠키를 빼앗아 내 요청에 붙입니다. XSS(Step 138)로
document.cookie를 읽어 보내는 것이 대표적입니다. 오늘은 "복사한 쿠키가 그대로 통한다"는 것까지만 재현합니다. - 고정(fixation): 공격자가 미리 알고 있는 세션 ID를 피해자에게 쓰게 만든 뒤, 피해자가 로그인하면 그 ID로 같이 들어갑니다. 로그인 순간에 세션 ID를 새로 발급하지 않는 앱에서 성립합니다.
2-3. 쿠키 플래그 — 신분증에 붙이는 자물쇠
Set-Cookie 헤더에는 값 말고도 속성(플래그) 을 붙일 수 있습니다.
| 플래그 | 막는 것 |
|---|---|
HttpOnly |
자바스크립트의 document.cookie로 읽기 — XSS 탈취의 통로를 잠금 |
Secure |
HTTPS가 아닌 평문 연결로의 전송 — 네트워크 도청 구간에서 신분증 보호 |
SameSite |
다른 사이트가 시작한 요청에 쿠키가 따라가는 것 — CSRF 완화 |
플래그는 서버가 헤더에 적어 주면 브라우저가 지키는 브라우저와의 약속입니다. 오늘 응답 헤더에서 실제 모습을 확인합니다.
2-4. 왜 Flask 세션 쿠키는 마음대로 못 고치는가
Flask의 기본 세션은 쿠키에 데이터를 통째로 넣되, 비밀 키로 서명을 덧붙입니다. 형태는 내용.타임스탬프.서명입니다. 내용을 한 글자만 고쳐도 서명이 안 맞아서 서버가 버립니다. 암호화가 아니라 위조 방지라는 점이 중요합니다 — 내용은 누구나 읽을 수 있습니다. 오늘 이것도 직접 확인합니다.
3. 따라 하기
3-1. 취약 실습 서버 띄우기
lab134.py를 작성합니다 (교육용 취약 코드 — 어디에도 배포 금지):
from flask import Flask, request, make_response
app = Flask(__name__)
SESSIONS = {} # 세션 ID -> 사용자 이름 (서버 측 '명단')
NEXT_ID = [1000] # 1씩 증가하는, 일부러 예측 가능하게 만든 ID
@app.route("/login")
def login():
user = request.args.get("user", "guest")
sid = str(NEXT_ID[0])
NEXT_ID[0] += 1
SESSIONS[sid] = user
resp = make_response(f"로그인 완료: {user} (발급된 세션 ID: {sid})")
resp.set_cookie("session_id", sid)
return resp
@app.route("/dashboard")
def dashboard():
sid = request.cookies.get("session_id")
if sid in SESSIONS:
return f"[대시보드] {SESSIONS[sid]}님 환영합니다. 잔액: 1,000,000원"
return "로그인이 필요합니다", 401
if __name__ == "__main__":
app.run(port=5134)
입력 (새 터미널에서 서버를 띄워 둡니다):
python lab134.py
이 서버의 취약점은 두 가지입니다. 세션 ID가 1000, 1001, 1002…로 순차 발급되고(예측 가능), 쿠키 값에 서명이 없습니다(변조 가능). 실수로 만든 것이 아니라, 실제로 오래된 자체 구현 세션이 이렇게 생겼습니다.
3-2. 로그인과 신분증 관찰
입력 (다른 터미널에서):
curl -i "http://127.0.0.1:5134/login?user=alice"
출력 (2026-09-09 실측, 헤더 일부):
HTTP/1.1 200 OK
Set-Cookie: session_id=1000; Path=/
로그인 완료: alice (발급된 세션 ID: 1000)
읽는 법: -i는 응답 헤더까지 보여 줍니다. Set-Cookie: session_id=1000 — 서버가 브라우저(여기서는 curl)에게 신분증을 쥐여 주는 순간입니다. 이 요청 이후 alice의 브라우저는 모든 요청에 Cookie: session_id=1000을 붙입니다.
bob도 로그인해 둡니다 — 1001을 받습니다. 그리고 공격자인 여러분도 로그인합니다.
Set-Cookie: session_id=1002; Path=/
로그인 완료: attacker (발급된 세션 ID: 1002)
여기서 멈추고 생각하세요. 내가 1002번이라면, 앞에 로그인한 사람들은 몇 번일까요?
3-3. 쿠키 재사용 — 신분증이 곧 나
먼저 정상 동작을 확인합니다. bob의 쿠키로 대시보드에 갑니다.
입력:
curl -H "Cookie: session_id=1001" "http://127.0.0.1:5134/dashboard"
출력 (2026-09-09 실측):
[대시보드] bob님 환영합니다. 잔액: 1,000,000원
쿠키 없이 가면 401 UNAUTHORIZED / 로그인이 필요합니다가 나옵니다 (동일 실측). 즉 이 서버에서 "나"를 증명하는 것은 오직 이 문자열 하나입니다. 이 값을 남에게서 훔치면(XSS 등), 훔친 쪽이 그 사람이 됩니다 — 탈취가 왜 치명적인지의 구조입니다.
3-4. 변조와 예측 — 번호를 하나 낮춰 보기
공격자(1002번)가 신분증의 숫자를 바꿉니다. 개발자 도구에서 고쳐도 되고, curl이면 그냥 다른 값을 내면 됩니다.
입력:
curl -H "Cookie: session_id=1000" "http://127.0.0.1:5134/dashboard"
출력 (2026-09-09 실측):
[대시보드] alice님 환영합니다. 잔액: 1,000,000원
읽는 법: 비밀번호를 한 글자도 몰랐는데 alice의 대시보드가 열렸습니다. 성공 조건은 두 개였습니다 — ID가 예측 가능했고(1씩 증가), 서버가 "내가 발급한 값이 맞나"를 검증하지 않았습니다(서명 부재). 아직 발급되지 않은 번호(예: 당시 1002 이후)를 넣으면 401로 실패합니다 — 공격은 "존재하는 남의 번호"를 맞혀야 성립합니다.
왜: 이것이 자체 구현 세션의 고전적 실패입니다. 방어는 두 줄로 요약됩니다 — 세션 ID는 암호학적으로 무작위로 길게 뽑을 것(파이썬 secrets.token_hex()), 그리고 가능하면 프레임워크의 검증된 세션을 쓸 것.
3-5. 세션 고정 — 로그인해도 번호가 안 바뀌는 앱
lab134.py에 두 경로를 추가하고 서버를 재시작합니다.
@app.route("/visit")
def visit():
sid = request.cookies.get("anon_id")
if sid and sid in SESSIONS:
return f"재방문입니다. 당신의 세션 ID: {sid}"
sid = str(NEXT_ID[0]); NEXT_ID[0] += 1
SESSIONS[sid] = "anonymous"
resp = make_response(f"첫 방문. 세션 ID 발급: {sid}")
resp.set_cookie("anon_id", sid)
return resp
@app.route("/upgrade")
def upgrade():
"""로그인했지만 세션 ID를 새로 발급하지 않는(=고정에 취약한) 앱."""
sid = request.cookies.get("anon_id")
if sid in SESSIONS:
SESSIONS[sid] = request.args.get("user", "victim")
return f"로그인 처리 완료. 세션 ID는 그대로: {sid} -> {SESSIONS[sid]}"
return "먼저 /visit 하세요", 400
입력과 출력 (2026-09-09 실측):
curl -c fix.txt "http://127.0.0.1:5134/visit" # 쿠키를 파일에 저장
→ 첫 방문. 세션 ID 발급: 1003
curl -b fix.txt "http://127.0.0.1:5134/upgrade?user=victim"
→ 로그인 처리 완료. 세션 ID는 그대로: 1003 -> victim
읽는 법: 방문 때 받은 번호가 로그인 후에도 그대로 1003입니다. 이제 공격 시나리오가 보입니다. 공격자가 피해자에게 "이 링크로 접속해"라며 미리 발급받은 ID를 심어 두면(피싱 링크·쿠키 심기 등), 피해자가 로그인하는 순간 그 ID는 로그인된 세션이 됩니다. 공격자는 자기가 아는 번호로 그냥 들어가면 됩니다. 실측에서도 1003으로 다시 접속하니 "재방문" 인사가 왔습니다.
왜: 방어는 한 줄 — 로그인 성공 시 세션 ID를 반드시 새로 발급할 것. 로그인 전후로 ID가 같으면 고정 공격의 문이 열려 있습니다. 실제 앱을 점검할 때 확인할 항목 1순위입니다.
3-6. 플래그 비교 — 응답 헤더가 다르다
두 경로를 더 추가하고 헤더를 비교합니다.
@app.route("/login_plain")
def login_plain():
resp = make_response("플래그 없는 쿠키 발급")
resp.set_cookie("token_plain", "abc123")
return resp
@app.route("/login_guarded")
def login_guarded():
resp = make_response("플래그 있는 쿠키 발급")
resp.set_cookie("token_guarded", "abc123",
httponly=True, secure=True, samesite="Lax")
return resp
출력 (2026-09-09 실측, curl -i의 Set-Cookie 줄):
Set-Cookie: token_plain=abc123; Path=/
Set-Cookie: token_guarded=abc123; Secure; HttpOnly; Path=/; SameSite=Lax
읽는 법: 값은 둘 다 abc123인데 약속이 다릅니다. HttpOnly가 붙은 쿠키는 브라우저에서 자바스크립트로 읽을 수 없습니다 — 브라우저를 띄울 수 있다면 콘솔에서 document.cookie를 쳐서 비교해 보세요. HttpOnly 없는 쿠키만 목록에 나옵니다 (브라우저 화면은 예시 — 헤더의 차이는 위 실측이 증명합니다). XSS가 터져도 쿠키 탈취가 실패하게 만드는 것이 이 한 단어입니다.
왜: 플래그는 공격을 "불가능"하게 하지는 않지만 탈취 통로 하나를 확실히 닫습니다. 방어는 겹겹이 쌓는 것입니다.
3-7. 서명된 쿠키 — Flask 기본 세션의 변조 실험
마지막으로 프레임워크의 정석 세션과 비교합니다. app.secret_key를 설정하고 Flask의 session을 쓰는 경로를 추가합니다.
from flask import session
app.secret_key = "lab-secret-key-for-demo"
@app.route("/flask_login")
def flask_login():
session["user"] = "alice"
return "Flask 서명 세션 발급 완료 (user=alice)"
@app.route("/flask_dashboard")
def flask_dashboard():
if "user" in session:
return f"[Flask 대시보드] {session['user']}님 환영합니다"
return "로그인이 필요합니다", 401
입력과 출력 (2026-09-09 실측):
curl -c fs.txt "http://127.0.0.1:5134/flask_login"
# 저장된 쿠키: eyJ1c2VyIjoiYWxpY2UifQ.aqED4Q.YiJt_SqG9Fjby4ehWW2k5PFLtp8
curl -H "Cookie: session=eyJ1c2VyIjoiYWxpY2UifQ.aqED4Q.YiJt_SqG9Fjby4ehWW2k5PFLtp8" ".../flask_dashboard"
→ [Flask 대시보드] alice님 환영합니다
이제 끝글자 하나만 X로 바꿔 보냅니다.
→ 로그인이 필요합니다 (2026-09-09 실측 — 변조 즉시 무효)
그런데 쿠키의 첫 덩어리를 base64로 디코딩하면 내용이 그대로 읽힙니다 (동일 실측):
echo "eyJ1c2VyIjoiYWxpY2UifQ" | python -c "import sys,base64; s=sys.stdin.read().strip(); s+='='*(-len(s)%4); print(base64.urlsafe_b64decode(s))"
→ b'{"user":"alice"}'
읽는 법: 두 사실이 동시에 성립합니다. 내용은 누구나 읽을 수 있고(암호화 아님), 고치면 무효가 됩니다(서명 덕분). 그래서 두 규칙이 나옵니다 — 서명 쿠키에 비밀번호 같은 비밀을 넣지 말 것(읽히니까), 그리고 secret_key를 유출하지 말 것(키를 알면 서명을 만들 수 있으니까).
왜: 3-4에서 번호만 고쳐 통과한 서버와 방금의 Flask 세션, 이 대비가 오늘의 결론입니다 — 세션은 "번호표"가 아니라 "검증 가능한 증명"이어야 합니다.
4. 미션과 연습문제
미션 — 세션 공격 3종 재현 보고서
lab134.py전체를 완성하고, 변조(3-4)·고정(3-5)·서명 변조 실패(3-7) 세 실험의 명령과 출력을 캡처합니다.- 각 실험 옆에 "이 공격이 성공/실패한 이유"를 두 줄씩 적습니다.
- 위키에
세션공격정리.md를 작성합니다 — 3종 공격(변조·탈취·고정)의 정의, 성공 조건, 대응 방어를 표로 정리하세요. - 마지막에 한 문장 결론: "Flask 세션은 변조에 안전한데, 그럼 어떤 앱이 취약한가?"에 대한 답을 쓰세요.
연습문제
문제 1. 쿠키와 세션의 차이를 "신분증과 명단" 비유를 쓰지 말고 기술 용어로만 설명해 보세요.
문제 2. 3-4의 공격이 성공한 두 가지 조건을 들고, 각각에 대응하는 방어를 한 줄씩 써 보세요.
문제 3. 세션 고정 공격에서 공격자가 미리 세션 ID를 알아야 하는 이유와, 로그인 시 세션 ID를 재발급하면 왜 공격이 무산되는지 설명해 보세요.
문제 4. Flask 세션 쿠키를 디코딩하면 내용이 보이는데도 안전하다고 말하는 이유는 무엇인가요? 단, 이 쿠키에 절대 넣으면 안 되는 것 하나를 함께 답하세요.
5. 모범 답안과 완료 기준
미션 모범 답안
검증하는 법: ① 변조 실험에서 session_id=1000이 alice의 대시보드를 여는 출력이 있는가 (2026-09-09 실측 기준). ② 고정 실험에서 /visit과 /upgrade의 ID가 같은 값으로 찍혔는가. ③ 서명 쿠키의 끝글자를 바꾼 요청이 로그인이 필요합니다로 떨어지는가. ④ 정리 표에 방어 열이 "무작위 긴 ID / HttpOnly / 로그인 시 ID 재발급 / 프레임워크 세션 사용"을 포함하는가.
한 문장 결론의 모범 예: "서명·검증 없이 세션 ID를 그대로 믿는 자체 구현 세션, 그리고 ID를 예측할 수 있게 발급하는 앱이 취약하다. Flask 같은 프레임워크의 세션은 서명으로 변조를 무효화하지만, 키 관리와 플래그 설정은 여전히 개발자의 몫이다."
연습문제 해답
문제 1 해답. 쿠키는 서버가 Set-Cookie 응답 헤더로 브라우저에 저장시키는 이름-값 쌍이고, 이후 요청의 Cookie 헤더로 자동 첨부되는 클라이언트 측 저장물입니다. 세션은 서버가 로그인 상태를 기억하는 서버 측 저장소이며, 보통 세션 ID만 쿠키로 주고받고 실제 상태는 서버에 둡니다. 쿠키는 운반 수단, 세션은 서버의 기억입니다.
문제 2 해답. 조건 ① 세션 ID가 예측 가능했다(1000, 1001…) → 방어: secrets 모듈 등으로 충분히 긴 무작위 ID 발급. 조건 ② 서버가 값의 출처를 검증하지 않았다(서명 없음) → 방어: 서명된 세션 쿠키 또는 서버 측 세션 저장소 사용. 둘 중 하나만 무너져도 공격은 실패합니다.
문제 3 해답. 고정 공격의 핵심은 피해자의 로그인이 공격자가 아는 ID 위에서 이루어지게 하는 것입니다. 로그인 순간 서버가 새 ID를 발급하면, 공격자가 심어 둔 옛 ID는 익명 세션으로 남고 실제 로그인 세션은 공격자가 모르는 새 번호가 되어 공격이 무산됩니다. 그래서 "로그인 성공 시 세션 ID 재발급"이 표준 방어입니다.
문제 4 해답. 안전하다는 뜻은 변조 불가이지 비밀 유지가 아니기 때문입니다. 내용을 고치면 서명이 어긋나 서버가 버리므로(3-7 실측) 권한 상승에는 쓸 수 없습니다. 하지만 내용은 누구나 디코딩할 수 있으므로 비밀번호·API 키 같은 비밀값을 이 쿠키에 넣으면 안 됩니다.
완료 기준 체크리스트
- [ ]
Set-Cookie와Cookie헤더의 역할을 요청·응답 흐름으로 설명할 수 있다 - [ ] curl로 쿠키를 붙여 로그인 없이 대시보드에 접근하는 것을 재현했다
- [ ] 예측 가능한 세션 ID의 변조 공격을 성공시키고 성공 조건 2개를 말할 수 있다
- [ ] 세션 고정이 성립하는 조건(로그인 전후 ID 불변)을 실험으로 확인했다
- [ ] HttpOnly / Secure / SameSite가 각각 무엇을 막는지 말할 수 있다
- [ ] 서명 쿠키의 변조가 실패함을 확인하고, "읽을 수는 있다"는 점까지 설명할 수 있다
- [ ] 미션: 3종 공격 재현 보고서를 작성했다
6. 흔한 실수와 해결
벽 1. curl에 쿠키를 붙였는데 401이 나와요
증상: 분명 로그인했는데 로그인이 필요합니다가 나옵니다.
원인: 쿠키 이름이 틀렸거나(session_id vs session), 값을 따옴표로 감싸지 않아 셸이 ;를 명령 구분자로 해석했습니다.
해결: curl -H "Cookie: session_id=1001"처럼 헤더 전체를 큰따옴표로 감싸세요. 로그인 응답의 Set-Cookie 줄에 적힌 이름을 그대로 써야 합니다 — 이름이 다르면 서버는 못 찾습니다.
벽 2. 쿠키 파일(-c)에서 값을 읽었는데 이상해요
증상: grep으로 뽑은 값이 비어 있거나 줄바꿈이 섞입니다.
원인: curl의 쿠키 파일은 탭으로 구분되고, HttpOnly 쿠키는 #HttpOnly_ 접두어가 붙은 줄로 저장됩니다.
해결: awk '$6=="session_id"{print $NF}' 쿠키파일처럼 필드 번호로 추출하세요. 마지막 필드가 값입니다.
벽 3. 서버를 고쳤는데 동작이 그대로예요
증상: 경로를 추가했는데 404가 나옵니다.
원인: Flask 개발 서버는 파일 변경 시 자동 재시작되지 않습니다(디버그 모드가 아니라면).
해결: 실행 중인 서버를 Ctrl+C로 멈추고 다시 python lab134.py. 포트가 안 비면 다른 터미널에 옛 서버가 살아 있는 것이니 먼저 끄세요.
벽 4. 서명 쿠키를 디코딩했는데 깨진 문자가 나와요
증상: base64 디코딩이 오류를 내거나 쓰레기가 나옵니다.
원인: Flask 쿠키는 URL 안전 base64라 -와 _를 쓰고, 끝의 패딩(=)이 생략되어 있습니다.
해결: 3-7의 한 줄 명령처럼 urlsafe_b64decode를 쓰고 패딩을 보정하세요 — s += '=' * (-len(s) % 4)가 그 보정입니다.
벽 5. "HttpOnly를 붙였는데 curl로는 여전히 읽혀요"
증상: 헤더에 HttpOnly가 있는데 curl/스크립트에서 쿠키 값이 보입니다.
원인: 혼동이 아니라 정상입니다. HttpOnly는 브라우저의 자바스크립트에 대한 약속이지, 값 자체를 숨기는 기능이 아닙니다.
해결: HttpOnly의 효과는 브라우저 콘솔의 document.cookie에서 해당 쿠키가 빠져 있음으로 확인하세요. 네트워크 구간의 값은 어차피 보이고, 그래서 Secure(HTTPS)가 별도로 필요합니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 쿠키 | 서버가 브라우저에 심는 이름-값 쌍 — 매 요청에 자동 첨부 |
| 세션 | 서버 측의 "누가 로그인했나" 저장소 — 쿠키는 그 출입증 |
| 쿠키 변조 | 값을 고쳐 서버를 속이기 — 서명·검증 없으면 통한다 |
| 쿠키 탈취 | 남의 쿠키를 빼앗아 내가 그 사람이 되기 — XSS가 주 통로 |
| 세션 고정 | 공격자가 아는 ID로 로그인하게 만들기 — 로그인 시 재발급으로 무산 |
| HttpOnly / Secure / SameSite | JS 읽기 금지 / HTTPS 전용 / 타 사이트 요청 차단 |
| 서명 쿠키 | 내용은 읽혀도 변조는 무효 — Flask 세션의 방식 |
오늘의 명령어
| 명령 | 하는 일 |
|---|---|
curl -i URL |
응답 헤더까지 보기 (Set-Cookie 확인) |
curl -H "Cookie: 이름=값" URL |
쿠키를 붙여 요청 (신분증 제시) |
curl -c 파일 / -b 파일 |
쿠키를 파일에 저장 / 파일에서 읽어 전송 |
resp.set_cookie("이름", 값, httponly=True, ...) |
Flask에서 플래그 붙은 쿠키 발급 |
base64.urlsafe_b64decode(...) |
서명 쿠키의 내용 부분 읽기 |
명령어보다 중요한 감각
서버는 비밀번호를 기억하지 않고 세션 ID 문자열 하나를 믿습니다. 그래서 공격은 전부 그 문자열을 향하고, 방어는 그 문자열의 발급(무작위·충분히 길게), 운반(Secure·HttpOnly), 갱신(로그인 시 재발급), 검증(서명) 네 군데에 걸쳐 있습니다. 오늘 여러분은 취약한 서버를 직접 띄워 네 구멍이 어떻게 뚫리는지 봤습니다. 다음부터 로그인 있는 사이트를 볼 때 개발자 도구 Application 탭이 먼저 생각난다면, 오늘의 목표는 달성입니다.
전부 체크되면 Step 134 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.