首页 > 其他分享 >揭秘JWT:从CTF实战到Web开发,使用JWT令牌验证

揭秘JWT:从CTF实战到Web开发,使用JWT令牌验证

时间:2024-07-03 18:21:04浏览次数:19  
标签:info Web jwt JWT token CTF user payload

揭秘JWT:从CTF实战到Web开发,使用JWT令牌验证

介绍

JWT(JSON Web Tokens)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在网络上安全地传输信息。这种信息可以验证和信任,因为它是数字签名的。JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

「优点」

  • 「无状态」:服务器不需要保存会话信息,减轻了服务器负担。
  • 「可扩展性」:易于在分布式系统中使用,支持跨域身份验证。
  • 「安全性」:通过数字签名确保信息的完整性和来源可信。

「缺点」

  • 「令牌大小」:由于包含头部、负载和签名,JWT的大小可能相对较大。
  • 「性能」:每次请求都需要验证JWT,可能会增加服务器的处理时间。
  • 「敏感信息泄露」:虽然Payload部分可以加密,但如果不当处理,仍可能泄露敏感信息。

简单复现

CTFShow-web345

image-20240703171126995
image-20240703171126995

让我们查看网页源码,貌似是告诉我们有这个admin后台页面

image-20240703171143135
image-20240703171143135

抓包

image-20240703171355547
image-20240703171355547

修改请求头为admin/index.php

image-20240703171446354
image-20240703171446354

将第一段进行解码,发现是jwt的第一段编码配置,但是加密方式为None,这段token也没有第三段,只有两段,说明根本没有进行加密

image-20240703171626103
image-20240703171626103

第二段就是payload,元数据

image-20240703171653730
image-20240703171653730

尝试将用户user改为admin获取后台权限

image-20240703171915498
image-20240703171915498

302重定向了

image-20240703171930648
image-20240703171930648

尝试把jwt的配置信息给删掉

image-20240703172045891
image-20240703172045891

成功了

image-20240703172059972
image-20240703172059972

python详解jwt

JWT(JSON Web Tokens)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在网络上安全地传输信息。这种信息可以验证和信任,因为它是数字签名的。JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

它的主要应用场景:

  • 授权:这是JWT最常见的使用场景。一旦用户登录,每个后续请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录(SSO)是目前广泛使用JWT的一项特性,因为它的开销很小,并且可以轻松地跨域使用。
  • 信息交换: JWT是在各方之间安全传输信息的好方式。因为JWT可以被签名,例如,使用公钥/私钥对,你可以确定发送方就是它们所说的那个人。此外,由于签名是使用标头和有效负载计算的,您还可以验证内容是否被篡改。

python代码示例

import jwt
import time
# 设置headers,即加密算法的配置
headers = {
    "alg": 'HS256',
    'typ': 'JWT'
}

key = 'abcdefghijklmnopqrstuvwxyz'
# 随机的salt密匙,只有token生成者(同时也是校验者)自己能有,用于校验生成的token是否合法
exp = int(time.time()+1)  # 设置过期时间, time.time()返回当前时间的时间戳,*1000即可得到毫秒的时间戳
payload = {
    'name': 'xiaoyu',  # 这个名称可以进行加密
    'exp': exp
}
# 配置主题信息,一般是登录成功的用户之类的,因为jwt的主题信息很容易被解码,所以不要放敏感信息
# 当然也可以将敏感信息加密后再放进payload

# 生成token
token = jwt.encode(payload=payload, key=key, headers=headers)
print(token)

# 将token进行解码,第二个参数key用于校验
info = jwt.decode(token, key,algorithms=['HS256'])
print(info)

#等待两秒后再次验证token,因超时将导致验证失败
time.sleep(2)
try:
    info=jwt.decode(token, key, algorithms=['HS256'])
    print(info)
except Exception as e:
    print(e)

info = jwt.decode(token, key, algorithms=['HS256'],options={'verify_signature': False})
print(info)

# 结果
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoieGlhb3l1IiwiZXhwIjoxNzE5OTE3NzgwfQ.TGzJclF64WW_Tpxm_p84IYhegL2Pjun5CXbMuizLnzA
# {'name': 'xiaoyu', 'exp': 1719917780}
# Signature has expired
# {'name': 'xiaoyu', 'exp': 1719917780}

