From d0b67275d2623cdb8fa6fb8b25b01e9854e9d1fc Mon Sep 17 00:00:00 2001 From: water <672684719@qq.com> Date: Wed, 26 Aug 2026 08:10:00 +0000 Subject: [PATCH] fix(basic-auth): reject empty consumer password and fail closed RFC 8265 section 4.1 requires the OpaqueString password profile to be non-zero-length. The consumer schema currently declares `password` as plain `{ type = "string" }` with no minLength, so the Admin API accepts a consumer whose password is an empty string. Such a consumer authenticates, appearing protected while effectively having no secret. Once #13836 lands (split on the first colon per RFC 7617), `user:` would also return 200 for an empty-password consumer. Fix: - add `minLength = 1` to `consumer_schema.password` so the Admin API rejects empty passwords on consumer create/update - fail closed in `find_consumer` when either the presented or the resolved password is empty, covering legacy empty-password consumers and `$secret://` / `$ENV://` references that resolve to "" Regression tests: consumer schema rejects empty password, Admin API rejects empty consumer password, empty/whitespace-only passwords in the Authorization header return 401. Closes #13881 --- apisix/plugins/basic-auth.lua | 12 +++++- t/plugin/basic-auth.t | 78 +++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/apisix/plugins/basic-auth.lua b/apisix/plugins/basic-auth.lua index 5d3f46a94201..47e70c07b44d 100644 --- a/apisix/plugins/basic-auth.lua +++ b/apisix/plugins/basic-auth.lua @@ -43,7 +43,10 @@ local consumer_schema = { title = "work with consumer object", properties = { username = { type = "string" }, - password = { type = "string" }, + password = { + type = "string", + minLength = 1, + }, }, encrypt_fields = {"password"}, required = {"username", "password"}, @@ -148,6 +151,13 @@ local function find_consumer(ctx) return nil, nil, "Invalid user authorization" end + -- RFC 8265 section 4.1: a password MUST NOT be zero-length. Fail closed + -- when the resolved password is empty, which also covers existing data + -- and $secret:// / $ENV:// references that resolve to "". + if password == "" or cur_consumer.auth_conf.password == "" then + return nil, nil, "Invalid user authorization" + end + if cur_consumer.auth_conf.password ~= password then return nil, nil, "Invalid user authorization" end diff --git a/t/plugin/basic-auth.t b/t/plugin/basic-auth.t index 2e19a2f6d7f1..23748ca7770d 100644 --- a/t/plugin/basic-auth.t +++ b/t/plugin/basic-auth.t @@ -708,3 +708,81 @@ Authorization: bASiC Zm9vOmJhcg== hello world --- error_log find consumer foo + + +=== TEST 40: consumer schema rejects empty password +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local plugin = require("apisix.plugins.basic-auth") + local ok, err = plugin.check_schema({username = 'foo', password = ''}, core.schema.TYPE_CONSUMER) + if ok then + ngx.say("unexpected: schema accepted empty password") + return + end + ngx.say(err or "rejected") + } + } +--- request +GET /t +--- response_body_like +property "password" validation failed +--- no_error_log +unexpected + + + +=== TEST 41: admin api rejects consumer with empty password +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/consumers', + ngx.HTTP_PUT, + [[{ + "username": "empty-pass", + "plugins": { + "basic-auth": { + "username": "empty-pass", + "password": "" + } + } + }]] + ) + if code < 400 then + ngx.status = 500 + ngx.say("unexpected: admin api accepted empty password") + return + end + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body_like +property "password" validation failed +--- no_error_log +unexpected + + + +=== TEST 42: empty password in Authorization header is rejected +--- request +GET /hello +--- more_headers +Authorization: Basic Zm9vOg== +--- error_code: 401 + + + +=== TEST 43: whitespace-only password in Authorization header is rejected +--- request +GET /hello +--- more_headers +Authorization: Basic Zm9vOiA= +--- error_code: 401 +--- response_body +{"message":"Invalid user authorization"}