가이드

JWT 보안 테스트

JWT 탭은 JSON Web Token을 위한 워크벤치입니다. 토큰을 디코드하고, claim을 편집해 재서명하며, 서버를 상대로 테스트할 고전적인 공격 페이로드를 생성합니다. 파트를 보여주기만 하는 Decoder의 읽기 전용 jwt-decode 컨버터보다 한 걸음 더 나아갑니다.

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

어디서든(예: History 상세 패널, Notes 등) 토큰을 선택하고 Space S(Send selection to…) → j JWT를 누르면 그 토큰으로 새 워크벤치 서브탭을 채웁니다. 세션은 휘발성이라 디스크에는 아무것도 기록되지 않습니다. 이 탭은 기본적으로 바 밖에 있습니다. **0**을 누르고 "jwt"를 입력하거나, 커맨드 팔레트(Ctrl-P → Go to JWT)를 쓰거나, Preferences에서 슬롯을 내주세요.

두 개의 렌즈

세션 하나를 두 가지 뷰로 보며, Ctrl-T로 전환합니다. 각 렌즈의 최상위 카드 테두리에 전환 칩이 있습니다(INPUT에는 ^T:→ENCODE, HEADER에는 ^T:→DECODE). 클릭해도 키와 똑같이 동작합니다:

  • Decode: INPUT에 토큰을 붙여 넣으면 header, payload, signature가 실시간으로 디코드됩니다. 그 아래에는 생성된 공격 페이로드를 고를 수 있는 목록이 있습니다.
  • Encode: HEADER와 PAYLOAD를 JSON으로 편집하고, 알고리즘을 선택하며(Ctrl-A로 HMAC 계열 → RS/PS/ES의 256/384/512 → EdDSA → none 순환), SECRET을 설정하면 재서명된 토큰이 OUTPUT에 실시간으로 나타납니다. 비대칭 알고리즘에서는 카드가 KEY로 바뀌며, 입력한 시크릿 대신 PEM 개인키 경로를 받습니다.

Space → Load decoded claims는 현재 Decode 쪽에서 디코드된 토큰을 Encode 편집기로 불러옵니다. 그래서 claim 하나를 손보고 두 동작만으로 재서명할 수 있습니다. 결과는 y로 복사하세요.

signature는 디코드되어 표시되지만 Decode 쪽에서는 검증되지 않습니다. 따라서 디코드는 토큰이 무엇을 주장하는지 알려줄 뿐, 신뢰할 수 있는지는 알려주지 않습니다. Encode는 지정한 키와 알고리즘으로 실제로 서명하고, 키를 가지고 있다면 gori run jwt --verify(또는 MCP jwt_verify)가 신뢰 여부에 답합니다.

알고리즘

HMAC(HS256 / HS384 / HS512)은 직접 입력한 시크릿으로 서명합니다. 비대칭 계열은 사용자가 제공한 PEM 키로 서명합니다. gori는 키를 만들지 않으므로, RS256 토큰을 위조하려면 여전히 RS256 개인키가 필요합니다:

계열 알고리즘 키
HMAC HS256 HS384 HS512 공유 시크릿을 그대로 입력
RSA PKCS#1 RS256 RS384 RS512 서명은 RSA 개인키, 검증은 공개키 / 인증서
RSA-PSS PS256 PS384 PS512 같은 RSA 키. salt 길이는 다이제스트 길이(RFC 7518 §3.5)
ECDSA ES256 ES384 ES512 P-256 / P-384 / P-521 키. 서명은 JOSE가 규정한 고정 폭 r‖s 형식이라 ES512는 128이 아니라 132바이트입니다
EdDSA EdDSA Ed25519 키
미서명 none 키 없음: 의도적으로 제공하는 인증 우회 형태

키는 PEM 본문을 그대로 넣거나 .pem 파일 경로로 지정합니다. 검증에는 X.509 인증서(IdP가 x5c로 공개하는 것)나 개인키 자체도 받습니다.

암호화된 토큰(JWE)

header.encrypted_key.iv.ciphertext.tag 형태의 5-세그먼트 토큰은 JWE, 즉 암호화된 JWT입니다. gori는 JWS를 인식하는 모든 곳(JWT 탭, Decoder, 본문 pretty-printer, gori run jwt, MCP jwt_decode)에서 JWE도 인식하고 보호 헤더를 보여줍니다: 콘텐츠 암호화 키를 감싼 alg, 클레임을 암호화한 enc, 그리고 kid.

gori는 복호화하지 않습니다. 키가 없으면 클레임은 암호화됨으로 표시되고 그 자리에 아무것도 추측해 넣지 않으며, 클레임 위에서만 의미가 있는 투영은 토큰을 아예 거부합니다: --attacks는 아무것도 생성하지 않고(조작할 클레임 세그먼트도, 제거할 서명도 없습니다), --verify는 지원하지 않는 알고리즘이라고 말하는 대신 그 사실을 그대로 알려줍니다.