jwt生成token所需要的字段

JWT由三部分组成:Header(头部)、Payload(负载)和Signature(签名)。

  • headers:头部通常包含两部分:令牌的类型(即JWT)和所使用的哈希算法(如HMAC SHA256或RSA)。
  • payload:负载包含了Claim,Claim是一些实体(通常指用户)的状态和额外的元数据,有三种类型的Claim:注册Claim、公共Claim和私有Claim。

iss:jwt签发者

sub:jwt所面向的用户

aud:接收jwt的一方

exp:jwt的过期时间,这个过期时间必须大于签发时间

nbf:定义在什么时间之前,该jwt都是不可用的

iat:jwt的签发时间

jti:jwt的唯一标识身份,主要用来作为一次性token,从而回避重放攻击。

  • secret_key: 签名是对Header和Payload的签名,防止数据篡改。首先,需要将Header和Payload使用Base64编码,然后用.连接,之后使用Header中指定的签名算法(HS256)进行签名。

jwt生成token的过程

由上面的简单示例可以看出,jwt生成token主要由三部分,用.号隔开,分别代表:编码后的headers、payload,以及校验字段

  • 通过对headers的json数据进行base64url编码生成第一部分

  • 通过对payload的json数据进行base64url编码生成第二部分

  • 将第一部分和第二部分通过.拼接起来,然后对拼接后的内容结合签名密钥进行HS256加密生成密文(加密算法可以自己选,默认HS256),然后再进行base64url编码,从而生成第三部分

  • 三个部分通过.拼接起来,作为token

base64url编码:先进行base64编码,然后将其中的+替换成-/替换成_,并且最后一般会将=都去掉

例如,下面是一个加密后的payload

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoieGlhb3l1IiwiZXhwIjoxNzE5OTE3NzgwfQ.TGzJclF64WW_Tpxm_p84IYhegL2Pjun5CXbMuizLnzA
------------------------------------ ---------------------------------------------- -------------------------------------------
-------------- headers ------------- --------  payload  --------------------------- --------------- 校验字段--------------------

解密后

image-20240703100522276
image-20240703100522276

将各段尝试使用base64进行解密,第一段headers

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
base64解密后:{"alg":"HS256","typ":"JWT"}

第二段payload

eyJuYW1lIjoieGlhb3l1IiwiZXhwIjoxNzE5OTE3NzgwfQ
base64解密后:{"name":"xiaoyu","exp":1719917780}

第三段校验字段

TGzJclF64WW_Tpxm_p84IYhegL2Pjun5CXbMuizLnzA
base64解密后:Ll�rQz�e������ ���lˢ̹�

jwt异常处理

jwt校验抛出的异常类基本都在jwtjwt.exceptions下,举例:

import jwt
import time
from jwt import exceptions

# 配置第一段
headers = {
    'tyb': 'JWT',
    'alg': 'HS256'
}
salt = 'demmskfhkjagh'  # 随便输入即可
exp = int(time.time() + 1)  # jwt过期时间-1为立即失效
payload = {  # 你需要加密的数据
    'name': 'xiaoyu',
    'password': '123456',
    'age': '21',
    'gender': 'man',
    'exp': exp
}
token=jwt.encode(payload, salt, headers=headers)
time.sleep(2)# 延迟2s使token过期
try:
    info=jwt.decode(token,salt,algorithms=['HS256'])
    print(info)
except exceptions.ExpiredSignatureError:
    print('token已过期')
except jwt.DecodeError:
    print('token认证失败')
except jwt.InvalidTokenError:
    print('非法的token')

Web开发中简单示例

传统token

from flask import Flask, request
import json
import uuid

app = Flask(__name__)

db_source = {
    'user_table': {
        'xiaoyu': {
            'pwd': '123456'
        }
    },
    'user_token': {},
    'user_info_table': {
        'xiaoyu': {
            'age': 21
        }
    }
}

@app.route("/login/<username>/<password>/")
def login(username, password):
    if (not username) or (not password):
        return {'status': 1, 'code': '400', 'msg': '用户或密码不允许为空!'}
    if not db_source['user_table'].get(username, None):
        return {'status': 1, 'code': '401', 'msg': '用户不存在!'}
    if db_source['user_table'][username]['pwd'] != password:
        return {'status': 1, 'code': '402', 'msg': '用户名或密码错误!'}
    token = str(uuid.uuid4())
    db_source['user_token'][token] = username
    return {'status': 1, 'code': '200', 'data': {'token': token}}

