JWT
1.JWT的基础知识
-
JWT是什么
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地作为 JSON 对象传输信息。它通常用于身份验证和授权。JWT由三个部分组成:头部、载荷和签名。
作为 JSON 对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
-
JWT用于什么?
jwt主要用于用户登录鉴权
2.JWT的数据结构
-
JWT的数据结构
JWT由三个部分组成:头部,载荷,签名
JWT中的加密数据是根据
.
号进行连接的,一共是三段xxxxxx.yyyyyy.zzzzzz 头部.有效载荷.签名 Header.Payload.Signature
-
结构解析
以下面这段JWT token为例
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
使用在线JWT解码平台:https://jwt.io/
2.1 头部(Header)
-
头部(Header),描述JWT源数据的JSON对象
{ "alg": "HS256", "typ": "JWT" } //可以看出使用的是HS256算法进行签名 //令牌的类型是JWT
-
默认字段
alg:表示签名使用的算法 type:表示令牌的类型
2.2 荷载(payload)
-
荷载(payload),包含需要传递的数据,通常使用JSON对象表示并使用Base64编码
Payload中包含三个类型的字段:注册声明、公共声明和私有声明
这里就有可能存在信息泄露,默认情况下jwt是未加密的,在进行解码时就可以查看到一些敏感信息
公共声明(Public Claims):是自定义的字段,用于传递非敏感信息,例如:用户ID、角色等
私有声明(Private Claims):是自定义的字段,用于传递敏感信息,例如密码、信用卡号等
注册声明(Registered Claims):预定义的标准字段,包含了一些JWT的元数据信息,例如:发行者、过期时间等
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } //sub:主题 //name:名字 //iat:JWT的签发时间
注册声明的默认字段
iss(issuer):签发人/发行人 sub (subject):主题 aud (audience):用户 exp (expiration time):过期时间 nbf (Not Before):生效时间,在此之前是无效的 iat (Issued At):签发时间 jti (JWT ID):用于标识该 JWT
2.3 签名(Signature)
-
签名(Signature),用于验证JWT的完整性和真实性
Signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),"secret") //secret保存在后端,就是来解析确定验证的key
-
签名生成流程
- 首先需要指定一个secret(是用于签名和验证的密钥),该secret仅仅保存在服务器中,保证不能让其他用户知道
- 这个部分
需要 base64URL 加密后的 heade
和base64URL 加密后的 payload
使用.
连接组成的字符串 - 通过header中声明的加密算法进行加密secret组合加密
- 得出一个签名哈希,就是Signature,且无法反向解密
2.4 JWT的工作流程
- jwt在服务器中的工作流程:
- 用户使用账号、密码登录应用,登录的请求发送到 Authentication Server(身份验证服务器)。
- Authentication Server 进行用户验证,然后创建 JWT 字符串返回给客户端。
- 客户端请求接口时,在请求头带上 JWT。
- Application Server 验证 JWT 合法性,如果合法则继续调用应用接口返回结果。
3.知识补充
- JWS:即JSON Web Signature,Signed JWT,签名过的JWT
- JWE:Encrypted JWT部分payload经过加密的JWT
- JWK:JWT的密钥,也就是我们常说的SECRET
- JKU:JKU(JSON Web Key Set URL)是JWT Header中的一个字段,字段内容是一个URI,该URI用于指定用于验证令牌秘钥的服务器,该服务器用于回复JWK
-
密钥的基本了解
symmetric:对称密钥
RSA:公钥密码
ECC:是一种基于椭圆曲线数学原理的加密算法,其密钥由一对(公钥、私钥)组成
OKP(Octet Key Pair)密钥是一种用于加密和身份验证的公钥密码学密钥对。它基于椭圆曲线密码学(Elliptic Curve Cryptography,ECC)算法,与传统的RSA(Rivest-Shamir-) Adleman)加密算法不同。
JWT的利用
实践出真理
-
什么是JWT攻击?
攻击用户将修改后的JWT发送到服务器以实现恶意目标
-
burp suite中的JTW编辑插件,查看下边的视频就能明白了
https://portswigger.net/burp/documentation/desktop/testing-workflow/session-management/jwts
1.签名未校验
要登录靶场需要自己先注册一个账号,登录之后在点击靶场链接
b{Mp:H-6d.93Yc6LZ7g7325RpS25@@3e
-
原理
JWT存在一个Signature 签名,如若没对签名进行认证,就可能存在越权情况
1.1 未验证的签名绕过身份验证
-
靶场位置
-
开始复现
提示:本练习使用基于 JWT 的机制来处理会话。服务器未安全地配置接受未签名的 JWT。
要解决实验室问题,请修改会话令牌以访问管理面板
/admin
,然后删除用户carlos
. 您可以使用以下凭据登录到自己的帐户:
wiener:peter
-
初始页面
-
进行登录
-
登录成功之后进行抓包,去访问
/admin
目录
-
把cookie:session字段使用的是JWT进行解密修改在为管理员即可访问成功
解密平台:https://jwt.io/
要修改payload字段中的信息,可以使用bp插件也可以使用base64解密修改在加密替换
-
替换请求包字段
成功未验证的签名绕过身份验证
-
根据题目信息删除用户即可
carlos
修改请求包路径
GET /admin/delete?username=carlos
即可访问/admin查看是否删除成功
1.2 空加密算法签名绕过
空加密算法(CVE-2015-2951) alg:none
签名绕过漏洞
-
原理
在JWT中
头部(header)
中alg
的值用于告诉服务器使用哪种算法:目前可以选择HS256,即HMAC和SHA256等JWT同时也支持将算法设定为
none
,alg:none
。为空加密算,即可签名绕过 -
靶场位置
-
开始复现
提示:
本实验使用基于 JWT 的机制来处理会话。服务器配置为接受未签名的 JWT 不安全。
要解决该实验,请修改您的会话令牌以访问 管理面板 ,然后删除用户 。
/admin
,carlos
您可以使用以下凭据登录自己的帐户:
wiener:peter
-
和上边的靶场一样唯一不一样的就是需要使用口算法进行绕过
-
我这里使用的是
JWT Editor
,bp中自带的插件可自行安装 -
进行抓包
-
转到JWT插件中
在插件中能更好的进行JWT内容修改
-
删除指定用户
-
2.暴力破解密钥
-
密钥的介绍
,一般来说JWT有以下两种类型的密钥:
- 对称密钥:对称密钥是一种使用相同的密钥进行加密和解密的加密算法,在JWT中使用对称密钥来生成和验证签名,因此密钥必须保密,只有发送方和接收方知道,由于对称密钥的安全性取决于密钥的保密性,因此需要采取一些措施来保护它
- 非对称密钥:非对称密钥使用公钥和私钥来加密和解密数据,在JWT中使用私钥生成签名,而使用公钥验证签名,由于公钥可以公开,因此非对称密钥通常用于验证方的身份
-
原理
签名(Signature)存在一个secret密钥,这个一般都会保存在服务器中,别人无法查看,但是类同于弱口令,一旦这个密钥不复杂就有可能被爆破
-
可以使用的工具
jwtcrack:GitHub - Sjord/jwtcrack:破解 HS256 签名的 JWT 的共享密钥
hashcat:kali中自带
jwt_tool:GitHub - ticarpi/jwt_tool: 🐍 A toolkit for testing, tweaking and cracking JSON Web Tokens
-
爆破可用的密码本
jwt-secrets/jwt.secrets.list at master · wallarm/jwt-secrets · GitHub
-
靶场位置
-
复现
-
进行访问登录,用户名/密码和前面靶场的是一样的
-
登录之后进行抓包
查看JWT
-
使用工具对签名(Signature)进行爆破
我使用的是
jwtcrack
python crackjwt.py JWT的数据 字典.txt //payload,字典可自行去密码本去下载复制为.txt文本即可,放在和工具同一目录即可 python crackjwt.py eyJraWQiOiIxZGYyMWZkOS05ZTEyLTRkNWQtODc1My1mMjkwNGMxMjQ1ZGUiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY5ODUwOTQ2N30.u0Zc-wbdlAbyil0R1SG4hVp6XzdpXH9G9IL32QlLfUo jwt.secrets.txt
-
修改签名和用户名即可越权成功
-
重放数据包
- 根据题目要求删除用户即可
-
3.JWT标头参数注入
-
基础了解
根据JWS规范,header参数是必须的,然而在实践中,JWT标头(也被称为JOSE标头)通常包含一下参数
jwk
(JSON Web 密钥) - 提供表示密钥的嵌入式 JSON 对象。服务器可以使用该参数以 JWK 格式将其公钥直接嵌入到令牌本身中jku
(JSON Web 密钥集 URL) - 提供一个 URL,服务器可以从中获取包含正确密钥的一组密钥。kid
(密钥 ID) - 提供一个 ID,服务器可以使用该 ID 在有多个密钥可供选择的情况下标识正确的密钥。
这些用户可控制的参数分别告诉收件人服务器在验证签名时使用哪个密钥
利用这些参数来注入自己的任意密钥(而不是服务器的密钥)就是标头注入
3.1 通过jwk参数注入自签名JWT
-
JWK 英文全称为 JSON Web Key,是一个JSON对象,表示一个加密的密钥,他不同于alg属性,JWK是可选的,以下就是一个示例
{ "kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG", "typ": "JWT", "alg": "RS256", "jwk": { "kty": "RSA", "e": "AQAB", "kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG", "n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m" } }
-
在理想情况下,服务器应该是只使用公钥白名单来验证JWT签名的,但对于一些相关配置错误的服务器会用JWK参数中嵌入的任何密钥进行验证,攻击者就可以利用这一行为,用自己的RSA私钥对修改过的JWT进行签名,然后在JWK头部中嵌入对应的公钥进行越权操作
-
靶场位置
-
复现
-
进行访问登录,用户名/密码和前面靶场的是一样的
-
登录之后进行抓包
查看JWT,进行分析
-
然后正常抓包。切换JWT Editor ,Keys选项新生成一个RSA Key
-
进行参数插入,并将sub内容修改为administrator
-
已经成功越权,下面根据要求进行下一步操作即可
-
3.2 通过jku参数注入自签名JWT
-
基础了解
有些服务器并不会直接使用JWK头部参数来嵌入公钥,而是使用JKU(JWK Set URL)来引用一个包含了密钥的JWK Set,我们就可以借此来构造一个密钥从而实现越权操作
-
下边是一个JWK示例
像这样的 JWK 集有时会通过标准端点公开,例如 .
/.well-known/jwks.json
{ "keys": [ { "kty": "RSA", "e": "AQAB", "kid": "75d0ef47-af89-47a9-9061-7c02a610d5ab", "n": "o-yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9mk6GPM9gNN4Y_qTVX67WhsN3JvaFYw-fhvsWQ" }, { "kty": "RSA", "e": "AQAB", "kid": "d8fDFo-fS9-faS14a9-ASf99sa-7c1Ad5abA", "n": "fc3f-yy1wpYmffgXBxhAUJzHql79gNNQ_cb33HocCuJolwDqmk6GPM4Y_qTVX67WhsN3JvaFYw-dfg6DH-asAScw" } ] }
-
靶场
-
复现
通过服务器进行jku参数传递,在真实环境中实现的条件还有点苛刻
更安全的网站只会从受信任的域获取密钥,基本就只有配合ssrf了
-
进行访问登录,用户名/密码和前面靶场的是一样的
-
登录之后进行抓包
-
使用JWT编辑器生成RSA key
-
复制jwk的公钥
{ "kty": "RSA", "e": "AQAB", "kid": "e7b9d5d4-54c0-47b5-99ff-58b896910e16", "n": "tvwsDyTCBctiTCbK2yE3kx0Ev9LinTOSl0JrA6KuokmE0FoSGbZMm5XBglDot6wIjnB921kk4ZFcvCIe1BLvQAiO4izHiQdb_oQqaDXvYd-2PWcZF3Sz5lWvbhQ1BR1kPHih75Ixt8_pi0_wIjoR9ElMKdY-LEJFtYjssqsloaNHN6pfykeJtFZmHnZCkDZHqSZlfr_MKPp-jOYJuK77cGe-imufXiYKpdPFTU-YZtq8cVxwlARdrM6wb2-4BbUYtdxr5u5QsHrVW8zZ0akotTEn4Bqlp0CYPs0D2q07wkNoF91Q_BWvW7c5JyYvRO2SWMn-sUu9b_BeCtHALjhmUw" }
-
在公钥的前面添加一个keys
{ "keys":[ { "kty": "RSA", "e": "AQAB", "kid": "8fe1e5d1-a2db-4c24-817d-b8f8771366b9", "n": "4bMwOg09yRaaSMS7b6nvU8V_50UcZYBsPSKyNp7mDa3V0I-UYxFENOMA3GPtsVVvQQbadwsIDQqkRlRcMn_GBk9L5JczQ2soUqyV5OW-CtdUNgD-WGeeiwMYBmMKlJBEAIEGaecVllRsOCQMHJGrJ2o7954p47-NQ97HTygP9qpwLN60VD2Ol1dIZ-8vZJ8HxFU6Yg0peGu7OJtML6O-7qwNOJIU5YRE42Za9lAGXD0HvoeE8qC4nXa--NlLjHRkFQbbA2vq3F2E-iIbs4q5hqchUinehl7bzMfktimNIyyirdj3axMMpl6vbDGlQpFzEsA9rnCJ6dq3zw797MZKpw" } ] }
-
点击Go to exploit server,题目自带的服务器
-
把制作的公钥复制到服务器里边
-
会生成一个网页界面可用于参数的传递
-
进行参数传递,点击确定即可
-
把路径替换为/admin
-
按照题目删除用户即可
-
3.3 通过kid参数注入自签名JWT
-
基础了解
某些服务器可以使用多个加密密钥来对不用类型的数据进行签名,而不仅仅是JWT
JWT的标头可能包含(key id)参数,该参数可帮助服务器识别在验证签名时要使用的密钥
-
漏洞出现的原因
验证密码通常存储为JWK集,在这种情况下,服务器可能只是查找与令牌相同的JWK
但是JWS规范被没有定义此ID的具体结构,它只是开发人员选择的任意字符串
如果
kid
参数可以进行目录遍历,则攻击者可能会强制服务器使用其文件系统中的任意文件作为验证密钥{ "kid": "../../path/to/file", "typ": "JWT", "alg": "HS256", "k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc" }
如果服务器还支持使用对称算法签名的JWT,则特别危险。在这种情况下,攻击者可能会将参数指向可预测的静态文件,然后使用与此文件内容匹配的密钥对 JWT 进行签名。
-
Linux系统文件特性
在Linux系统中
/dev/null
这是一个空文件 -
靶场
-
复现
利用对称密钥来进行注入
-
进行访问登录,用户名/密码和前面靶场的是一样的
-
在jwt编辑器里边创建一个空对称密钥
-
设置kid的值,前边提到过:该参数可帮助服务器识别在验证签名时要使用的密钥
{ "kid": "../../../../../dev/null", "alg": "HS256" }
-
使用Sign中的密钥进行攻击
-
越权成功,按照题目要求进行删除用户即可
-
JWT防护
- 使用最新的 JWT 库,虽然最新版本的稳定性有待商榷,但是安全性都是较高的。
- 对 jku 标头进行严格的白名单设置。
- 确保 kid 标头不容易受到通过 header 参数进行目录遍历或 SQL 注入的攻击。
扩展
-
参考大佬文章
https://paper.seebug.org/3057/#_21
-
靶场平台
https://authlab.digi.ninja/Leaky_JWT
JWT 攻击 |网络安全学院 (portswigger.net)
https://attackdefense.com/