JWT Attacks
- -
JWT(JSON Web Token)
시스템 간에 암호화된 JSON 데이터를 전송하기 위한 표준 형이다. 이론적으로 JWT는 모든 종류의 데이터를 포함할 수 있지만 인증, 세션처리 및 액세스 제어 메커니즘으로 인하여 일반적으로 사용자의 정보를 보내는 데 사용된다.
서버에 필요한 모든 데이터는 JWT 자체 내에 클라이언트에 저장된다. 따라서 JWT는 사용자가 여러 백엔드 서버와 원할하게 상호작용해야 하는 고도로 분산된 웹 사이트에서 사용된다.
JWT Format
JWT는 header, payload, signature 3가지 파트로 구성되어 있다.
eyJraWQiOiI5MTM2ZGRiMy1jYjBhLTRhMTktYTA3ZS1lYWRmNWE0NGM4YjUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTY0ODAzNzE2NCwibmFtZSI6IkNhcmxvcyBNb250b3lhIiwic3ViIjoiY2FybG9zIiwicm9sZSI6ImJsb2dfYXV0aG9yIiwiZW1haWwiOiJjYXJsb3NAY2FybG9zLW1vbnRveWEubmV0IiwiaWF0IjoxNTE2MjM5MDIyfQ.SYZBPIBg2CRjXAJ8vCER0LA_ENjII1JakvNQoP-Hw6GG1zfl4JyngsZReIfqRvIAEi5L4HV0q7_9qGhQZvy9ZdxEJbwTxRs_6Lb-fZTDpW6lKYNdMyjw45_alSCZ1fypsMWz_2mTpQzil0lOtps5Ei_z7mM7M8gCwe_AGpI53JxduQOaB5HkT5gVrv9cKu9CsW5MS6ZbqYXpGyOG5ehoxqm8DL5tFYaW3lB50ELxi0KsuTKEbD0t5BCl0aCR2MBJWAbN-xeLwEenaqBiwPVvKixYleeDQiBEIylFdNNIMviKRgXiYuAvMziVPbwSgkZVHeEdF5MQP1Oe2Spac-6IfA
header랑 payload는 base64로 인코딩 되어 있다. header는 토큰의 메타 데이터가 포함돼있으며, payload는 사용자의 데이터가 포함되어 있다.
header랑 payload는 jwt에 대한 액세스 권한만 있는 모든 사람이 쉽게 읽고 수정할 수 있다.
따라서 모든 JWT 기반 메커니즘의 경우 signature를 통해 이루어진다.
서버는 토큰을 발행할 때 일반적으로 header와 payload를 해싱하여 signature를 생성한다. 경우에 따라서는 해시도 암호화를 한다. 이러한 과정에서 signature는 비밀 서명키를 포함한다. 그래서 토큰을 발행한 이후 토큰 내의 데이터가 변조되지 않았는지 확인한다.
jwt.io를 통하여 JWT의 형식을 자세하게 확인할 수 있다.
JWT Attack
JWT Attack은 JWT 수정하여 서버에 전송하는 것이다. 일반적으로 이미 인증된 사용자로 가장하여 액세스 제어를 무시하는 것이다. 해커가 임의의 값으로 유효한 토큰을 생성할 수 있는 경우 자신의 권한을 확대하거나 다른 사용자로 사칭하여 계정을 완전히 제어할 수 있다.
JWT 취약점은 일반적으로 애플리케이션 자체 내에서 결함이 있는 JWT 처리로 인해 발생한다. JWT의 처리는 설계상 상대적으로 유연하므로 개발자가 많은 구현 세부 사항을 스스로 결정할 수 있다. 그래서 이러한 취약점은 구현 결함으로 인하여 signature가 제대로 확인되지 않다는 것을 의미한다.
기본적으로 서버는 JWT에 대한 정보를 저장하지 않는다. 대산 각 토큰은 완전히 독립적인 엔티티이다,
이러한 방식은 몇 가지 장점이 있지만 근본적인 문제도 존재한다. 서버는 실제로 토큰의 내용이나 원래 signature가 무엇인지 모르기 때문에 제대로 확인하지 않으면 해커가 임의로 토큰을 변경할 수 있다.
Accepting arbitrary signatures
JWT 라이브러리는 일반적으로 토큰을 확인하고, 토큰을 디코딩한다.
node.js의 jsonwebtoken 라이브러리에는 verify() 하고 decode()가 있다.
const jwt = require('jsonwebtoken');
// data 입력, exp: 만료시간 설정, KEY: secret key로 암호화
const token = jwt.sign(data, exp, KEY);
// verify()로 KEY를 활용하여 유효성을 검사하고 값을 가져옴
const payload = jwt.verify(token, KEY);
// decode()는 유효성 검사 없이 토큰의 실제 값만 가져옴
jwt.decode(token);
jwt.decode()를 사용할 경우 signature를 확인하지 않아서 JWT를 변조할 수 있다.
payload의 data만 변조하여 액세스 제어를 할 수 있다.
Accepting tokens with no signature
JWT header에는 alg가 있다. alg 매개변수는 토큰을 서명하는 데 사용된 알고리즘과 서명을 확인할 때 사용해야 하는 알고리즘을 가리킨다.
서버는 현재 확인되지 않은 토큰의 사용자의 입력을 신뢰하는 암묵적으로 신뢰하는 것 외에는 선택이 없기 때문에 본질적으로 결함이 있다.
JWT는 다양한 알고리즘을 사용하여 서명할 수 있지만 서명하지 않은 상태로 둘 수도 있다. 이 경우 alg는 none으로 설정되므로 위험하므로 일반적으로 이러한 JWT는 거부한다. 하지만 다양한 우회방법을 통해 이러한 필터링을 우회할 수 있다.
none으로 설정하고 signature를 없애면 액세스 제어를 할 수 있다.
Brute forcing secret keys
HS256(HMAC+SHA-256)과 같은 일부 서명 알고리즘은 임의의 독립 문자를 secret key로 사용한다. JWT를 구현할 때 개발자는 하드 코딩된 암호를 변경하지 않는 것과 같은 실수를 범하기도 하는 등 실수를 범하기도 한다. 개발자가 잘 알려진 키를 사용하는 경우 brute force를 통해 secret key를 찾을 수 있다.
보통 hashcat를 사용하여 secret key를 brute force 할 수 있다.
eyJraWQiOiI3OTU1ZDkyMy0yMDE4LTQ2YjItYjI2NS02MDBkZjEwMzg0OGEiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY4MjkyNzM3OH0.uwkEkHhuR9RCdzwkxIV9gZgt0yA17buVezbv2XHngQg
hashcat으로 secret key가 secret1인 것을 확인했다.
secret key를 넣으면 signature가 같아지는 것을 확인할 수 있다.
JWT header parameter injections
JWS에 따르면 alg는 필수이다. 그러나 실제로 JWT header에는 여러 매개변수가 포함된다.
- jwk(json web key) - key를 나타내는 json 객체를 제공
- jku(json web key set URL) - 서버가 올바른 키를 포함하는 키 세트를 가져올 수 있는 URL을 제공
- kid(key id) - 선택할 key가 여러개인 경우 서버가 올바른 키를 식별하는 데 사용할 수 있는 id를 제공
다양한 매개변수는 서명을 확인할 때 사용할 키를 서버에게 알려준다. 그래서 이런 매개변수를 악용하여 서버의 비밀키가 아닌 공격자의 임의의 키를 사용하여 서명된 수정된 JWT를 주입할 수 있다.
JWT header에 대한 예시이다.
원래 서버는 공개 키를 whitelist로 사용하여 JWT signature를 확인해야 한다. 만약 jwk 매개변수에 포함된 key를 사용한 경우 취약할 수 있다.
이러한 경우 jwk의 RSA 개인 키를 사용하여 수정된 JWT에 서명한 다음 공개 키를 header에 삽입하여 악용할 수 있다.
Injecting self-signed JWTs via the jwk parameter
JWS에서 jwk는 토큰 자체에 공개키를 삽입하는 데 사용하는 매개변수이다.
원래 서버는 공개키를 whitelist로 관리하여 JWT signature를 확인한다. 하지만 때때로 jwk에 포함된 키를 사용하여 취약점이 발생하기도 한다.
그래서 자체 RSA의 개인키를 사용하여 JWT에 서명한 다음 jwk에 일치하는 공개키를 삽입하여 악용할 수 있다.
Injecting self-signed JWTs via the jku parameter
JWT는 jwk에서 jku 매개변수를 사용하여 키를 참조할 수 있다. 그래서 signature를 확인할 때 jku의 URL에서 관련 키를 가져온다.
jku는 /.well-known/jwks.json처럼 신뢰할 수 있는 도메인에서만 키를 가져온다. 하지만 URL구문 불일치를 이용하여 URL에 대한 필터링을 우회할 수 있다.
Injecting self-signed JWTs via the kid parameter
서버는 JWT뿐만 아니라 다양한 종류의 데이터에 서명하기 위해 여러 암호화 키를 사용한다. 그래서 JWT 헤더에 signature를 확인할 때 사용하는 키를 식별하는 kid를 사용하기도 한다.
키는 JWK에 저장된다. 그래서 kid를 이용하여 토큰과 동일한 JWK를 찾는다. 하지만 JWS에는 kid에 대한 구체적인 정의가 없다. 단지 개발자가 설정한 임의의 문자열이다. 그래서 kid를 이용하여 파일에 접근하거나 DB에 접근할 수 있다.
directory traversal에 취약하면 해커는 kid에 적용하여 임의 파일을 확인 키로 사용하도록 할 수 있다.
서버가 대칭 알고리즘을 사용하여 JWT를 서명하는 경우 임의 파일을 가리키게 하여 JWT에 서명하여 우회할 수 있다.
/dev/null은 이론적으로 모든 파일에 대한 작업을 수행할 수 있지만 간단하게는 빈 파일을 가리키게 하여 서명하면 유효하게 작동된다.
Other JWT header parameters
- cty(Content type) - JWT는 콘텐츠 유형을 선언할 수 있다. signature 우회를 할 수 있는 경우 text/xml, application/x-java-serialized-object로 설정하여 XXE나 역직렬화 공격의 vector로 사용할 수 있다.
- x5c(X.509 Certificate Chain) - JWT를 서명하는 데 사용하는 키 중 x.509 공개 키를 사용하는 경우가 있다. 이때 JWT header injection와 유사하게 자체 서명한 인증서를 삽입하는 경우 해당 JWT는 취약하다.
Algorithm Confusion Attack
Algorithm Confusion Attack(이하 ACA)은 해커가 개발자의 의도와 다른 알고리즘을 사용하여 서버가 JWT signature를 확인할 수 있을 때 발생한다.
이 공격을 이용하면 해커는 서버의 비밀키를 알 필요 없이 임의의 값을 넣어서 JWT를 위조할 수 있다.
ACA는 일반적으로 jwt 라이브러리의 결함으로 발생한다. 키를 확인하는 프로세스는 사용된 알고리즘에 따라 다르지만 대부분은 signature를 확인하기 위해 알고리즘에 영향을 받지 않는 단일 방법을 사용한다. 그래서 header의 alg 매개변수를 통해 검증 유형을 결정한다.
function verify(token, Key){
alg = token.getAlgHeader();
if(alg == "RS256"){
// Use the provided key as an RSA public key
}
else if(alg == "HS256"){
// Use the provided key as an HMAC secret key
}
}
개발자가 RS256과 같은 비대칭 알고리즘을 사용하여 서명된 JWT를 배타적으로 처리할 경우 verify함수는 항상 고정된 공개키를 return 한다.
publicKey = <public-key-of-server>;
token = request.getCookie("session");
verify(token, publicKey);
이 경우 서버가 HS256과 같은 대칭 알고리즘을 사용하여 서명된 토큰을 받으면 verify()는 공개 키를 HMAC의 secret로 처리한다. 그러면 해커는 HS256과 공개 키를 사용하여 토큰에 서명할 수 있으며, 서버는 동일한 공개 키를 사용하여 확인한다.
ACA Perform
- Obtain the server's public key
- Convert the public key to a suitable format
- Create a malicious JWT with a modified payload and the alg header set to HS256
- Sign the token with HS256, using the public key as the secret
1. Obtain the server's public key
서버는 /jwks.json 또는 /.well-known/jwks.json에 매핑된 표준 endpoint를 통해 공개 키를 노출하는 경우가 있다.
키가 노출되지 않더라도 JWT를 이용하여 키를 추출할 수 있다.
2. Convert the public key to a suitable format
서버는 공개키를 JWK형식으로 노출하지만 토큰의 signature를 확인할 때는 로컬 파일이나 db에서 키 복사본을 사용한다.
그래서 공격을 하려면 JWT signature에 사용하는 키 버전이 서버의 로컬 사본과 동일해야 한다. 또한 모든 바이트가 일치해야 한다.
jwks.json 파일을 통해 JWK를 얻는다.
얻은 JWK를 가지고 새로운 RSA를 만든다. 그리고 만든 형식을 PEM으로 받아서 base64로 인코딩한다.
그리고 인코딩한 값을 가지고 새로운 JWK형식의 키를 만든다.
3. Create a malicious JWT with a modified payload and the alg header set to HS256
JWT를 변조하여 header를 RSA256에서 HS256으로 변경한다.
4. Sign the token with HS256, using the public key as the secret
변조한 JWT를 새로운 JWK형식의 키를 가지고 HS256 알고리즘으로 서명한다.
이처럼 RSA256과 HS256 알고리즘의 혼동으로 JWT를 변조할 수 있다.
'Hacking > Web' 카테고리의 다른 글
Web cache poisoning (0) | 2023.05.14 |
---|---|
Click jacking (0) | 2023.04.25 |
Directory Traversal (0) | 2023.04.22 |
DNS Rebinding Attack (0) | 2023.04.16 |
SOP와 CSP (0) | 2022.05.20 |
소중한 공감 감사합니다