JWT伪造初步学习

JWT 简介

JSON Web Token(JSON Web 令牌)是一个开放标准 (rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以 JSON 对象安全地传输信息。通过 JSON 形式作为 Web 应用中的令牌,用于在各方之间安全地将信息作为 JSON 对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。

传统 session

HTTP 协议本身是一种无状态的协议,即使用户向服务器提供了用户名和密码来进行用户认证,在下次请求时用户也得再一次进行用户认证。因为根据 HTTP 协议,服务器并不能知道接收到的请求来自哪个用户,所以为了让应用能识别是哪个用户发出的请求,只能在服务器存储─份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为 Cookie 以便下次请求时发送给应用。这样应用就能识别请求来自哪个用户。

存在问题

  1. 用户经改应用认证后,应用都要在服务端存储一份 session。而 session 一般都是保存在内存中,随着认证用户的增多,服务端的开销会明显增大。而且用户下次的请求还必须发送到这台服务器上,这样才能拿到授权的资源。在分布式应用上会限制负载均衡器的能力。
  2. session 是基于 cookie 来进行用户识别,cookie 如果被截获,用户很容易受到 CSRF(跨站伪造请求攻击)攻击。

JWT 认证

认证流程:

  1. 前端通过 Web 表单将自己的用户名和密码发送到后端的接口。该过程一般是 HTTP 的 POST 请求。建议的方式是通过 SSL 加密的传输 (https 协议),从而避免敏感信息被嗅探。
  2. 后端核对用户名和密码成功后,将用户的 id 等其他信息作为 JWT Payload (负载),将其与头部分别进行 Base64 编码拼接后签名,形成一个 JWT (Token)。
  3. 后端将 JWT 字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在 localStorage(浏览器本地缓存)或 sessionStorage(session 缓存)上,退出登录时前端删除保存的 JWT 即可。
  4. 前端在每次请求时将 JWT 放入 HTTP 的 Header 中的 Authorization 位。(解决 XSS 和 XSRF 问题)
    后端检查是否存在,如存在验证 JWT 的有效性。例如,检查签名是否正确﹔检查 Token 是否过期;检查 Token 的接收方是否是自己 (可选)
  5. 验证通过后后端使用 JWT 中包含的用户信息进行其他逻辑操作,返回相应结果

JWT 结构

先放一个可以提供 JWT 验证的网站:https://jwt.io/

JWT 是一个字符串,由三部分组成,中间用 . 隔开
例如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphcmVuIiwiYWRtaW4iOnRydWV9.J3Vcpqx76LFtxe8xTMBORxZydb2YnPsMcSHq8cdSRww

第一部分是头部(Header),第二部分是有效载荷(Payload),第三部分是签名(Signature)
其 secret 为:helloctf

1、头部(Header)

头部包含两部分信息:

  • 声明类型
  • 声明加密的算法。通常直接使用 HMAC、SHA256、RSA。
{  "alg": "HS256",  "typ": "JWT"}

然后将头部进行 base64 加密,构成第一部分。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  • 注意:可以将 JWT 中的 alg 算法修改为 none:
    JWT 支持将算法设定为 “None”。如果 “alg” 字段设为 “ None”,那么 JWT 的第三部分会被置空,这样任何 token 都是有效的。这样就可以伪造 token 进行随意访问。

2、有效载荷(Payload)

包含 3 部分信息:

  1. 标准中注册的声明(建议但不强制使用)
    • iss: jwt 签发者
    • sub: jwt 所面向的用户
    • aud: 接收 jwt 的一方
    • exp: jwt 的过期时间,这个过期时间必须要大于签发时间
    • nbf: 定义在什么时间之前,该 jwt 都是不可用的.
    • iat: jwt 的签发时间
    • jti: jwt 的唯一身份标识,主要用来作为一次性 token,从而回避重放攻击。
  2. 公共的声明
    公共的声明可以添加任何的信息。
  3. 私有的声明
    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为 base64 是对称解密的,意味着该部分信息可以归类为明文信息。

举例一个 payload:

{  "sub": "1234567890",  "name": "Jaren",  "admin": true}

然后将其进行 base64 加密,得到 Jwt 的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphcmVuIiwiYWRtaW4iOnRydWV9

3、签证(Signature)

包含以下三个部分:

  • base64 加密后的 header
  • base64 加密后 payload
  • 密钥 secret

这个部分需要 base64 加密后的 header 和 base64 加密后的 payload 使用. 连接组成的字符串,然后通过 header 中声明的加密方式进行加盐 secret 组合加密,然后就构成了 jwt 的第三部分。

// javascriptvar encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); var signature = HMACSHA256(encodedString, 'secret'); //J3Vcpqx76LFtxe8xTMBORxZydb2YnPsMcSHq8cdSRww

通过 JWT 进行认证

客户端接收服务器返回的 JWT,将其存储在 Cookie 或 localStorage 中。此后,客户端将在与服务器交互中都会带 JWT。如果将它存储在 Cookie 中,就可以自动发送,但是不会跨域,因此一般是将它放入 HTTP 请求的 Header Authorization 字段中。当跨域时,也可以将 JWT 被放置于 POST 请求的数据主体中。

服务器每次收到信息都会对它的前两部分进行加密,然后比对加密后的结果是否跟客户端传送过来的第三部分相同,如果相同则验证通过,否则失败。

一般是在请求头里加入 Authorization,并加上 Bearer 标注:

fetch('api/user/1', {  headers: {    'Authorization': 'Bearer ' + token  }})

JWT 本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。

JWT token 破解绕过

空加密算法

JWT 支持使用空加密算法,可以在 header 中指定 alg 为 None

这样的话,只要把 signature 设置为空(即不添加 signature 字段),提交到服务器,任何 token 都可以通过服务器的验证。

修改 RSA 加密算法为 HMAC

JWT 中最常用的两种算法为 HMAC 和 RSA。

  • HMAC 是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,它是一种对称加密算法,使用相同的密钥对传输信息进行加解密。
  • RSA 则是一种非对称加密算法,使用私钥加密明文,公钥解密密文。

在 HMAC 和 RSA 算法中,都是使用私钥对 signature 字段进行签名,只有拿到了加密时使用的私钥,才有可能伪造 token。

现在我们假设有这样一种情况,一个 Web 应用,在 JWT 传输过程中使用 RSA 算法,密钥 pem 对 JWT token 进行签名,公钥 pub 对签名进行验证。

{    "alg" : "RS256",    "typ" : "jwt"}

通常情况下密钥 pem 是无法获取到的,但是公钥 pub 却可以很容易通过某些途径读取到,这时,将 JWT 的加密算法修改为 HMAC,即

{    "alg" : "HS256",    "typ" : "jwt"}

同时使用获取到的公钥 pub 作为算法的密钥,对 token 进行签名,发送到服务器端。

服务器端会将 RSA 的公钥(pub)视为当前算法(HMAC)的密钥,使用 HS256 算法对接收到的签名进行验证。

爆破密钥

JWT 的密钥爆破需要在一定的前提下进行:

  • 知悉 JWT 使用的加密算法
  • 一段有效的、已签名的 token
  • 签名用的密钥不复杂(弱密钥)

所以其实 JWT 密钥爆破的局限性很大。

工具:c-jwt-cracker

文件位置/jwtcrack token 例如:root@Jaren:~/c-jwt-cracker# /root/c-jwt-cracker/jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.sh6GvTOAjHLEnmqP_ZoUqWVtddg7KlmHqKPa9VKM5G0

然后就是漫长的等待了。。。

c-jwt-cracker 配置

什么?你还要问 c-jwt-cracker 怎么安装?

  1. ubuntu 中直接下载:git clone https://github.com/brendan-rius/c-jwt-cracker.git
  2. 安装 gcc:sudo apt install gcc
  3. 安装 make:sudo apt install make
  4. 进入 c-jwt-cracker 目录使用 make 编译:make
  5. 倘若上一步报错,使用如下命令安装该头文件:sudo apt-get install libssl-dev
  6. 正常使用即可: 文件位置/jwtcrack token

JWT 特殊参数的利用

修改 KID 参数

kid 是 jwt header 中的一个可选参数,全称是 key ID,它用于指定加密算法的密钥

{    "alg" : "HS256",    "typ" : "jwt",    "kid" : "/home/jwt/.ssh/pem"}

因为该参数可以由用户输入,所以也可能造成一些安全问题。

任意文件读取

kid 参数用于读取密钥文件,但系统并不会验证用户想要读取的到底是不是密钥文件
所以,如果在没有对参数进行过滤的前提下,攻击者是可以读取到系统的任意文件的。

{    "alg" : "HS256",    "typ" : "jwt",    "kid" : "/etc/passwd"}

SQL 注入

kid 也可以从数据库中提取数据,这时候就有可能造成 SQL 注入攻击,通过构造 SQL 语句来获取数据或者是绕过 signature 的验证

{    "alg" : "HS256",    "typ" : "jwt",    "kid" : "key11111111' || union select 'secretkey' -- "}

命令注入

对 kid 参数过滤不严也可能会出现命令注入问题,但是利用条件比较苛刻。
如果服务器后端使用的是 Ruby,在读取密钥文件时使用了 open 函数,通过构造参数就可能造成命令注入。

"/path/to/key_file|whoami"

对于其他的语言,例如 php,如果代码中使用的是 exec 或者是 system 来读取密钥文件,那么同样也可以造成命令注入。

修改 JKU/X5U 参数

JKU 的全称是 “JSON Web Key Set URL”,用于指定一组用于验证令牌的密钥的 URL。类似于 kidJKU 也可以由用户指定输入数据,如果没有经过严格过滤,就可以指定一组自定义的密钥文件,并指定 web 应用使用该组密钥来验证 token。

X5U 则以 URL 的形式数允许攻击者指定用于验证令牌的公钥证书证书链,与 JKU 的攻击利用方式类似。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