From 3916e2aeee2238ca5a73b9e80432b2e761a03014 Mon Sep 17 00:00:00 2001 From: Fredrik Hindersson Date: Tue, 23 Jun 2026 16:09:57 +0000 Subject: [PATCH] gatekeeper: Block X-Container-Host and other update headers from clients Backport of the upstream fix for CVE-2026-50221 / OSSA-2026-024 (Swift proxy-server SSRF via header injection) to the elastx/yoga branch. There are a variety of headers that object-servers use to update container-servers, or that container-servers use to update account-servers. None of them start with X-Backend- (they predated that namespace), and previously were not blocked by the gatekeeper. As a result, a malicious authenticated client could direct the object-server to send updates to unintended locations (server-side request forgery). Now block all update-related X-Account-*, X-Container-*, and X-Delete-At-* headers at the gatekeeper. Upstream only backported as far as 2025.1/epoxy; Yoga is EOL upstream, so this carries the same change onto our fork. Cherry-picked from upstream commit 75b050fdb1d1a6096f3765faa3b2059e28065d13 (2025.1/epoxy backport: https://review.opendev.org/994452). Fixes: CVE-2026-50221 Closes-Bug: #2150261 Original-Author: Tim Burke --- swift/common/middleware/gatekeeper.py | 16 +++++++++++----- test/unit/common/middleware/test_gatekeeper.py | 13 +++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/swift/common/middleware/gatekeeper.py b/swift/common/middleware/gatekeeper.py index 0254fef98b..d37accd2c9 100644 --- a/swift/common/middleware/gatekeeper.py +++ b/swift/common/middleware/gatekeeper.py @@ -48,11 +48,17 @@ # If adding to this list, note that these are regex patterns, # so use a trailing $ to constrain to an exact header match # rather than prefix match. -inbound_exclusions = [get_sys_meta_prefix('account'), - get_sys_meta_prefix('container'), - get_sys_meta_prefix('object'), - OBJECT_TRANSIENT_SYSMETA_PREFIX, - 'x-backend'] +inbound_exclusions = [ + get_sys_meta_prefix('account'), + get_sys_meta_prefix('container'), + get_sys_meta_prefix('object'), + OBJECT_TRANSIENT_SYSMETA_PREFIX, + 'x-backend', + # Block any update headers that predate x-backend headers + 'x-account-(host|device|partition)$', + 'x-container-(host|device|partition|root-db-state)$', + 'x-delete-at-(host|device|partition|container)$', +] #: A list of python regular expressions that will be used to diff --git a/test/unit/common/middleware/test_gatekeeper.py b/test/unit/common/middleware/test_gatekeeper.py index 4f8cb480b7..735abec0a6 100644 --- a/test/unit/common/middleware/test_gatekeeper.py +++ b/test/unit/common/middleware/test_gatekeeper.py @@ -74,6 +74,18 @@ class TestGatekeeper(unittest.TestCase): x_backend_headers = {'X-Backend-Replication': 'true', 'X-Backend-Replication-Headers': 'stuff'} + update_headers = {'X-Account-Host': 'localhost:8888', + 'X-Account-Device': 'stuff', + 'X-Account-Partition': '0', + 'X-Container-Host': 'localhost:8888', + 'X-Container-Device': 'stuff', + 'X-Container-Partition': '0', + 'X-Container-Root-Db-State': 'stuff', + 'X-Delete-At-Host': 'localhost:8888', + 'X-Delete-At-Container': 'stuff', + 'X-Delete-At-Device': 'stuff', + 'X-Delete-At-Partition': '0'} + object_transient_sysmeta_headers = { 'x-object-transient-sysmeta-': 'value', 'x-object-transient-sysmeta-foo': 'value'} @@ -82,6 +94,7 @@ class TestGatekeeper(unittest.TestCase): forbidden_headers_out = dict(sysmeta_headers) forbidden_headers_out.update(x_backend_headers) forbidden_headers_out.update(object_transient_sysmeta_headers) + forbidden_headers_out.update(update_headers) forbidden_headers_in = dict(forbidden_headers_out) shunted_headers_in = dict(x_timestamp_headers)