7/ 12

JWT 공격

JWT는 서버가 서명을 검사하는 만큼만 믿을 수 있습니다. 이 플레이북은 캡처한 요청에서 토큰을 떼어 내 클레임을 읽고, 하나를 바꾸고, 다시 서명하고, 고전적인 검증 우회 페이로드(alg:none, 약한 비밀키 추측, 헤더 인젝션)를 쏘아 서버가 어느 것을 받아들이는지 알아냅니다. 약 10분을 잡으세요.

시작하기 전에. 테스트 권한이 있는 호스트에 대해 프로젝트와 스코프를 준비하고(엔게이지먼트 준비), JWT를 담은 플로우를 캡처하세요. Authorization: Bearer eyJ… 헤더가 흔한 출처입니다. 예시는 api.example.com을 대상으로 합니다.

1. JWT 탭으로 토큰 보내기

JWT 탭은 기본적으로 바 밖에 있습니다 — **0**을 누르고 "jwt"를 입력하거나 Ctrl-PGo to JWT를 쓰세요. 토큰을 자주 다룬다면 Preferences에서 아홉 슬롯 중 하나를 내주면 됩니다. 토큰을 찾습니다. 캡처한 플로우를 History에서 열고, 요청 상세에서 Bearer 뒤의 토큰 텍스트를 선택한 뒤 SpaceSend to JWT. 그러면 새 JWT 서브탭이 시드되고, Decode 렌즈에서 토큰이 header, payload, signature로 라이브 디코드됩니다.

디코드는 토큰이 주장하는 바를 보여 줄 뿐, 서명을 검사하지는 않습니다. 그래서 깔끔하게 디코드되는 토큰이라고 서버가 반드시 믿는 토큰은 아닙니다. 그것이 이 플레이북의 나머지가 답하는 질문입니다.

디코드된 HS256 토큰이 있는 gori JWT 탭: ^T:→ENCODE 렌즈 칩 아래의 INPUT 토큰, 디코드된 header JSON, 그리고 alg=none 대소문자 변형과 서명 제거를 포함한 생성된 페이로드의 ATTACKS 목록
JWT 탭은 토큰을 라이브로 디코드하고(header, payload, signature) 바로 보낼 수 있는 공격 페이로드를 나열합니다: alg:none, weak-secret, header injection.

체크포인트. JWT 탭에 토큰의 header와 payload가 JSON으로, 그 아래에 signature가 보입니다.

2. 클레임 변조

Ctrl-T로 Encode 렌즈로 전환하거나, l을 눌러 디코드된 토큰을 곧장 Encode 편집기로 불러오세요. PAYLOAD JSON을 편집합니다. role을 올리고, sub를 바꾸고, exp를 늘리세요. Ctrl-A로 알고리즘을 고르고(HMAC 계열 → RS/PS/ES의 256/384/512 → EdDSAnone 순환), HMAC 알고리즘이면 SECRET을 설정하거나 비대칭 알고리즘이면 KEY 카드에 PEM 개인키 경로를 지정하면, 다시 서명된 토큰이 OUTPUT에 라이브로 나타납니다. y로 복사하세요.

같은 클레임 편집이 헤드리스로도 실행되며, 토큰은 인수나 stdin에서 받습니다. --set KEY=VALUE는 클레임 하나를 패치하고(반복 가능), --payload는 클레임을 통째로 교체합니다:

gori run jwt eyJhbGci... --encode --set role=admin --secret s3cret       # 클레임 하나를 올림
gori run jwt eyJhbGci... --encode --payload '{"sub":"1","admin":true}' --secret s3cret

--set의 값은 JSON으로 파싱되면 그 타입을 유지하므로 admin=true는 불리언, role=admin은 문자열입니다. MCP에서도 jwt_encode가 같은 set / payload 편집을 받습니다.

토큰이 ES256 / RS256 / EdDSA이고 서명 키를 가지고 있다면 --key로 넘기고(PEM 본문 또는 경로), --verify가 디코드로는 답할 수 없는 질문에 답합니다:

