Abstract
Json Web Token 简称JWT,是一种紧凑的 URL 安全方式,用于表示在双方之间传输的声明。JWT 中的声明被编码为使用JSON Web 签名(JWS)进行数字签名的 JSON 对象。——IETF
简单的说,JWT是一种非常轻量级的业务流程管理规范,用于用户和服务器之间安全可靠地传递信息,常用于前后端分离、Restful API配合使用构建身份认证机制.
JWT结构
jwt有三大部分组成,用小数点分割——header.payload.signature
Header
第一部分头部解码后表示一个简单的JSON对象,一般来说这个对象描述了JWT所使用的签名算法和类型
1 | { |
经过Base64URL编码之后就为eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9
【注】: Base64URL是base64修改版,为了方便地在Web中传输使用了不同的编码表,不会在末尾填充=号,并将+
和/
分别改为-
和_
Payload
第二部分是JWT的核心,也就是载荷.储存一些用户的数据(用户名,时间戳,过期时间等等).通常遵守的原则是存储尽量少的必要数据在载荷中,因为Base64可解,基本上相当于明文传输了
1 | { |
经过Base64URL编码之后就为eyJrZXkiOiJ2YWwiLCJpYXQiOjE0MjI2MDU0NDV9
signature
最后一部分是签名,是根据头部和载荷通过密钥secret
和指定的签名算法进行加密计算出来的一个签名,密钥保存在服务端,用来校验JWT是否有效,保证完整性
eUiabuiKv-8PYk2AkGY4Fb5KMZeorYBLw261JPQD5lM
综上述,这个JWT完整版为
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9.eyJrZXkiOiJ2YWwiLCJpYXQiOjE0MjI2MDU0NDV9.eUiabuiKv-8PYk2AkGY4Fb5KMZeorYBLw261JPQD5lM |
攻击方式
加密算法可控
修改算法为None
JWT为方便调试可以将加密算法设为none
,结构为header.payload.
。有些情况下,alg字段为none时,服务器不会进行签名校验,这样就存在伪造JWT的风险
1 | docker pull gluckzhang/ctf-jwt-token |
访问8080端口,是一个登录框,尝试登录失败后会给出正确的账号密码
输入账号密码,后台抓包
此时cookie中的Token就是认证通过之后的JWT,拿去解码: 传送门
1 | //Header |
这里Payload中的role为user,我们将其改成admin,并且将alg的值改为none,然后借助pyjwt库生成新的JWT
1 | import jwt |
然后替换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 | import jwt |
但是运行的时候,pyjwt会校验密钥的格式,然后会报错
我们定位到algorithms.py
的150行
1 | if any([string_value in key for string_value in invalid_strings]): |
这里会进行判断然后报错,我们直接将这一段给注释掉,然后回来执行就没有问题了
1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoidGVzdCJ9.5yqz5BAxgRYv4oXX0eoESlwMbXd5-uycC4ZFIPkcE64 |
密钥弱口令
JWT的安全性取决于密钥的保密性,一个JWT的密钥不够强大,就能够通过爆破的方式获取密钥伪造Token
工具
- c-jwt-cracker
- Hashcat
- john
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