JWT(JSON Web Token) Attacks
JWT Attack이란?
JWT Attack이란, JWT의 설계 문제와 잘못된 처리 방식으로 인해 생기는 취약점입니다. (JWT란?)
JWT Attack의 종류
JWT Attack은 아래와 같은 방법들을 통해 이루어집니다.
- 서명 검증의 결함으로 인한 JWT 공격
- 서명이 없는 토큰이 수락된 경우의 JWT 공격
- Secret Key 무차별 대입 공격
- hashcat을 이용한 brute force
- JWT Header Parameter Injection
- jwk Injection
- jku Injection
- kid Injection
- JWT 알고리즘 혼동 공격
이번 포스팅에서는 서명 검증의 결함으로 인한 JWT 공격과 JWT Header Parameter Injection에 대해 다루도록 하겠습니다.
서명 검증의 결함으로 인한 JWT 공격
해당 공격에 대한 실습은 로컬 환경에서 진행하였습니다. (JWT PHP 구현)
JWT의 시그니처를 제대로 검증하지 않는 경우와 같이 서명에 대한 검증이 철저하게 이루어지지 않을 때 발생하는 취약점입니다.
위와 같이 서명을 검증하는 과정이 제대로 이루어지지 않은 상태에서 payload의 name 필드의 값이 admin이라면 회원 정보를 모두 확인할 수 있는 로그인 환경을 구현했습니다.
로그인 시 위와 같이 사용자 정보를 확인하는 페이지로 넘어가고,
위와 같은 내용의 토큰이 부여된 것을 확인할 수 있습니다. 하지만 서명 검증 없이 name의 필드가 admin이라면 관리자 페이지를 조회할 수 있기 때문에 해당 토큰의 페이로드를 변조하여 우회해 보겠습니다.
위의 토큰을 요청에 함께 제출하게 되면
로그인이 우회되어 위와 같이 관리자 페이지가 뜨는 것을 확인할 수 있었습니다.
public function dehashing($token) {
list($header, $payload, $signature) = explode('.', $token);
$new_signature = hash_hmac('sha256', "$header.$payload", $this->secretKey, true);
$new_signature = $this->base64UrlEncode($new_signature);
if ($signature===$new_signature){
return true;
}
else{
echo "<script>alert('서명 검증 실패');</script>";
return false;
}
}
때문에 위와 같이 서명 검증을 철저하게 진행하여야 합니다.
JWT Header Parameter Injection
Header에는 해시 알고리즘과 토큰의 타입, 시크릿 키가 명시되는데, 이때 사용되는 헤더 필드를 주입하여 서명에 사용되는 시크릿 키를 조작할 수 있습니다.
해당 포스팅에서는 jwk, jku, kid Injection에 대해 다루도록 하겠습니다.
실습 환경은 모두 Portswigger에서 진행되었습니다.
jwk Injection
jwk는 JWT 서명에 사용되는 시크릿 키를 나타내는 JSON 객체를 포함할 수 있는 헤더 필드입니다.
다음 실습은 jwk injection으로 일반 유저로 로그인 후 관리자로 권한 상승을 통해 특정 유저를 삭제하는 실습인데요.



위와 같이 문제에서 부여된 계정으로 로그인 후 /admin 엔드포인트로 접근하면

위와 같이 administrator가 아니라면 해당 페이지를 사용할 수 없다고 뜨는 것을 확인할 수 있었습니다.

해당 엔드포인트에 접속하는 요청을 확인해 보니

위와 같이 sub가 wiener으로 설정되어 있는 것을 확인할 수 있었습니다. 하지만, 그냥 이를 변조시키기에는 RSA 알고리즘을 통해 암호화가 이루어져 있기에 공개 키에 맞는 개인 키로 서명을 해야 하여 우회를 할 수 없게 됩니다.

때문에 burp suite의 기능을 통해 RSA 키 쌍을 하나 만들고,

임베디드 JWK 공격 기능을 통해 만든 RSA 키로 서명 알고리즘을 작성해 주면,

RSA 공개 키에 대한 내용이 헤더의 jwk라는 필드로 추가되고, sub까지 직접 administrator로 바꿔주면

위와 같이 새로운 토큰이 생성됩니다.

토큰 헤더의 내용을 확인해 보면 바뀐 내용의 토큰인 것을 알 수 있었고, 다시 /admin 엔드포인트에 접근해 보면

