Jwt認識與攻擊


今天看到2018強網杯的題目,因此總結一下。

Json Web Token

Json Web Token簡稱jwt

那么怎么樣可以讓HTTP記住曾經發生的事情呢?

這里的選擇可以很多:cookie,session,jwt

對於一般的cookie,如果我們的加密措施不當,很容易造成信息泄露,甚至信息偽造,這肯定不是我們期望的。

那么對於session呢?

對於session:客戶端在服務端登陸成功之后,服務端會生成一個sessionID,返回給客戶端,客戶端將sessionID保存到cookie中,例如phpsessid,再次發起請求的時候,攜帶cookie中的sessionID到服務端,服務端會緩存該session(會話),當客戶端請求到來的時候,服務端就知道是哪個用戶的請求,並將處理的結果返回給客戶端,完成通信。

但是這樣的機制會存在一些問題:

1、session保存在服務端,當客戶訪問量增加時,服務端就需要存儲大量的session會話,對服務器有很大的考驗;

2、當服務端為集群時,用戶登陸其中一台服務器,會將session保存到該服務器的內存中,但是當用戶的訪問到其他服務器時,會無法訪問,通常采用緩存一致性技術來保證可以共享,或者采用第三方緩存來保存session,不方便。

所以這個時候就需要jwt了

在身份驗證中,當用戶使用他們的憑證成功登錄時,JSON Web Token將被返回並且必須保存在本地(通常在本地存儲中,但也可以使用Cookie),而不是在傳統方法中創建會話服務器並返回一個cookie。

JWT一般包括三部分:header,payload,Signature

一般jwt模樣,以.號分為三部分。我們用https://jwt.io/來解密下看看

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyYW1lIjoiYWRtaW5za3kiLCJwcml2Ijoib3RoZXIifQ.AoTc1q2NAErgqk6EeTK4MGH7cANVVF9XTy0wLv8HpgUfNcdM-etmv0Y9XmOuygF_ymV1rF6XQZzLrtkFqdMdP0NaZnTOYH35Yevaudx9bVpu9JHG4qeXo-0TXBcpaPmBaM0V0GxyZRNIS2KwRkNaxAQDQnyTN-Yi3w8OVpJYBiI

 

 

Header
通常由兩部分組成:令牌的類型,即JWT和正在使用的散列算法,如HMAC SHA256或RSA。

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

alg為算法的縮寫,typ為類型的縮寫

然后,這個JSON被Base64編碼,形成JSON Web Token的第一部分。

 

Payload
令牌的第二部分是包含聲明的有效負載。聲明是關於實體(通常是用戶)和其他元數據的聲明。

這里是用戶隨意定義的數據

例如上面的舉例

{
    "name":"adminsky",
    "priv":"other"
}

然后將有效載荷Base64進行編碼以形成JSON Web Token的第二部分。

但是需要注意對於已簽名的令牌,此信息盡管受到篡改保護,但任何人都可以閱讀。除非加密,否則不要將秘密信息放在JWT的有效內容或標題元素中。

 

Signature
要創建簽名部分,必須采用header,payload,密鑰

然后利用header中指定算法進行簽名

例如HS256(HMAC SHA256),簽名的構成為

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

然后將這部分base64編碼形成JSON Web Token第三部分  

這里采用的是私鑰簽名,公鑰驗證的方法。

 

這里講解三種常見攻擊方式

修改算法RS256為HS256(非對稱密碼算法 => 對稱密碼算法)

算法HS256使用秘密密鑰對每條消息進行簽名和驗證。

算法RS256使用私鑰對消息進行簽名,並使用公鑰進行驗證。

如果將算法從RS256更改為HS256,后端代碼會使用公鑰作為秘密密鑰,然后使用HS256算法驗證簽名。

由於公鑰有時可以被攻擊者獲取到,所以攻擊者可以修改header中算法為HS256,然后使用RSA公鑰對數據進行簽名。

后端代碼會使用RSA公鑰+HS256算法進行簽名驗證。

同樣的,可以通過一個例子來理解這種攻擊方式 http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php

RSA公鑰:http://demo.sjoerdlangkemper.nl/jwtdemo/public.pem

該例子解法如下

import jwt

# eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9
# {"typ":"JWT","alg":"RS256"}

# eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTUwNDAwNzg3NCwiZXhwIjoxNTA0MDA3OTk0LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0
# {"iss":"http:\/\/demo.sjoerdlangkemper.nl\/","iat":1504007874,"exp":1504007994,"data":{"hello":"world"}}

public = open('public.pem.1', 'r').read()

print public

print jwt.encode({"data":"test"}, key=public, algorithm='HS256')  //修改payload中的明文信息

然后生成的jwt串中,就偽造了payload信息,並且服務器端驗證也會以RSA的公鑰來驗證HS256算法,因此偽造偽造成功

在iscc2019中,有相同題目。附上我自己的安裝過程:python3 pip3 install PyJWT==1.6.0

import jwt
import base64

public = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMRTzM9ujkHmh42aXG0aHZk/PK\nomh6laVF+c3+D+klIjXglj7+/wxnztnhyOZpYxdtk7FfpHa3Xh4Pkpd5VivwOu1h\nKk3XQYZeMHov4kW0yuS+5RpFV1Q2gm/NWGY52EaQmpCNFQbGNigZhu95R2OoMtuc\nIC+LX+9V/mpyKe9R3wIDAQAB\n-----END PUBLIC KEY-----"

print(jwt.encode({"name": "yunying666","priv": "admin"}, key=public, algorithm='HS256').decode())

生成的jwt,就可以偽造yunying666用戶為admin,從而以管理員登陸

 

HS256(對稱加密)密鑰暴力破解

因為HS256只有一個密鑰,解密加密都是同一個密鑰,因此破解比較容易。

附上現成的破解工具鏈接https://github.com/brendan-rius/c-jwt-cracker

 

 

修改算法為none

簽名算法保證了JWT在傳輸的過程中不被惡意用戶修改

但是header中的alg字段可被修改為none

一些JWT庫支持none算法,即沒有簽名算法,當alg為none時后端不會進行簽名校驗

將alg修改為none后,去掉JWT中的signature數據(僅剩header + '.' + payload + '.')然后提交到服務端即可

這種攻擊的例子可以參考:http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php

代碼可以在Github上找到 https://github.com/Sjord/jwtdemo/

這個例子的解法如下

import jwt
import base64

# 原header
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
# {"typ":"JWT","alg":"HS256"}

# 原payload eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTUwNDAwNjQzNSwiZXhwIjoxNTA0MDA2NTU1LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0
# {"iss":"http:\/\/demo.sjoerdlangkemper.nl\/","iat":1504006435,"exp":1504006555,"data":{"hello":"world"}}

def b64urlencode(data):
    return base64.b64encode(data).replace('+', '-').replace('/', '_').replace('=', '')

# 構造算法字段為none, payload部分可以隨意修改
print b64urlencode("{\"typ\":\"JWT\",\"alg\":\"none\"}") + \
    '.' + b64urlencode("{\"data\":\"test\"}") + '.'

 

信息泄露

源於HITBCTF2017 中的一道jwt題目

jwt的header部分為

{"kid":"keys/3c3c2ea1c3f113f649dc9389dd71b851","typ":"JWT","alg":"RS256"}

kid指定了公鑰的地址,kid字段(key ID),它指定了服務器用哪一個key來加密。

其實簽名和驗證都是在服務器端上進行的,如果是RSA,在服務器端用私鑰簽名,發到客戶端,如果客戶端修改了payload中的明文,在請求到服務端,服務端接收到,用公鑰解密簽名,發現明文不同,就會拒絕。

這道題的思路就是,kid用來指定服務器用哪個公鑰驗證簽名。

所以只要偽造一對公鑰私鑰,修改payload,重新簽名后發過去,服務器指定了你防止公鑰的地方,取出公鑰並且驗證。解密簽名后,發現都相同,從而通過,返回admin的信息

 

 

信息泄露:https://chybeta.github.io/2017/08/29/HITB-CTF-2017-Pasty-writeup/

RSA=>HS256:https://skysec.top/2018/05/19/2018CUMTCTF-Final-Web/#Pastebin

學習資料:

https://www.cnblogs.com/dliv3/p/7450057.html

https://blog.csdn.net/qq_43500877/article/details/90273139

 

 

  

 


免責聲明!

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



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