공격 페이로드

디코드된 토큰에서 gori는 흔한 JWT 검증 결함을 찔러보는, 바로 전송 가능한 변형을 생성합니다:

공격 무엇을 테스트하는가
alg:none signature를 제거하고 alg를 none으로 설정합니다(그리고 None / NONE / nOnE 대소문자 변형 포함). 서명 없는 토큰을 받아들이는 서버를 겨냥합니다. 원래 header를 유지하는 페이로드가 둘 더 있습니다: signature stripped(세 번째 세그먼트가 빈 토큰)와 no signature segment(세그먼트가 둘뿐인 토큰).
Weak secret 흔한 약한 HMAC secret 목록으로 토큰을 재서명해, 추측 가능한 서명 키를 잡아냅니다.
Header injection kid, jku, x5u, jwk header 파라미터를 조작합니다. 공격자가 제공한 키 자료를 신뢰하는 서버를 겨냥합니다.
Algorithm confusion 서버의 공개키를 줬을 때만 생성됩니다(--attacks --key, MCP public_key). RS/PS/ES 토큰을 HS256으로 낮추고 공개키 바이트 자체를 HMAC 키로 써서 서명합니다. 토큰의 alg로 분기하면서 검증 키를 그대로 재사용하는 서버를 겨냥합니다. 서버가 가지고 있을 법한 표기마다 페이로드를 하나씩 만듭니다: OpenSSL이 쓰는 정규 SPKI PEM, 그리고 준 그대로의 바이트를 끝 개행 있는 형태와 없는 형태로.

강조된 페이로드를 y로 복사해 Repeater 세션(또는 이미 편집 중인 요청)에 붙여 넣고 대상을 상대로 시도하세요.

헤드리스

gori run jwt eyJhbGci...                       # decode (default)
gori run jwt eyJhbGci... --encode --alg HS256 --secret s3cret
gori run jwt eyJhbGci... --encode --set role=admin --secret s3cret   # claim 하나를 패치해 재서명
gori run jwt eyJhbGci... --encode --payload '{"sub":"1","admin":true}' --secret s3cret   # claim 전체를 교체
gori run jwt eyJhbGci... --encode --alg ES256 --key ./private.pem     # 비대칭 키로 서명
gori run jwt eyJhbGci... --verify --key ./public.pem                  # 이 토큰은 유효한가?
gori run jwt eyJhbGci... --attacks             # print the attack payloads
gori run jwt eyJhbGci... --attacks --key ./public.pem                 # ...algorithm confusion까지
cat token.txt | gori run jwt --attacks         # token from stdin

토큰은 인자나 stdin에서 옵니다. 프로젝트나 캡처는 관여하지 않습니다(순수한 로컬 연산입니다). --format은 text 또는 json입니다. --encode에서 --set KEY=VALUE는 claim 하나를 패치하고(반복 가능. 값이 JSON으로 파싱되면 그 타입을 유지하므로 admin=true는 불리언, role=admin은 문자열입니다), --payload JSON은 claim 객체 전체를 교체합니다. 둘은 상호 배타적입니다. --secret과 --key는 같은 자리를 채우므로 하나만 넘기세요. --verify에는 --secret이나 --key가 필요하고(빈 secret을 확인하려면 --secret ''), verified: yes|no와 함께 no일 때는 reason을 출력하며, gori run cookie --verify처럼 토큰이 검증될 때만 0으로, 그렇지 않으면 1로 종료합니다. --format json에서는 no에 분기용 code도 붙습니다: 다른 키로 성공할 수 있는 것은 signature_mismatch와 key_mismatch(토큰의 alg에 맞지 않는 종류의 키)뿐이고, unsigned, alg_unsupported, signature_malformed, jwe 등은 어떤 키로도 검증되지 않는다는 뜻입니다. --secret과 --key를 함께 넘기면 --secret ''이라도 거부됩니다. CLI Reference를 참고하세요.

MCP에서는 jwt_decode / jwt_verify / jwt_encode / jwt_attacks가 --read-only에서도 사용할 수 있는 read 도구입니다. 네트워크를 건드리지 않고 아무것도 쓰지 않습니다. jwt_encode도 같은 set / payload claim 편집과 같은 key를 받고, jwt_attacks는 public_key를 받습니다. 다만 key / public_key는 경로도 받으므로 이 두 인자는 호출자가 지정한 파일을 읽습니다. 클라이언트가 파일시스템에 닿지 않아야 한다면 PEM을 인라인으로 넘기세요.

다음 단계

  • Decoder: 더 긴 변환 체인 안에서 JWT를 디코드합니다
  • Sequencer: JWT가 아닌 토큰의 무작위성을 평가합니다
  • Repeater & Fuzzer: 공격 페이로드를 대상에 발사합니다