@app.route('/user_info', methods=['GET'])
def user_info():
    '''查看用户信息,需要和本地保存的token进行校验'''
    token = request.args.get('token', None)
    if not token:
        return json.dumps({'status': 1, 'code': '500', 'msg': 'token不允许为空!'}, ensure_ascii=False)
    if not db_source['user_token'].get(token, None):
        return json.dumps({'status': 1, 'code': '501', 'msg': 'token校验失败!'}, ensure_ascii=False)
    # 当token校验成功则返回用户信息
    username = db_source['user_token'][token]
    info = db_source['user_info_table'][username]
    return json.dumps({'status': 1, 'code': '200', 'data': {username: info}})

if __name__ == '__main__':
    app.run(debug=True)

测试效果我们的用户名为xiaoyu,密码为123456

image-20240703145540255
image-20240703145540255
image-20240703144709304
image-20240703144709304
image-20240703144735604
image-20240703144735604
image-20240703144802164
image-20240703144802164
image-20240703144813369
image-20240703144813369

可以发现这个基于uuid的token使用过之后没有过期时间,永久的存储非常的不方便,数据库也要新加一个字段token有效期,而jwt可以很好的解决这个问题

基于jwt

import jwt
from flask import Flask, request
import json
import uuid
from jwt import exceptions
app = Flask(__name__)

db_source = {
    'user_table': {
        'xiaoyu': {
            'pwd': '123456'
        }
    },
    'user_info_table': {
        'xiaoyu': {
            'age': 21
        }
    }
}

SECRET_KEY = "ljksljfasiieksf"


# @app.route('/create_token/<name>')
def create_token(name):
    '''
    基于jwt创建token函数
    :param name:
    :return:
    '''
    global SECRET_KEY
    # 加密配置
    headers = {
        'alg': 'HS256',
        'typ': 'JWT'
    }
    exp = int(time.time() + 20)  # 配置token有效时间为20s
    payload = {
        'name': name,  # 这里只配置用户名或者用户id,防止信息泄露
        'exp': exp
    }
    token = jwt.encode(payload=payload, key=SECRET_KEY, headers=headers)
    return token

def validate_token(token):
    '''
    校验token
    :param token:
    :return:
    '''
    payload=None
    msg=None
    try:
        payload=jwt.decode(token,SECRET_KEY,algorithms=['HS256'])
    except jwt.DecodeError as e:
        msg='token verify failed!' # 错误
    except jwt.ExpiredSignatureError:
        msg='token lose efficacy!' # 失效
    except jwt.InvalidTokenError:
        msg='token unlawfulness!' # 非法
    return (payload,msg)

@app.route("/login/<username>/<password>/")
def login(username, password):
    if (not username) or (not password):
        return {'status': 1, 'code': '400', 'msg': 'user or pass is None!'}
    if not db_source['user_table'].get(username, None):
        return {'status': 1, 'code': '401', 'msg': 'not user!'}
    if db_source['user_table'][username]['pwd'] != password:
        return {'status': 1, 'code': '402', 'msg': 'username or password error!'}
    # token = str(uuid.uuid4())
    # db_source['user_token'][token] = username
    token=create_token(username)
    return {'status': 1, 'code': '200', 'data': {'token': token}}

# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoieGlhb3l1IiwiZXhwIjoxNzE5OTk1MDM4fQ.siRTK3IKV4uhgFmEUMvfxBfmKqjg7G4B7eeXatP-Q-M
@app.route('/user_info', methods=['GET'])
def user_info():
    '''查看用户信息,需要和本地保存的token进行校验'''
    token = request.args.get('token', None)
    if not token:
        return {'status': 1, 'code': '500', 'msg': 'token is NULL!'}
    # 校验token,并进行解码
    payload,msg=validate_token(token)
    if msg:
        return {'status': 1, 'code': '501', 'msg': msg}
    # 获取解码后的用户名
    username = payload['name']
    # 从数据源中读取解码后的用户信息
    info = db_source['user_info_table'][username]
    return {'status': 1, 'code': '200', 'data': {username: info}}


if __name__ == '__main__':
    app.run(debug=True)

