From 237b424a16fb22b0278fc2cf0ae10d69fbce088f Mon Sep 17 00:00:00 2001 From: An Tran Date: Fri, 18 Oct 2024 17:00:03 +1000 Subject: [PATCH] [http_authorization] Check for nil value when decode based64 value Performing a match on a nil value results in an exception being thrown and bypassing the entire authorization validation process. --- CHANGELOG.md | 36 ++ gateway/src/resty/http_authorization.lua | 5 +- spec/resty/http_authorization_spec.lua | 7 + t/apicast.t | 524 +++++++++++++++++++++++ 4 files changed, 571 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 001aea2ea..1ef20db19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,42 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- Fixed 3scale Batcher policy unable to handle `app_id`/`access_token` contains special characters [PR #1457](https://github.com/3scale/APIcast/pull/1457) [THREESCALE-10934](https://issues.redhat.com/browse/THREESCALE-10934) + +- Fixed APIcast send request through proxy server even when `NO_PROXY` is used [PR #1478](https://github.com/3scale/APIcast/pull/1478) [THREESCALE-11128](https://issues.redhat.com/browse/THREESCALE-11128) + +- Fixed config reloading even when reloading is disabled [PR #1468](https://github.com/3scale/APIcast/pull/1468) + +- Fixed confusing log display when APIcast listens on HTTPS and path routing is enabled [PR #1486](https://github.com/3scale/APIcast/pull/1486/files) [THREESCALE #8486](https://issues.redhat.com/browse/THREESCALE-8486) + +- Fixed Conditional policy evaluating incorrectly: second policy in policy chain that implement export() always triggers [PR #1485](https://github.com/3scale/APIcast/pull/1485) [THREESCALE-9320](https://issues.redhat.com/browse/THREESCALE-9320) +- Fix APIcast using stale configuration for deleted products [PR #1488](https://github.com/3scale/APIcast/pull/1488) [THREESCALE-10130](https://issues.redhat.com/browse/THREESCALE-10130) +- Fixed Mutual TLS between APIcast and the Backend API fails when using a Forward Proxy [PR #1499](https://github.com/3scale/APIcast/pull/1499) [THREESCALE-5105](https://issues.redhat.com/browse/THREESCALE-5105) +- Fixed dns cache miss [PR #1500](https://github.com/3scale/APIcast/pull/1500) [THEESCALE-9301](https://issues.redhat.com/browse/THREESCALE-9301) +- Fixed APIcast panic when parsing invalid base64 encoded value [PR #1505](https://github.com/3scale/APIcast/pull/1505) [THEESCALE-11435](https://issues.redhat.com/browse/THREESCALE-11435) + +### Added + +- Bump openresty to 1.21.4.3 [PR #1461](https://github.com/3scale/APIcast/pull/1461) [THREESCALE-10601](https://issues.redhat.com/browse/THREESCALE-10601) + +- Support Financial-grade API (FAPI) 1.0 - Baseline profile [PR #1465](https://github.com/3scale/APIcast/pull/1465) [THREESCALE-10973](https://issues.redhat.com/browse/THREESCALE-10973) + +- Support Financial-grade API (FAPI) 1.0 - Advance profile [PR #1465](https://github.com/3scale/APIcast/pull/1466) [THREESCALE-11019](https://issues.redhat.com/browse/THREESCALE-11019) + +- Token Introspection Policy - Support `private_key_jwt` and `client_secret_jwt` authentication mode [PR #1464](https://github.com/3scale/APIcast/pull/1464) [THREESCALE-11015](https://issues.redhat.com/browse/THREESCALE-11015) + +- Added the `APICAST_PROXY_BUFFER_SIZE` variable to allow configuration of the buffer size for handling response from the proxied servers. [PR #1473](https://github.com/3scale/APIcast/pull/1473), [THREESCALE-8410](https://issues.redhat.com/browse/THREESCALE-8410) + +- Added the `APICAST_HTTPS_VERIFY_CLIENT` variable to allow configuration of the `ssl_verify_client` directive. [PR #1491](https://github.com/3scale/APIcast/pull/1491) [THREESCALE-10156](https://issues.redhat.com/browse/THREESCALE-10156) +- Add `APICAST_LUA_SOCKET_KEEPALIVE_REQUESTS` to limit the number of requests a single keepalive socket can handle [PR #1496](https://github.com/3scale/APIcast/pull/1496) [THREESCALE-11321](https://issues.redhat.com/browse/THREESCALE-11321) +- Replace internal OPENSSL module with lua-resty-openssl [PR #1502](https://github.com/3scale/APIcast/pull/1502) [THREESCALE-11412](https://issues.redhat.com/browse/THREESCALE-11412) + +## [3.15.0] 2024-04-04 + +### Fixed + +- Fix GRPC on HTTP2 POST method [PR #1419](https://github.com/3scale/apicast/pull/1419) [THREESCALE-9976](https://issues.redhat.com/browse/THREESCALE-9976) + - Fixed CVE-2023-44487 (HTTP/2 Rapid Reset) [PR #1417](https://github.com/3scale/apicast/pull/1417) [THREESCALE-10224](https://issues.redhat.com/browse/THREESCALE-10224) ### Added diff --git a/gateway/src/resty/http_authorization.lua b/gateway/src/resty/http_authorization.lua index 89a990e47..4a618404f 100644 --- a/gateway/src/resty/http_authorization.lua +++ b/gateway/src/resty/http_authorization.lua @@ -9,8 +9,11 @@ local _M = { local mt = { __index = _M } function _M.parsers.Basic(param) + local userid, password local user_pass = ngx.decode_base64(param) - local userid, password = match(user_pass, '^(.*):(.*)$') + if user_pass then + userid, password = match(user_pass, '^(.*):(.*)$') + end return { userid = userid, diff --git a/spec/resty/http_authorization_spec.lua b/spec/resty/http_authorization_spec.lua index 0f50b7636..4e94a0073 100644 --- a/spec/resty/http_authorization_spec.lua +++ b/spec/resty/http_authorization_spec.lua @@ -60,6 +60,13 @@ describe('HTTP Authorization', function() assert.equal('', auth.userid) assert.equal('pass', auth.password) end) + + it('do not panic with invalid header', function() + local auth = authorization.new('Basic !123!') + + assert.equal(nil, auth.userid) + assert.equal(nil, auth.password) + end) end) describe('Bearer', function() diff --git a/t/apicast.t b/t/apicast.t index 94f089344..213a17201 100644 --- a/t/apicast.t +++ b/t/apicast.t @@ -786,3 +786,527 @@ Authentication failed --- no_error_log [error] + + +=== TEST 24: with user_key in header +--- configuration +{ + "services": [ + { + "backend_version": "1", + "id": 42, + "proxy": { + "credentials_location": "headers", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&user_key=value" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- more_headers +user_key: value +--- request +GET / +--- response_body +yay, api backend +--- error_code: 200 +--- no_error_log +[error] + + + +=== TEST 25: with invalid user_key in header +--- configuration +{ + "services": [ + { + "backend_version": "1", + "id": 42, + "proxy": { + "credentials_location": "headers", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&user_key=value" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- more_headers +user_key: !123! +--- request +GET / +--- response_body chomp +Authentication failed +--- error_code: 403 + + + +=== TEST 26: with user_key in query parameters +--- configuration +{ + "services": [ + { + "backend_version": "1", + "id": 42, + "proxy": { + "credentials_location": "query", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&user_key=value" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- request +GET /?user_key=value +--- response_body +yay, api backend +--- error_code: 200 +--- no_error_log +[error] + + + +=== TEST 27: with invalid user_key in query parameters +--- configuration +{ + "services": [ + { + "backend_version": "1", + "id": 42, + "proxy": { + "credentials_location": "query", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&user_key=value" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- request +GET /?user_key= +--- response_body chomp +Authentication failed +--- error_code: 403 + + + +=== TEST 28: with user_key in Basic Authorization header +--- configuration +{ + "services": [ + { + "backend_version": "1", + "id": 42, + "proxy": { + "credentials_location": "authorization", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&user_key=value" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- more_headers +Authorization: Basic dmFsdWU6Cg== +--- request +GET / +--- response_body +yay, api backend +--- error_code: 200 +--- no_error_log +[error] + + + +=== TEST 29: with invalid user_key in Basic Authorization header +--- configuration +{ + "services": [ + { + "backend_version": "1", + "id": 42, + "proxy": { + "credentials_location": "query", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&user_key=value" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- more_headers +Authorization: Basic !123! +--- request +GET / +--- response_body chomp +Authentication parameters missing +--- error_code: 401 + + + +=== TEST 30: with app_id and app_key in header +--- configuration +{ + "services": [ + { + "backend_version": "2", + "id": 42, + "proxy": { + "credentials_location": "headers", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&app_id=foo&app_key=bar" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- more_headers +app_id: foo +app_key: bar +--- request +GET / +--- response_body +yay, api backend +--- error_code: 200 +--- no_error_log +[error] + + + +=== TEST 31: with invalid app_key and app_id in header +--- configuration +{ + "services": [ + { + "backend_version": "2", + "id": 42, + "proxy": { + "credentials_location": "headers", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&app_id=foo&app_key=bar" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- more_headers +app_id: foo +app_key: !123! +--- request +GET / +--- response_body chomp +Authentication failed +--- error_code: 403 + + + +=== TEST 32: with app_key and app_id in query parameters +--- configuration +{ + "services": [ + { + "backend_version": "2", + "id": 42, + "proxy": { + "credentials_location": "query", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&app_id=foo&app_key=bar" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- request +GET /?app_id=foo&app_key=bar +--- response_body +yay, api backend +--- error_code: 200 +--- no_error_log +[error] + + + +=== TEST 33: with invalid app_id and app_key in query parameters +--- configuration +{ + "services": [ + { + "backend_version": "2", + "id": 42, + "proxy": { + "credentials_location": "query", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&app_id=foo&app_key=bar" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- request +GET /?app_id=foo&app_key=!123! +--- response_body chomp +Authentication failed +--- error_code: 403 + + + +=== TEST 34: with app_id and app_key in Basic Authorization header +--- configuration +{ + "services": [ + { + "backend_version": "2", + "id": 42, + "proxy": { + "credentials_location": "authorization", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&app_id=foo&app_key=bar" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- more_headers +Authorization: Basic Zm9vOmJhcg== +--- request +GET / +--- response_body +yay, api backend +--- error_code: 200 +--- no_error_log +[error] + + + +=== TEST 35: with invalid app_key/app_id in Basic Authorization header +--- configuration +{ + "services": [ + { + "backend_version": "1", + "id": 42, + "proxy": { + "credentials_location": "query", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_id=42&usage%5Bhits%5D=2&user_key=value" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + content_by_lua_block { + ngx.say('yay, api backend'); + } + } +--- more_headers +Authorization: Basic !123! +--- request +GET / +--- response_body chomp +Authentication parameters missing +--- error_code: 401 + + +