Kali Forms 更新後訪客送不出表單,問題出在快取頁面裡的過期 nonce
整理日期:2026-10-27
一個用 Kali Forms 做預約表單的網站,訪客回報送出後看到 404,也有人看到 403,但管理員登入後台自己測試,每次都能正常送出。
查主機的 access log,失敗的訪客都有同一個順序,先送出一次表單請求到 admin-ajax.php,幾秒後同一位訪客的姓名、Email 和電話出現在網址後面,例如 /booking/?name=...&email=...,再由這個網址回 404 或 403。
這幾筆送出在表單外掛的送出紀錄裡都找不到。
先拆成幾層看
Section titled “先拆成幾層看”- 第一次送出的
admin-ajax.php請求回了什麼,是成功還是被拒。 - 表單資料為什麼會跑到網址上,表單的 JS 有沒有攔住瀏覽器原本的送出方式。
- 登入和沒登入差在哪裡,通常是快取和權限。
- 頁面經過哪幾層快取,包含 WordPress 外掛、主機的 Varnish 和 CDN。
- 表單外掛最近有沒有更新,前後版本改了什麼。
Kali Forms 會把 nonce(表單用來確認請求來自網站本身的驗證碼)寫在頁面 HTML 裡。這個網站的主機用 Varnish 把整頁快取 7 天,但 WordPress 的 nonce 最多 24 小時有效,訪客拿到的快取頁面裡,nonce 早就過期了,送出時被外掛以 {"message":"Sneaky sneaky","error":true} 拒絕。
外掛其實有設計補救,前端每次載入頁面都會呼叫 kaliforms_get_js_var 換一個新的 nonce。問題是 2.4.17 的資安修正把這支 API 改成只開放給有編輯權限的帳號,未登入的訪客一律回 Denied,前端程式卻沒有跟著改。比對 2.4.16 和 2.4.27 的原始碼,差別就在 wp_ajax_nopriv_kaliforms_get_js_var 從原本的處理函式改接到 denied。
這也解釋了兩件事。2.4.16 以前訪客每次載入都能換到新 nonce,快取多舊都沒關係,網站自動更新後才開始出事。管理員登入時有權限換新 nonce,所以自己測試永遠正常。
被拒絕之後畫面沒有任何提示,因為表單的「全域錯誤訊息」預設是空白,訪客只看到轉圈出現又消失。Kali Forms 的 JS 在送出前會先拆掉自己攔截送出的程式,失敗後沒有裝回去,訪客再按一次,瀏覽器就照一般表單的方式用 GET 把欄位接在目前網址後面。欄位名稱 name 剛好是 WordPress 的保留參數,找不到對應文章就回 404,內容有引號或換行時則被主機防火牆擋成 403。
這些送出在 nonce 那一關就被拒絕,不會存進表單紀錄,唯一留下的痕跡是 access log 裡帶著欄位的那筆網址。
讓訪客也能換到新的 nonce
Section titled “讓訪客也能換到新的 nonce”在程式碼片段外掛新增一段 PHP,補回未登入訪客換 nonce 的處理:
<?php
// Kali Forms 2.4.17 起不讓訪客換新 nonce,快取頁面的舊 nonce 過期就會送出失敗;這裡補回訪客版本add_action( 'wp_ajax_nopriv_kaliforms_get_js_var', function () { $key = isset( $_POST['data']['key'] ) ? sanitize_key( wp_unslash( $_POST['data']['key'] ) ) : ''; if ( 'ajax_nonce' === $key ) { wp_send_json_success( wp_create_nonce( 'kaliforms_nonce' ) ); }}, 1 );這段要設成全站執行,不能只限前台。換 nonce 的請求走 admin-ajax.php,WordPress 把它算在後台,限定前台的片段不會執行到。
訪客拿到的 nonce 本來就公開寫在每一頁的 HTML 裡,所以用這支 API 給出去並沒有多暴露資訊。這段也只處理 ajax_nonce,其他請求照樣交回外掛原本的 Denied。
admin-ajax.php 的 POST 請求不會被頁面快取保存,補上之後訪客每次載入都會換到新的 nonce,表單頁可以繼續快取。
快取時間先縮短
Section titled “快取時間先縮短”在程式補上之前,可以先把主機 Varnish 的快取時間從 7 天縮短到 10 小時左右,讓快取頁面裡的 nonce 還在有效期間內。這只能壓住大部分狀況,頁面開著放太久才送出的訪客還是會失敗,所以仍要補上面那段程式。
把全域錯誤訊息填上
Section titled “把全域錯誤訊息填上”到每張表單的設定裡填上全域錯誤訊息,例如請訪客重新整理頁面,或改用電話、LINE 聯絡。之後就算因為別的原因送出失敗,訪客至少會知道要換一個方式,不會一直重按。
- 未登入的狀態下對
admin-ajax.php送出action=kaliforms_get_js_var&data[key]=ajax_nonce,修正前回Denied,修正後回{"success":true,"data":"..."}。 - 拿換到的 nonce 送出
action=kaliforms_form_process&data[nonce]=換到的值,刻意不帶表單 ID。回Form didn`t send a form id代表 nonce 有效,回Sneaky sneaky代表無效,兩種情況都不會真的產生一筆送出。 - 用無痕視窗實際送出一筆,確認出現成功訊息。
- 幾天後回頭看 access log,網址裡不應該再出現
?name=。
看 access log 判斷是哪一種回應時要注意,nginx 記的是壓縮後的大小。這次 Denied 經 brotli 壓縮後是 10 bytes,Sneaky sneaky 的回應是 44 bytes,把候選的回應內容自己壓一次比對,就能分出是哪一種被拒。
不建議先做的事
Section titled “不建議先做的事”- 不要一開始就把所有表單頁排除在快取之外,這些頁面常常是最重要的落地頁,補上換 nonce 的程式就能保留快取。
- 不要先改欄位名稱
name來避開 404,那只會讓訪客看到一個看似正常的頁面,資料一樣沒有送出。 - 不要用帶引號的網址反覆測試 403 是哪一層擋的,安全外掛可能直接封鎖自己的 IP。
- 不要只看表單紀錄判斷有沒有漏掉詢問,這種失敗不會留下紀錄,要從 access log 找。
下次遇到可以先整理什麼
Section titled “下次遇到可以先整理什麼”- 表單外掛名稱、目前版本和最近一次更新日期。
- 頁面經過哪些快取,各自的快取時間。
- 未登入時
admin-ajax.php的回應內容。 - access log 裡
admin-ajax.phpPOST 的回應大小,以及之後有沒有帶表單欄位的 GET 網址。 - 表單的全域錯誤訊息有沒有設定。
WordPressKali FormsnonceVarnish快取表單送出404