文章参考:https://www.jianshu.com/p/03ad32c1586c

原文链接:https://mp.weixin.qq.com/s/RT2SNHlrCcbA8IazJ6usqQ

标签:info,Web,jwt,JWT,token,CTF,user,payload
From: https://www.cnblogs.com/xiaoyus/p/18282338

相关文章

  • web前端应用性能指标测量工具有哪些?
    接上一篇介绍前端性能指标的内容,本文主要总结下一般使用的性能测量工具。1、可以借助Gooogle开源的web-vitals库来测量一些性能指标:import{onCLS,onINP,onLCP,onFCP,onFID,onTTFB}from'web-vitals';onCLS(console.log);onINP(console.log);onLCP(console.log);o......
  • web前端应用性能指标优化方案有哪些?
    接上一篇介绍前端性能指标的内容,本文主要总结下一般使用的指标优化方案加载相关:FCP优化:降低服务器响应时间:确保服务器能迅速响应请求,这样浏览器就能更快地开始处理并渲染页面内容。可以通过优化服务端代码、改善静态资源的提供方式(如将图片部署到CDN)来实现。延迟加载非关......
  • web前端应用应该关注哪些性能指标?
    作为一个有经验的前端开发工程师来说,关注性能是必不可少的一项日常工作,那么应该重点关注一些什么样的性能指标呢?其实主要还是从用户体验的角度来看,一般我们会从页面加载相关、交互相关方面入手。原文:前端性能指标,一网打尽FCP:首次内容绘制LCP:最大内容绘制TTFB:首字节时间TBT:总......
  • 教你从零开始制作一个Web蜜罐扫描器
    01想法的来源在渗透的过程中,会遇到很多蜜罐,一旦不小心踩了蜜罐,就会被溯源,所以很可怕。为了规避上面的现象,就需要把蜜罐筛出来。使用场景是在前期资产收集的过程中,搞到了一堆子域名,先筛掉一批蜜罐,留下可以攻击的纯净资产。同上得到的一份资产如下:如上图所示,大量的域名如......
  • 详解Web应用安全系列(6)安全配置错误
    Web攻击中的安全配置错误漏洞是一个重要的安全问题,它涉及到对应用程序、框架、应用程序服务器、Web服务器、数据库服务器等组件的安全配置不当。这类漏洞往往由于配置过程中的疏忽或错误,使得攻击者能够未经授权地访问系统数据或执行系统功能。安全配置错误类漏洞是指在对Web应用......
  • CentOS Stream 8 发布.net 8 webapi
    参考资料https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-rhel#where-is-centos-linux 微软好像不持支.net7所以把demo换成.net8sudodnfinstalldotnet-sdk-8.0 然后就开始报错,大致意思就是无法解析,找不到地址资源进入yum的repos目录cd/etc/yum.rep......
  • 保持校园网自动登录的代码备忘: 基于webdriver和chrome
    在跑一个处理很多数据的代码,需要不间断地运行。而且最近打算回家一周,需要远程控制电脑。但是学校的校园网每到晚上就会自己断开,干脆写份脚本来保持校园网的连接这份简单的代码只包括三个部分,检测网络状态的部分,自动登录校园网的部分,循环执行的部分、importosimporttimeimpo......
  • OpenSpace Web3课程大纲
    这个OpenSpaceWeb3BootCamp区块链技术线下集训营培训的内容看起来不错,比较全面也比较深入,但我没有金钱和时间,而且似乎这个面向的应该是即将毕业的应届生,2个月脱产应该没多少工作的人参加,除了一些处于gap期的吧。年轻真好。不管怎样,在这里列一下这个培训的大纲,作为参考。夯实基......
  • 应对Web安全防护,溯源追踪攻击者。
    前言随着时代的进行,互联网站走进千家万户,网络安全问题也逐渐提上日程,国家对网络安全也欲加重视。一、重视保护现如今,大数据已成为必要发展趋势。Web网站也成为人们获取信息和企业开放产品的重要行式。提到网站,就不得不提到Web安全。仍有许多企业不重视,不在乎Web安全,只要求......
  • 打印功能实现--web端+移动端
    web端---调用本机的打印机即可 <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>PrintCustomContentandStyle</title><style>@mediaprint{/*打印时隐藏页面中的元素*/#header,#footer{......