Token 經 base64_encode() 後包含 +、/、=。透過 URL GET 傳遞時,+ 會被解析為空格,導致解碼失敗。改進版將 + 替換為 _ (URL 安全字元),傳輸前再還原。
<?php
class GEncrypt {
protected static function keyED($txt, $key) {
$key = md5($key);
$ctr = 0; $tmp = '';
for ($i = 0; $i < strlen($txt); $i++) {
if ($ctr == strlen($key)) $ctr = 0;
$tmp .= $txt[$i] ^ $key[$ctr++];
}
return $tmp;
}
public static function encrypt($txt, $key) {
$encrypt_key = md5((float)date('YmdHis') + rand(1e16, 99999999999999999) . rand(100000, 999999));
$ctr = 0; $tmp = '';
for ($i = 0; $i < strlen($txt); $i++) {
if ($ctr == strlen($encrypt_key)) $ctr = 0;
$tmp .= $encrypt_key[$ctr] . ($txt[$i] ^ $encrypt_key[$ctr++]);
}
// 關鍵:將 + 替換為 _
return preg_replace('/\+/s', '_', base64_encode(self::keyED($tmp, $key)));
}
public static function decrypt($txt, $key) {
if ($txt === '') return false;
// 還原 _ 為 +
$txt = self::keyED(base64_decode(preg_replace('/_/s', '+', $txt)), $key);
$tmp = '';
for ($i = 0; $i < strlen($txt); $i++) {
$md5 = $txt[$i++];
$tmp .= $txt[$i] ^ $md5;
}
return $tmp;
}
}
?>
<?php
class GToken {
public static function getTokens() {
$tokens = $_SESSION[GConfig::SSN_KEY_TOKEN] ?? [];
return is_array($tokens) ? $tokens : [];
}
public static function newToken($formName, $key = GConfig::ENCRYPT_KEY) {
return GEncrypt::encrypt($formName . session_id(), $key);
}
public static function dropToken($token) {
$tokens = self::getTokens();
$tokens[] = $token;
GSession::set(GConfig::SESSION_KEY_TOKEN, $tokens);
}
public static function isToken($token, $formName, $fromCheck = false, $key = GConfig::ENCRYPT_KEY) {
if (empty($token)) return false;
$tokens = self::getTokens();
if (in_array($token, $tokens, true)) return false; // 已使用
$source = GEncrypt::decrypt($token, $key);
if ($fromCheck) {
return $source === $formName . session_id();
}
return strpos($source, $formName) === 0;
}
public static function getTokenKey($token, $key = GConfig::ENCRYPT_KEY) {
if (empty($token)) return false;
$source = GEncrypt::decrypt($token, $key);
return $source !== '' ? str_replace(session_id(), '', $source) : false;
}
// Smarty 外掛支援
public function newTokenForSmarty($params) {
extract($params);
return self::newToken($form ?? '');
}
}
?>
<input type="hidden" name="token" value="">if (GToken::isToken($_POST['token'], 'post_form')) {
// 驗證通過,處理業務邏輯
GToken::dropToken($_POST['token']); // 標記已用
} else {
// Token 無效或重複提交
}
+ → _ 避免 GET 傳輸語義衝突。fromCheck=true 可驗證 Token 內嵌的 session_id,防跨站盜用。改進版 Token 機制解決 Base64 URL 傳輸編碼問題,並提供一次性、Session 綁定、表單隔離等多層防禦,適合表單密集型應用防重複提交與 CSRF 保護。