This is an automated email from the ASF dual-hosted git repository.
shreemaan-abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 0e70e6919 fix: reset X-Forwarded-For only for untrusted sources under
trusted_addresses (#13611)
0e70e6919 is described below
commit 0e70e691947f35594117f63a3e2559f63f69fcc2
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Wed Jul 1 18:54:39 2026 +0800
fix: reset X-Forwarded-For only for untrusted sources under
trusted_addresses (#13611)
---
apisix/init.lua | 22 ++++++---
apisix/utils/trusted-addresses.lua | 6 +++
conf/config.yaml.example | 9 +++-
docs/en/latest/plugins/chaitin-waf.md | 2 +-
docs/en/latest/plugins/real-ip.md | 2 +-
docs/zh/latest/plugins/chaitin-waf.md | 2 +-
docs/zh/latest/plugins/real-ip.md | 2 +-
t/core/trusted-addresses.t | 88 +++++++++++++++++++++++++++++++++--
t/plugin/real-ip.t | 3 +-
9 files changed, 118 insertions(+), 18 deletions(-)
diff --git a/apisix/init.lua b/apisix/init.lua
index 32c9dfa08..51ae3084e 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -660,7 +660,8 @@ local function handle_x_forwarded_headers(api_ctx)
-- making them highly credible.
local proto = api_ctx.var.scheme
local http_host = api_ctx.var.http_host or api_ctx.var.host
- local _, port_from_host = http_host:match("^(.+):(%d+)$")
+ -- parse_addr handles IPv6 literals and bracketed host:port correctly.
+ local _, port_from_host = core.utils.parse_addr(http_host)
local host = http_host
local port = port_from_host or api_ctx.var.server_port
@@ -670,18 +671,25 @@ local function handle_x_forwarded_headers(api_ctx)
core.request.set_header(api_ctx, "X-Forwarded-Proto", proto)
core.request.set_header(api_ctx, "X-Forwarded-Host", host)
core.request.set_header(api_ctx, "X-Forwarded-Port", port)
- -- later processed in ngx_tpl by `$proxy_add_x_forwarded_for`.
- core.request.set_header(api_ctx, "X-Forwarded-For", nil)
-- Clear RFC 7239 Forwarded header to prevent forgery.
core.request.set_header(api_ctx, "Forwarded", nil)
+ -- X-Forwarded-For: when a trust boundary is configured but this peer
is
+ -- untrusted, reset it so the upstream only sees the APISIX-observed
+ -- connection IP via `$proxy_add_x_forwarded_for`, dropping the
spoofable
+ -- inbound chain. When `trusted_addresses` is unset, keep the
compatible
+ -- default of preserving the inbound chain (the connection IP is
appended).
+ if trusted_addresses_util.is_configured() then
+ core.request.set_header(api_ctx, "X-Forwarded-For", nil)
+ api_ctx.var.http_x_forwarded_for = nil
+ end
+
-- update the cached value in http_x_forwarded_* to the trusted ones.
-- make sure that the correct values are obtained
-- in the subsequent stages using `var.http_x_forwarded_*`.
api_ctx.var.http_x_forwarded_proto = proto
api_ctx.var.http_x_forwarded_host = host
api_ctx.var.http_x_forwarded_port = port
- api_ctx.var.http_x_forwarded_for = nil
api_ctx.var.http_forwarded = nil
end
end
@@ -890,9 +898,11 @@ function _M.http_access_phase()
end
span:finish(ngx_ctx)
- _M.handle_upstream(api_ctx, route, enable_websocket)
-
+ -- set before handle_upstream: grpc/dubbo/disable_proxy_buffering exit via
+ -- ngx.exec() and never return, so the trusted values must be applied
first.
set_upstream_x_forwarded_headers(api_ctx)
+
+ _M.handle_upstream(api_ctx, route, enable_websocket)
end
diff --git a/apisix/utils/trusted-addresses.lua
b/apisix/utils/trusted-addresses.lua
index df2a314a2..3e1fbe6a5 100644
--- a/apisix/utils/trusted-addresses.lua
+++ b/apisix/utils/trusted-addresses.lua
@@ -49,4 +49,10 @@ function _M.is_trusted(address)
return trusted_addresses_matcher:match(address)
end
+
+-- whether a trust boundary is configured via `apisix.trusted_addresses`.
+function _M.is_configured()
+ return trusted_addresses_matcher ~= nil
+end
+
return _M
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index f83b0e0f8..b7b84d8bb 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -156,8 +156,13 @@ apisix:
# When set to true, it overrides all
upstream healthcheck configurations and globally disabling healthchecks.
# trusted_addresses: # When configured, APISIX will trust the
`X-Forwarded-*` Headers
# - 127.0.0.1 # passed in requests from the IP/CIDR in the
list.
-# - 172.18.0.0/16 # CAUTION: When not configured or the
request from an untrusted address,
- # APISIX will override `X-Forwarded-*`
headers with trusted values.
+# - 172.18.0.0/16 # CAUTION: When not configured or the
request is from an untrusted
+ # address, APISIX overrides
`X-Forwarded-Proto/Host/Port` with trusted
+ # values and clears the `Forwarded` header.
For `X-Forwarded-For`: when
+ # `trusted_addresses` is configured and the
request is from an untrusted
+ # address, it is reset so the upstream only
sees the APISIX-observed
+ # connection IP; when `trusted_addresses` is
not configured, it is
+ # preserved and the connection IP is
appended (compatible default).
# fine tune the parameters of LRU cache for some features like secret
lru:
secret:
diff --git a/docs/en/latest/plugins/chaitin-waf.md
b/docs/en/latest/plugins/chaitin-waf.md
index 16a658791..dd8ef5a6a 100644
--- a/docs/en/latest/plugins/chaitin-waf.md
+++ b/docs/en/latest/plugins/chaitin-waf.md
@@ -90,7 +90,7 @@ The examples below demonstrate how you can configure
chaitin-waf Plugin for diff
Before proceeding, make sure you have installed [Chaitin WAF
(SafeLine)](https://docs.waf.chaitin.com/en/GetStarted/Deploy).
:::note
-Only `X-Forwarded-*` headers sent from addresses in the
`apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted
and passed to plugins or upstream. If `apisix.trusted_addresses` is not
configured or the IP is not within the configured address range, all
`X-Forwarded-*` headers will be overridden with trusted values.
+Only `X-Forwarded-*` headers sent from addresses in the
`apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted
and passed to plugins or upstream. If `apisix.trusted_addresses` is not
configured or the IP is not within the configured address range, the
`X-Forwarded-Proto`, `X-Forwarded-Host`, and `X-Forwarded-Port` headers are
overridden with trusted values and the `Forwarded` header is cleared. The
`X-Forwarded-For` header is reset to the APISIX-observed connecti [...]
:::
:::note
diff --git a/docs/en/latest/plugins/real-ip.md
b/docs/en/latest/plugins/real-ip.md
index 1d9977906..215cdeddf 100644
--- a/docs/en/latest/plugins/real-ip.md
+++ b/docs/en/latest/plugins/real-ip.md
@@ -49,7 +49,7 @@ The Plugin is functionally similar to NGINX's
[ngx_http_realip_module](https://n
| recursive | boolean | False | False | | If false, replace the
original client address that matches one of the trusted addresses by the last
address sent in the configured `source`.<br />If true, replace the original
client address that matches one of the trusted addresses by the last
non-trusted address sent in the configured `source`. |
:::note
-Only `X-Forwarded-*` headers sent from addresses in the
`apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted
and passed to plugins or upstream. If `apisix.trusted_addresses` is not
configured or the IP is not within the configured address range, all
`X-Forwarded-*` headers will be overridden with trusted values.
+Only `X-Forwarded-*` headers sent from addresses in the
`apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted
and passed to plugins or upstream. If `apisix.trusted_addresses` is not
configured or the IP is not within the configured address range, the
`X-Forwarded-Proto`, `X-Forwarded-Host`, and `X-Forwarded-Port` headers are
overridden with trusted values and the `Forwarded` header is cleared. The
`X-Forwarded-For` header is reset to the APISIX-observed connecti [...]
:::
:::note
diff --git a/docs/zh/latest/plugins/chaitin-waf.md
b/docs/zh/latest/plugins/chaitin-waf.md
index 7a6193f7d..73a29b60d 100644
--- a/docs/zh/latest/plugins/chaitin-waf.md
+++ b/docs/zh/latest/plugins/chaitin-waf.md
@@ -90,7 +90,7 @@ description: chaitin-waf 插件与长亭雷池 WAF 集成,以检测和阻止
继续操作之前,请确保您已安装 [长亭雷池 WAF](https://docs.waf.chaitin.com/en/GetStarted/Deploy)。
:::note
-只有发送自 `apisix.trusted_addresses` 配置(支持 IP 和 CIDR)地址的 `X-Forwarded-*`
头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip
不在配置地址范围内的,`X-Forwarded-*` 头将全部被可信值覆盖。
+只有发送自 `apisix.trusted_addresses` 配置(支持 IP 和 CIDR)地址的 `X-Forwarded-*`
头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip
不在配置地址范围内的,`X-Forwarded-Proto`、`X-Forwarded-Host`、`X-Forwarded-Port`
头将被可信值覆盖,并清除 `Forwarded` 头。对于 `X-Forwarded-For`:当配置了 `apisix.trusted_addresses`
且请求来自不可信地址时,该头会被重置,使上游仅获取到 APISIX 观测到的连接 IP;当未配置 `apisix.trusted_addresses`
时,该头会被保留,并追加连接 IP(兼容的默认行为)。
:::
:::note
diff --git a/docs/zh/latest/plugins/real-ip.md
b/docs/zh/latest/plugins/real-ip.md
index c8628b719..96eb8cac0 100644
--- a/docs/zh/latest/plugins/real-ip.md
+++ b/docs/zh/latest/plugins/real-ip.md
@@ -49,7 +49,7 @@ import TabItem from '@theme/TabItem';
| recursive | boolean | 否 | false |
| 如果为 false,则将匹配可信地址之一的原始客户端地址替换为配置的 `source` 中发送的最后一个地址。<br />如果为
true,则将匹配可信地址之一的原始客户端地址替换为配置的 `source` 中发送的最后一个非可信地址。 |
:::note
-只有发送自 `apisix.trusted_addresses` 配置 (支持 IP 和 CIDR) 地址的 `X-Forwarded-*`
头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip
不在配置地址范围内的,`X-Forwarded-*` 头将全部被可信值覆盖。
+只有发送自 `apisix.trusted_addresses` 配置 (支持 IP 和 CIDR) 地址的 `X-Forwarded-*`
头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip
不在配置地址范围内的,`X-Forwarded-Proto`、`X-Forwarded-Host`、`X-Forwarded-Port`
头将被可信值覆盖,并清除 `Forwarded` 头。对于 `X-Forwarded-For`:当配置了 `apisix.trusted_addresses`
且请求来自不可信地址时,该头会被重置,使上游仅获取到 APISIX 观测到的连接 IP;当未配置 `apisix.trusted_addresses`
时,该头会被保留,并追加连接 IP(兼容的默认行为)。
:::
:::note
diff --git a/t/core/trusted-addresses.t b/t/core/trusted-addresses.t
index c8a0fc6fa..233bca36c 100644
--- a/t/core/trusted-addresses.t
+++ b/t/core/trusted-addresses.t
@@ -32,7 +32,7 @@ run_tests();
__DATA__
-=== TEST 1: without trusted_addresses configuration, X-Forwarded headers
should be overridden
+=== TEST 1: without trusted_addresses, X-Forwarded-For is preserved while
others are overridden
--- yaml_config
apisix:
node_listen: 1984
@@ -54,13 +54,14 @@ routes:
--- request
GET /old_uri
--- more_headers
+X-Forwarded-For: 1.2.3.4
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
X-Forwarded-Port: 8443
--- response_body
uri: /old_uri
host: localhost
-x-forwarded-for: 127.0.0.1
+x-forwarded-for: 1.2.3.4, 127.0.0.1
x-forwarded-host: localhost
x-forwarded-port: 1984
x-forwarded-proto: http
@@ -95,13 +96,14 @@ routes:
--- request
GET /old_uri
--- more_headers
+X-Forwarded-For: 1.2.3.4
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
X-Forwarded-Port: 8443
--- response_body
uri: /old_uri
host: localhost
-x-forwarded-for: 127.0.0.1
+x-forwarded-for: 1.2.3.4, 127.0.0.1
x-forwarded-host: example.com
x-forwarded-port: 8443
x-forwarded-proto: https
@@ -319,7 +321,7 @@ x-real-ip: 127.0.0.1
-=== TEST 8: with trusted_addresses configuration, but client not in trusted
list, X-Forwarded headers should be overridden
+=== TEST 8: client not in trusted list, X-Forwarded-For is reset along with
the others
--- yaml_config
apisix:
node_listen: 1984
@@ -344,6 +346,7 @@ routes:
--- request
GET /old_uri
--- more_headers
+X-Forwarded-For: 1.2.3.4
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
X-Forwarded-Port: 8443
@@ -358,3 +361,80 @@ x-real-ip: 127.0.0.1
--- no_error_log
trusted_addresses is not configured
trusted_addresses_matcher is not initialized
+
+
+
+=== TEST 9: client not in trusted list, RFC 7239 Forwarded header is cleared
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "1.0.0.1"
+ - "10.0.0.0/8"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /old_uri
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+Forwarded: for=1.2.3.4;host=evil.com;proto=https
+--- response_body
+uri: /old_uri
+host: localhost
+x-forwarded-for: 127.0.0.1
+x-forwarded-host: localhost
+x-forwarded-port: 1984
+x-forwarded-proto: http
+x-real-ip: 127.0.0.1
+
+
+
+=== TEST 10: with `0.0.0.0/0`, RFC 7239 Forwarded header is preserved from
trusted client
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "0.0.0.0/0"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /old_uri
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+Forwarded: for=1.2.3.4;host=evil.com;proto=https
+X-Forwarded-Proto: https
+X-Forwarded-Host: example.com
+X-Forwarded-Port: 8443
+--- response_body
+uri: /old_uri
+forwarded: for=1.2.3.4;host=evil.com;proto=https
+host: localhost
+x-forwarded-for: 127.0.0.1
+x-forwarded-host: example.com
+x-forwarded-port: 8443
+x-forwarded-proto: https
+x-real-ip: 127.0.0.1
diff --git a/t/plugin/real-ip.t b/t/plugin/real-ip.t
index fafe0b4a5..916a5374e 100644
--- a/t/plugin/real-ip.t
+++ b/t/plugin/real-ip.t
@@ -473,8 +473,7 @@ X-Forwarded-For: 1.1.1.1, 192.128.1.1, 127.0.0.1
-=== TEST 24: trusted in real-ip, but not trusted by `apisix.trusted_addresses`
-should be rejected
+=== TEST 24: untrusted by `apisix.trusted_addresses`, forged X-Forwarded-For
is stripped before real-ip
--- yaml_config
apisix:
node_listen: 1984