위와 같이 접근이 허용된 것을 확인할 수 있었습니다.

이제 delete를 눌렀을 때 넘어가는 /admin/delete?username=(유저명) 엔드포인트에서도 이 전에 생성되었던 토큰을 붙여 넣어주면

해당 유저 정보가 삭제되며 LAB을 클리어할 수 있었습니다.
jku Injection
jku란 RSA 공개 키 세트가 포함된 URL을 통해 서명에 사용된 공개 키를 지정해 주는 필드로 jwk 필드를 사용하지 못하는 경우에 RSA 알고리즘을 우회할 때 사용되는 헤더 필드입니다.
아래의 실습도 jku injection으로 일반 유저로 로그인 후 관리자로 권한 상승을 통해 특정 유저를 삭제하는 실습인데요.

이번에도 같은 조건이기에 부여받은 계정으로 로그인 후 토큰을 확인해 보았습니다.


토큰의 내용은 위와 같았고 이번에는 jku 필드를 활용해 보겠습니다.

아까와 같이 RSA 키 쌍을 생성해 주고, 키에 대한 데이터를 복사해 준 후


실습 랩에서 제공하는 익스플로잇 서버(외부 서버)에 { "keys":[ ... ] }의 대괄호 안에 키 데이터를 붙여 넣어 저장해 주었습니다.

그리고 jku 헤더 필드를 추가하여 익스플로잇 서버(외부 서버)의 링크를 붙여 넣고, sub를 administrator로 바꾸어

생성한 RSA 개인 키로 서명하여

토큰을 변조하였습니다. 해당 토큰으로

/admin 엔드포인트에 접근할 수 있었고,

/admin/delete?username=(유저명) 엔드포인트도 똑같이 토큰을 붙여 넣어

LAB을 클리어할 수 있었습니다.
kid Injection
kid 필드는 여러 키를 식별하고 관리하기 위한 헤더 필드로 데이터베이스의 특정 항목이나 파일 이름을 통해서도 Key ID를 지정해 줄 수 있습니다.

kid injection의 경우 서버의 데이터에 접근할 수 있는 필드이기에 다음과 같은 조금 더 다양한 공격이 가능합니다.
- Directory Traversal
- 공개키 조작으로 임의 서명 허용 (File Upload Vulnerability)
- SQL Injection
- 서명 검증 무력화 (None)
- 권한 상승 (관리자의 key ID로 kid 변경)

해당 실습도 위의 두 실습과 같은 조건입니다. 공개키 조작을 통해 풀어보도록 하겠습니다.


이번에도 로그인 후 /admin 엔드포인트에 위와 같은 요청이 가는 것을 확인했습니다. 앞서 실습과 다른 점이 있다면, 알고리즘이 SHA256으로 되어있다는 점입니다.

때문에 이번에는 대칭키인 SHA를 생성하였는데, 이때

k(비밀 키 값)을 AA==로 설정해 줍니다. 그 이유는 아래에서 설명해 보겠습니다.

kid의 값을 /../../../../dev/null(빈 파일)로 설정해 주었는데, 때문에 서버가 암호화할 알고리즘을 kid 필드를 통해 찾는 과정에서 빈 값으로 들어가게 됩니다. 때문에 위에서 SHA 키를 만들 때 해당 값을 AA==로 정해주어 base64로 빈 바이트를 설정해 준 것인데요. 이렇게 대칭을 맞춰주어야 유요한 암호화가 되고, 우회가 되는 것입니다.


이렇게 빈 값이 포함된 SHA로 암호화 서명을 진행해 주어

조작된 토큰으로

/admin에 접근할 수 있었습니다.

/admin/delete?username=(유저명) 에도 똑같은 토큰으로 접근해

유저를 삭제하여 LAB을 클리어할 수 있었습니다.
대응 방안
서명 검증의 결함으로 인한 JWT 공격에 대한 대응 방안은 위에서 미리 소개해드렸고, JWT Header Parameter Injection에 대한 대응방안은 다음과 같습니다.
- jwk Injection
- 해당 필드 비활성화
- key store에 존재하는 key만 허용하도록 설정
- jku Injection
- 외부에서 제공하는 URL을 허용하지 않도록 설정 (특정 URL만 화이트 리스트로 허용)
- kid Injection
- 화이트 리스트를 통해 특정 key ID만 허용하도록 설정
- 경로가 포함되는 경우, .. 또는 / 와 같은 문자열 필터링