로그 한 줄에서 시작하는 조사
필드 여섯 개를 순서대로 열어 만료된 토큰을 찾아냅니다. 실제로 돌려 본 조사 흐름입니다.
경보가 왔고 로그에 이 줄이 있습니다.
2026-08-31T09:15:30.250Z WARN [api] req=01M1BHMD2A
user=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1LTQyIiwiZXhwIjoxNzg4MTAxODM0fQ.x
ip=203.0.113.42 dur=1834ms
payload=H4sIAAAAAAAAE6tWyi9KSS1SslJy1DVU0lEqLKlUsjKqBQBaiwz9FwAAAA==
hash=5d41402abc4b2a76b9719d911017c592
값 넷이 인코딩되어 있습니다. 순서대로 엽니다.
1단계 — 각 값이 무엇인지 판별
접두사와 길이로 갈립니다.
req 01M1BHMD2A… 26자, 하이픈 없음 → ULID
user eyJhbGci… eyJ 로 시작 → JWT
payload H4sIAAAA… H4sI 로 시작 → gzip 을 base64 로
hash 5d41402abc4b2a76… 32자 hex → MD5
eyJ는 {"의 base64이고, H4sI는 gzip 매직 바이트 1f 8b 08의 base64입니다.
2단계 — ULID에서 시각을 꺼낸다
ULID 앞 10자가 유닉스 밀리초입니다. Crockford base32로 디코딩합니다.
01M1BHMD2A → 2026-08-31T09:15:30.250Z
로그 타임스탬프와 정확히 같습니다. 요청 ID가 서버에서 생성됐고 로그가 그 직후에 찍혔다는 뜻입니다.
만약 두 값이 크게 벌어져 있다면 그 사이에 지연이 있었다는 단서가 됩니다 — 큐에 머물렀거나, 재시도였거나.
3단계 — JWT를 연다
세 조각을 .으로 나눠 앞 두 조각을 base64url 디코딩합니다.
헤더 {"alg":"HS256"}
페이로드 {"sub":"u-42","exp":1788101834}
exp를 사람이 읽는 시각으로 바꿉니다.
exp 2026-08-30T14:57:14.000Z
요청 시각 2026-08-31T09:15:30.250Z
18시간 전에 만료된 토큰입니다.
여기서 조사 방향이 정해집니다. 클라이언트가 만료된 토큰을 계속 보내고 있거나, 갱신 로직이 동작하지 않았거나 — 어느 쪽이든 dur=1834ms라는 긴 응답 시간과 함께 볼 값이 생겼습니다.
서명은 확인하지 않았습니다. 디코딩은 키 없이 되지만 검증은 키가 필요합니다. 이 단계에서는 "무엇이 담겼나"만 봅니다.
4단계 — payload 압축을 푼다
base64로 디코딩한 뒤 gzip을 풉니다.
{"order":"A-1","qty":2}
23바이트짜리 JSON이 gzip과 base64를 거쳐 60자가 됐습니다. 원본보다 커졌습니다 — 짧은 값을 압축하면 헤더가 본문보다 큽니다.
이것 자체가 발견입니다. 이 경로에서 압축이 이득을 내지 못하고 있다는 뜻입니다.
5단계 — 해시를 대조한다
32자 hex는 MD5입니다. 값이 무엇의 해시인지는 후보를 넣어 봐야 압니다.
MD5("hello") = 5d41402abc4b2a76b9719d911017c592
일치합니다. 요청 본문의 체크섬으로 쓰이는 값이라면, 실제 본문과 대조해 전송 중 변형이 있었는지 볼 수 있습니다.
조사 순서가 도구 순서다
1 필드를 나눈다 줄 정렬·중복 제거
2 접두사로 무엇인지 판별 (표를 본다)
3 식별자에서 시각을 꺼낸다 Epoch 변환
4 토큰을 연다 JWT 디코더
5 압축·인코딩을 푼다 Base64 → Gzip
6 해시를 대조한다 MD5 / SHA-256
이 순서가 중요한 이유는 각 단계가 다음 단계의 입력을 만들기 때문입니다. ULID의 시각을 먼저 꺼내야 JWT의 exp와 비교할 대상이 생기고, base64를 먼저 풀어야 gzip인지 알 수 있습니다.
여러 줄을 다룰 때
로그 한 줄이 아니라 수천 줄이면 앞 단계가 달라집니다.
타임스탬프가 유닉스 초로 찍혀 있으면 일괄 변환이 필요합니다. IP나 URL만 뽑아 빈도를 보려면 추출이 먼저입니다. 중복 요청을 찾으려면 정렬과 중복 제거를 먼저 겁니다.
값 하나를 깊이 파는 것과 전체에서 패턴을 찾는 것은 도구 순서가 반대입니다.
확인 순서
- 인코딩된 값을 만나면 앞 4자로 종류를 먼저 정합니다
- 식별자에 시각이 들어 있으면 로그 타임스탬프와 대조합니다. 지연 구간이 보입니다
- JWT는 디코딩과 검증이 다릅니다. 조사 단계에서는 내용만 봅니다
- 압축된 값이 원본보다 큰지 봅니다. 짧은 페이로드에서 흔합니다
- 여러 줄을 다룰 때는 추출·정렬·중복 제거를 먼저 겁니다
tools.onuel.dev에서 이 순서를 그대로 밟을 수 있습니다. JWT 디코더가 세 조각을 나눠 주고, Base64 디코딩과 Gzip이 payload를 풉니다. 타임스탬프는 Epoch 변환, 여러 줄이면 Epoch 일괄 변환이고, 로그에서 주소만 뽑을 때는 이메일·URL 추출기, 중복 요청을 찾을 때는 줄 정렬·중복 제거를 씁니다.
- #디버깅
- #로그
- #조사