From 5740694f2ca57e48e6331b9a90677536d5b4fa5f Mon Sep 17 00:00:00 2001 From: bhuvan-somisetty Date: Sun, 11 Oct 2026 04:36:26 +0530 Subject: [PATCH 1/2] fix(csrf): safely validate decoded token table and fields In `apisix/plugins/csrf.lua`, `check_csrf_token` decodes the base64 token into JSON without verifying that the result is a table. When a client supplies a token decoding to a JSON primitive (e.g. number, boolean, null), indexing fields like `token_table["random"]` raises an unhandled Lua runtime error (e.g. `attempt to index a number value`), causing a 500 error. This commit verifies `type(token_table) == "table"` and checks that `random`, `expires`, and `sign` have valid types, safely returning `false` on invalid tokens. Signed-off-by: bhuvan-somisetty --- apisix/plugins/csrf.lua | 12 ++++++----- t/plugin/csrf.t | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/apisix/plugins/csrf.lua b/apisix/plugins/csrf.lua index 4ed2ad624aa7..88af4fa2d49c 100644 --- a/apisix/plugins/csrf.lua +++ b/apisix/plugins/csrf.lua @@ -23,6 +23,8 @@ local ngx_decode_base64 = ngx.decode_base64 local ngx_time = ngx.time local ngx_cookie_time = ngx.cookie_time local math = math +local type = type +local tonumber = tonumber local SAFE_METHODS = {"GET", "HEAD", "OPTIONS"} @@ -98,18 +100,18 @@ local function check_csrf_token(conf, ctx, token) end local token_table, err = core.json.decode(token_str) - if err then - core.log.error("decode token error: ", err) + if err or type(token_table) ~= "table" then + core.log.error("decode token error: ", err or "token is not an object") return false end local random = token_table["random"] - if not random then + if not random or (type(random) ~= "number" and type(random) ~= "string") then core.log.error("no random in token") return false end - local expires = token_table["expires"] + local expires = tonumber(token_table["expires"]) if not expires then core.log.error("no expires in token") return false @@ -121,7 +123,7 @@ local function check_csrf_token(conf, ctx, token) end local sign = gen_sign(random, expires, conf.key) - if token_table["sign"] ~= sign then + if type(token_table["sign"]) ~= "string" or token_table["sign"] ~= sign then core.log.error("Invalid signatures") return false end diff --git a/t/plugin/csrf.t b/t/plugin/csrf.t index 6ed8b959256c..2465d02e4826 100644 --- a/t/plugin/csrf.t +++ b/t/plugin/csrf.t @@ -388,3 +388,49 @@ apisix: --- response_body userkey mt39FazQccyMqt4ctoRV7w== + + + +=== TEST 16: non-table csrf token (JSON number) +--- request +POST /hello +--- more_headers +apisix-csrf-token: MTIz +Cookie: apisix-csrf-token=MTIz +--- error_code: 401 +--- error_log: decode token error +--- no_error_log +attempt to index +--- response_body +{"error_msg":"Failed to verify the csrf token signature"} + + + +=== TEST 17: non-table csrf token (JSON boolean) +--- request +POST /hello +--- more_headers +apisix-csrf-token: dHJ1ZQ== +Cookie: apisix-csrf-token=dHJ1ZQ== +--- error_code: 401 +--- error_log: decode token error +--- no_error_log +attempt to index +--- response_body +{"error_msg":"Failed to verify the csrf token signature"} + + + +=== TEST 18: csrf token with invalid non-numeric expires +--- request +POST /hello +--- more_headers +apisix-csrf-token: eyJyYW5kb20iOjAuMSwiZXhwaXJlcyI6ImludmFsaWQiLCJzaWduIjoiYWJjIn0= +Cookie: apisix-csrf-token=eyJyYW5kb20iOjAuMSwiZXhwaXJlcyI6ImludmFsaWQiLCJzaWduIjoiYWJjIn0= +--- error_code: 401 +--- error_log: no expires in token +--- no_error_log +attempt to perform arithmetic +--- response_body +{"error_msg":"Failed to verify the csrf token signature"} + From f9c8cc8a4793656c35a0654dc451c342ec384f3d Mon Sep 17 00:00:00 2001 From: bhuvan-somisetty Date: Thu, 15 Oct 2026 20:30:49 +0530 Subject: [PATCH 2/2] test(csrf): cover null token and invalid random/sign types Signed-off-by: bhuvan-somisetty --- t/plugin/csrf.t | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/t/plugin/csrf.t b/t/plugin/csrf.t index 2465d02e4826..aad4809964ec 100644 --- a/t/plugin/csrf.t +++ b/t/plugin/csrf.t @@ -434,3 +434,46 @@ attempt to perform arithmetic --- response_body {"error_msg":"Failed to verify the csrf token signature"} + + + +=== TEST 19: non-table csrf token (JSON null) +--- request +POST /hello +--- more_headers +apisix-csrf-token: bnVsbA== +Cookie: apisix-csrf-token=bnVsbA== +--- error_code: 401 +--- error_log: decode token error +--- no_error_log +attempt to index +--- response_body +{"error_msg":"Failed to verify the csrf token signature"} + + + +=== TEST 20: csrf token with invalid random type +--- request +POST /hello +--- more_headers +apisix-csrf-token: eyJyYW5kb20iOnRydWUsImV4cGlyZXMiOjk5OTk5OTk5OTksInNpZ24iOiJhYmMifQ== +Cookie: apisix-csrf-token=eyJyYW5kb20iOnRydWUsImV4cGlyZXMiOjk5OTk5OTk5OTksInNpZ24iOiJhYmMifQ== +--- error_code: 401 +--- error_log: no random in token +--- no_error_log +attempt to concatenate +--- response_body +{"error_msg":"Failed to verify the csrf token signature"} + + + +=== TEST 21: csrf token with invalid sign type +--- request +POST /hello +--- more_headers +apisix-csrf-token: eyJyYW5kb20iOjAuMSwiZXhwaXJlcyI6OTk5OTk5OTk5OSwic2lnbiI6MTIzfQ== +Cookie: apisix-csrf-token=eyJyYW5kb20iOjAuMSwiZXhwaXJlcyI6OTk5OTk5OTk5OSwic2lnbiI6MTIzfQ== +--- error_code: 401 +--- error_log: Invalid signatures +--- response_body +{"error_msg":"Failed to verify the csrf token signature"}