[주요정보통신기반시설 웹 애플리케이션 취약점 분석·평가 21일 완성 코스] 18강 — [Day 18] 쿠키 변조
안녕하세요! Hackerstory 공부방의 IT/보안 튜터 정다은입니다. 👋
어느덧 [주요정보통신기반시설 웹 애플리케이션 취약점 분석·평가 21일 완성 코스]의 18일 차 강의에 도달했습니다! 지난 시간에 배운 '데이터 평문 전송(Day 17)'에 이어, 오늘은 클라이언트 측에 저장되는 데이터의 허점을 노리는 아주 고전적이면서도 치명적인 취약점인 **'쿠키 변조(Cookie Tampering)'**에 대해 알아보겠습니다.
HTTP 프로토콜의 특성상 클라이언트(브라우저)에서 서버로 보내는 모든 요청은 사용자가 임의로 조작할 수 있다는 사실, 기억하시죠? 개발자가 이 점을 간과하고 중요 정보를 쿠키에 담아두면 어떤 일이 발생하는지, 지금부터 자세히 파헤쳐 봅시다!
[Day 18] 쿠키 변조 (Cookie Tampering)
1. 개념 설명: 쿠키 변조란 무엇인가요?
💡 쿠키(Cookie)의 기본 개념
웹 프로토콜인 HTTP는 **Stateful(상태 유지)**이 아니라 Stateless(비상태성) 프로토콜입니다. 즉, 이전 요청과 다음 요청이 서로 연결되지 않습니다. 그래서 웹 사이트는 로그인 상태나 장바구니 목록 등을 기억하기 위해 **쿠키(Cookie)**라는 작은 텍스트 조각을 클라이언트 브라우저에 저장하고, 서버에 요청을 보낼 때마다 이 쿠키를 함께 전송합니다.
⚠️ 쿠키 변조 취약점의 원리
쿠키 변조 취약점은 서버가 클라이언트 브라우저에 저장된 쿠키 값을 **‘무조건 신뢰’**할 때 발생합니다.
웹 브라우저의 개발자 도구(F12)나 프록시 툴(Burp Suite 등)을 사용하면 클라이언트에 저장된 쿠키 값을 사용자가 눈으로 확인하고, 원하는 값으로 자유롭게 수정할 수 있습니다. 만약 웹 애플리케이션이 쿠키에 사용자의 등급, 권한, 결제 금액, 사용자 ID 등을 평문( 또는 쉽게 복호화할 수 있는 형태로) 저장하고 이를 검증 없이 신뢰한다면, 공격자는 단 몇 초 만에 다른 사람의 계정으로 위장하거나 관리자 권한을 획득할 수 있게 됩니다.
🎟️ 비유하자면? 놀이공원에서 "VIP 손님"이라고 직접 수기로 적을 수 있는 종이 자유이용권을 발급해 준 것과 같습니다. 손님이 볼펜으로 '일반'을 'VIP'로 고쳐 써서 제출하는데, 입구의 검표원이 중앙 시스템 확인 없이 종이에 적힌 글자만 보고 VIP 라인으로 통과시켜 주는 상황인 것이죠!
2. 실무 예시 및 공격 시나리오
쿠키 변조 공격이 실제로 어떻게 일어나는지 구체적인 시나리오를 통해 살펴보겠습니다.
🎭 시나리오 1: 권한 우회 및 관리자 승격 (Privilege Escalation)
서비스 개발자가 로그인 성공 시 아래와 같이 쿠키를 생성하여 브라우저에 전달했다고 가정해 봅시다.
[서버가 전달한 정상 쿠키 응답]
HTTP/1.1 200 OK
Set-Cookie: user_id=kim123; user_role=USER; is_admin=false
공격자 kim123은 브라우저의 개발자 도구를 열어 쿠키 값을 다음과 같이 변경합니다.
[공격자가 변조하여 전송한 쿠키 요청]
GET /admin/dashboard HTTP/1.1
Host: vulnerable-bank.com
Cookie: user_id=kim123; user_role=ADMIN; is_admin=true
서버가 세션 DB나 토큰 검증 없이 is_admin=true라는 쿠키 값만 확인하고 관리자 페이지 접근을 허용한다면, 공격자는 클릭 한 번으로 관리자 권한을 탈취하게 됩니다.
💳 시나리오 2: 비즈니스 로직(금액) 변조
장바구니 시스템에서 결제할 상품 정보와 금액을 쿠키에 담아 처리하는 취약한 쇼핑몰이 있습니다.
[정상적인 결제 요청 쿠키]
GET /checkout HTTP/1.1
Host: vulnerable-shop.com
Cookie: item_id=LAPTOP_01; price=1500000; qty=1
공격자는 Burp Suite 등의 프록시 도구를 이용해 요청을 가로챈 후 price 값을 수정합니다.
[변조된 결제 요청 쿠키]
GET /checkout HTTP/1.1
Host: vulnerable-shop.com
Cookie: item_id=LAPTOP_01; price=100; qty=1
서버가 결제 금액을 DB의 실제 상품 가격과 비교하지 않고 쿠키에 담긴 price 값으로 PG사 결제를 요청하면, 150만 원짜리 노트북을 100원에 구매하는 치명적인 보안 사고가 발생합니다.
🔍 시나리오 3: 인코딩 및 단순 암호화 착각 (Base64 등)
개발자가 나름대로 보안을 고려하여 쿠키 값을 Base64로 인코딩하여 저장하는 경우가 있습니다.
- 원래 값:
user_id=guest&role=user - Base64 인코딩 후 쿠키:
dXNlcl9pZD1ndWVzdCZyb2xlPXVzZXI=
공격자는 이를 보고 Base64 디코딩을 수행하여 원문 데이터를 확인합니다. 그 후 user_id=admin&role=super_admin으로 문자열을 고친 뒤, 다시 Base64로 인코딩(dXNlcl9pZD1hZG1pbiZyb2xlPXN1cGVyX2FkbWlu)하여 쿠키로 전송합니다.
⚠️ 주의: Base64나 Hex(16진수) 변환은 **'암호화'가 아닌 '인코딩(단순 형태 변환)'**일 뿐입니다. 누구나 역으로 계산해 원본을 알아낼 수 있으므로 보안 대책이 될 수 없습니다!
3. 보안 대책 및 대응 방안 (Remediation)
쿠키 변조 취약점을 근본적으로 해결하기 위해서는 **"클라이언트에 저장되는 모든 데이터는 조작될 수 있다"**는 원칙에서 출발해야 합니다.
1) 민감 정보의 쿠키 직접 저장 금지 & 서버 측 세션(Session) 사용
권한, 사용자 식별 정보, 결제 정보 등 보안상 중요한 데이터는 절대 클라이언트 쿠키에 직접 저장하면 안 됩니다. 대신 서버 측 세션(Server-side Session) 메커니즘을 사용해야 합니다.
- 올바른 구조:
- 서버는 예측 불가능한 무작위 문자열인 **세션 ID(Session ID)**만 생성하여 쿠키로 클라이언트에 전달합니다.
- 사용자의 식별자, 권한, 상태 등 민감한 정보는 서버의 메모리나 DB 세션 스토어에 보관합니다.
- 클라이언트가 세션 ID를 보낼 때마다 서버가 내부 DB에서 권한을 조회하여 검증합니다.
[취약한 구조]
클라이언트 (Cookie: role=admin) ---> 서버 ("role이 admin이네? 통과!")
[안전한 구조]
클라이언트 (Cookie: JSESSIONID=a8f9c2...) ---> 서버 (세션 DB 조회: "a8f9c2는 일반 유저(USER)군. 통과 불가능!")
2) 쿠키 서명(Signing) 및 암호화(Encryption) 적용
어쩔 수 없이 클라이언트 측 쿠키에 데이터를 저장해야 하는 구조라면(예: Stateless한 JWT 또는 쿠키 기반 세션), 서버만 알고 있는 비밀키(Secret Key)로 서명하거나 암호화해야 합니다.
- 서명(Digital Signature): 쿠키 내용 뒤에 HMAC 형태의 서명 값을 붙여 전송합니다. 클라이언트가 데이터를 1비트라도 수정하면 서버에서 서명 검증이 실패하여 조작 여부를 즉시 감지할 수 있습니다.
3) 쿠키 보안 속성(Security Flags) 필수 설정
쿠키를 생성할 때는 변조 외에도 탈취 방지를 위해 아래의 3가지 핵심 보안 플래그를 반드시 설정해야 합니다.
HttpOnly: 자바스크립트(document.cookie)를 통한 쿠키 접근을 차단합니다. (XSS 공격을 통한 세션 쿠키 탈취 방지)Secure: HTTPS(암호화 통신) 연결일 때만 쿠키를 전송하도록 설정합니다. (Day 17에서 배운 데이터 평문 전송 및 도청 방지)SameSite=Strict또는Lax: 타 사이트에서의 요청 시 쿠키 전송을 제한하여 CSRF 공격을 방지합니다.
🛠️ 보안 설정 예시 코드
[Java / Spring Boot 예시]
// 취약한 예시: 민감한 정보를 평문 쿠키로 저장
Cookie roleCookie = new Cookie("user_role", "ADMIN");
response.addCookie(roleCookie);
// 안전한 예시: 세션 사용 및 쿠키 보안 플래그 설정
ResponseCookie cookie = ResponseCookie.from("SESSIONID", request.getSession().getId())
.httpOnly(true) // JS 접근 불가
.secure(true) // HTTPS 전용
.path("/")
.maxAge(3600)
.sameSite("Strict") // CSRF 방지
.build();
response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
[Node.js / Express 예시]
// express-session 활용을 통한 안전한 세션 쿠키 설정
const session = require('express-session');
app.use(session({
secret: 'my-super-secret-key-1234', // 세션 암호화 키
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // XSS 방지
secure: true, // HTTPS 전용
sameSite: 'strict',
maxAge: 3600000 // 1시간
}
}));
4. 요약 및 정리
오늘 배운 내용을 한 줄로 요약하자면 "클라이언트가 보내오는 쿠키 값은 절대 믿지 말고, 중요 상태는 서버에서 직접 관리하라" 입니다.
| 구분 | 취약한 방식 ❌ | 안전한 보안 방식 ⭕ |
|---|---|---|
| 데이터 저장 위치 | 클라이언트 쿠키에 권한/금액/ID 직접 저장 | 서버 측 세션 스토어(DB/Redis)에 저장 |
| 쿠키 역할 | 사용자 상태 데이터 자체를 전달 | 난수화된 세션 식별자(Session ID)만 전달 |
| 검증 방식 | 쿠키 파라미터 값 단순 확인 | 서버 DB 조회 및 무결성 서명 검증 |
| 보안 옵션 | 플래그 미설정 (Set-Cookie: sid=123) |
HttpOnly, Secure, SameSite 플래그 적용 |
웹 보안 진단을 수행할 때 쿠키에 role, auth, admin, price, user_id 같은 이름의 파라미터가 들어있는지 유심히 관찰해 보세요. 만약 발견된다면 해당 값을 변경했을 때 권한이 우회되는지 검증하는 것이 정석적인 진단 모범 사례입니다!
📍 다음 강의 예고: [Day 19] 관리자 페이지 노출
고생 많으셨습니다! 이제 커리큘럼도 막바지를 향해 달려가고 있네요.
다음 시간 Day 19에서는 '관리자 페이지 노출' 취약점을 다룹니다. 웹 서비스의 최고 권한이 모여있는 관리자 페이지가 추측하기 쉬운 URL, 브라우저 로컬 스토리지 leakage, 혹은 구글링(구글 해킹) 등을 통해 외부 공격자에게 노출될 때 발생하는 위협과 이를 철저히 숨기고 보호하는 보안 대책을 공부하겠습니다.
그럼, 다음 시간에도 즐겁게 열공해 봅시다! 궁금한 점은 언제든 댓글로 남겨주세요. 😊
댓글 0
로그인하면 댓글을 작성할 수 있습니다.