gori run jwt eyJhbGci... --encode --alg ES256 --key ./private.pem --set role=admin
gori run jwt eyJhbGci... --verify --key ./public.pem                # verified: yes | no

체크포인트. OUTPUT에 편집한 클레임을 담고, 고른 알고리즘과 비밀키로 다시 서명된 토큰이 있습니다.

3. 공격 프리셋 실행

비밀키를 아는 경우는 드무니, 대신 gori가 우회 시도를 생성하게 하세요. Decode 렌즈에서 디코드된 부분 아래에, 토큰으로부터 만들어진 선택 가능한 공격 페이로드 목록이 있습니다:

공격 무엇을 시험하는가
alg:none 서명을 제거하고 algnone으로 설정합니다(None / NONE 대소문자 변형 포함). 서명 없는 토큰을 받아들이는 서버를 잡습니다.
Weak secret 흔한 약한 HMAC 비밀키 목록으로 다시 서명합니다. 추측 가능한 서명 키를 잡습니다.
Header injection kid, jku, x5u, jwk 헤더 파라미터를 조작합니다. 공격자가 제공한 키 자료를 신뢰하는 서버를 잡습니다.
Algorithm confusion 서버의 공개키가 필요합니다. RS/PS/ES 토큰을 HS256으로 낮추고 공개키 바이트 자체를 HMAC 키로 씁니다. 토큰의 alg로 분기하면서 검증 키를 재사용하는 서버를 잡습니다.

같은 세트를 헤드리스로 생성합니다:

gori run jwt eyJhbGci... --attacks
gori run jwt eyJhbGci... --attacks --key ./server-public.pem   # ...algorithm confusion까지

공개키는 대상이 공개하는 것이면 무엇이든 됩니다 — JWKS의 x5c 인증서도 순수 PUBLIC KEY 블록과 똑같이 동작하며, gori가 어느 쪽이든 서버가 들고 있을 SPKI PEM으로 환원합니다.

MCP에서는 jwt_attacks 도구가 같은 키를 public_key로 받아 동일한 목록을 반환합니다(jwt_decode / jwt_verify / jwt_encode가 1·2단계를 담당). 모두 --read-only에서도 쓸 수 있는 읽기 도구로, 네트워크를 건드리지 않고 아무것도 쓰지 않습니다. 다만 key / public_key를 경로로 주면 디스크에서 파일을 읽습니다.

세그먼트가 셋이 아니라 다섯인 암호화된 토큰은 JWE이며, 위 내용은 하나도 적용되지 않습니다. gori는 무엇을 보고 있는지 알 수 있도록 보호 헤더(alg, enc, kid)를 보여주고, 페이로드는 생성하지 않습니다 — 조작할 클레임 세그먼트도, 제거할 서명도 없기 때문입니다.

체크포인트. ATTACKS 목록이 바로 보낼 수 있는 토큰 변형들로 채워집니다.

4. 재생하고 확인하기

위조 토큰은 서버가 볼 때까지 아무것도 증명하지 못합니다. 페이로드를 하나 고르세요(2단계에서 변조한 토큰이든, 3단계의 프리셋이든). 그리고 Repeater로 보내, 캡처한 요청의 Authorization 헤더에 끼워 넣고 Ctrl-R로 다시 보냅니다. 잘못된 토큰에 대해 엔드포인트가 반환해야 할 것과 상태를 대조해 읽으세요:

  • 401 또는 403은 서버가 위조를 거부했다는 뜻입니다. 서명을 검증한 것입니다.
  • 거부를 기대한 자리에 200이 나오면 검증하지 않은 것입니다. 토큰이 내 조건대로 받아들여졌습니다.

체크포인트. 받아들여진 위조(성공 상태)와 거부된 위조(401 / 403)를 구분할 수 있고, 서버가 어떤 프리셋을(있다면) 통과시켰는지 압니다.

다음 단계