CVE-2023-51982
Description
CrateDB 5.5.1 is contains an authentication bypass vulnerability in the Admin UI component. After configuring password authentication and_ Local_ In the case of an address, identity authentication can be bypassed by setting the X-Real IP request header to a specific value and accessing the Admin UI directly using the default user identity.(https://github.com/crate/crate/issues/15231)
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CrateDB 5.5.1 Admin UI authentication bypass via crafted X-Real-IP header, allowing unauthenticated access.
CVE-2023-51982 is an authentication bypass vulnerability in CrateDB 5.5.1's Admin UI component. The root cause is that the application trusts the X-Real-IP HTTP header when evaluating host-based authentication (HBA) rules [1]. This allows a remote attacker to spoof their IP address and bypass authentication.
Exploitation requires configuring CrateDB with password authentication and a local address HBA rule. An attacker can send a crafted HTTP request to the Admin UI with the X-Real-IP header set to a value that matches the trusted local address (e.g., 10.1.0.100). No valid credentials are needed; the attacker can then access the Admin UI as the default user [1].
Successful exploitation grants the attacker unauthorized access to the CrateDB Admin UI, potentially leading to full control over the database. The impact includes data exposure, modification, or deletion, depending on the privileges of the default user.
The vulnerability has been addressed in commits that disable trust of the X-Real-IP header by default [2][3][4]. Users should update to a patched version or apply the workaround of not using the vulnerable configuration. The issue was reported in the CrateDB issue tracker [1].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
io.crate:crateMaven | < 5.2.11 | 5.2.11 |
io.crate:crateMaven | >= 5.3.0, < 5.3.8 | 5.3.8 |
io.crate:crateMaven | >= 5.4.0, < 5.4.7 | 5.4.7 |
io.crate:crateMaven | >= 5.5.0, < 5.5.2 | 5.5.2 |
Affected products
2- CrateDB/CrateDBdescription
Patches
40c166ef083beDisable trust of HTTP ``X-Real-IP`` header by default.
9 files changed · +189 −27
docs/admin/auth/hba.rst+5 −2 modified@@ -49,8 +49,11 @@ username, IP address, protocol and connection scheme against these entries to determine which authentication method is required. If no entry matches, the client authentication request will be denied. -For HTTP connections the ``X-REAL-IP`` request header has priority over the -actual client IP address in order to allow proxied clients to authenticate. +To support proxied clients to authenticate, the ``X-REAL-IP`` request header +can be used. For security reasons, this is disabled by default as it allows +clients to impersonate other clients. To enable this feature, +set :ref:`auth.trust.http_support_x_real_ip` to ``true``. If enabled, the +``X-REAL-IP`` request header has priority over the actual client IP address. If ``auth.host_based`` is not set, the host based authentication is disabled. In this case CrateDB **trusts all connections** and accepts the user provided by
docs/appendices/release-notes/5.3.8.rst+60 −0 added@@ -0,0 +1,60 @@ +.. _version_5.3.8: + +========================== +Version 5.3.8 - Unreleased +========================== + +.. comment 1. Remove the " - Unreleased" from the header above and adjust the == +.. comment 2. Remove the NOTE below and replace with: "Released on 20XX-XX-XX." +.. comment (without a NOTE entry, simply starting from col 1 of the line) + +.. NOTE:: + + In development. 5.3.8 isn't released yet. These are the release notes for + the upcoming release. + +.. NOTE:: + + If you are upgrading a cluster, you must be running CrateDB 4.0.2 or higher + before you upgrade to 5.3.8. + + We recommend that you upgrade to the latest 5.3 release before moving to + 5.3.8. + + A rolling upgrade from 5.2.x to 5.3.8 is supported. + Before upgrading, you should `back up your data`_. + +.. WARNING:: + + Tables that were created before CrateDB 4.x will not function with 5.x + and must be recreated before moving to 5.x.x. + + You can recreate tables using ``COPY TO`` and ``COPY FROM`` or by + `inserting the data into a new table`_. + +.. _back up your data: https://crate.io/docs/crate/reference/en/latest/admin/snapshots.html +.. _inserting the data into a new table: https://crate.io/docs/crate/reference/en/latest/admin/system-information.html#tables-need-to-be-recreated + +.. rubric:: Table of Contents + +.. contents:: + :local: + +See the :ref:`version_5.3.0` release notes for a full list of changes in the +5.3 series. + +Security Fixes +============== + +- The HTTP transport will not trust any ``X-Real-IP`` header by default anymore. + This prevents a client from spoofing its IP address by setting these headers + and thus bypassing IP based authentication with is enabled by default for the + ``crate`` superuser. + To keep allowing the ``X-Real-IP`` header to be trusted, you have to + explicitly enable it via the :ref:`auth.trust.http_support_x_real_ip` node + setting. + +Fixes +===== + +None
docs/appendices/release-notes/index.rst+1 −0 modified@@ -27,6 +27,7 @@ Versions .. toctree:: :maxdepth: 1 + 5.3.8 5.3.7 5.3.6 5.3.5
docs/config/node.rst+18 −0 modified@@ -495,6 +495,24 @@ Trust authentication to CrateDB via HTTP protocol and they do not specify a user via the ``Authorization`` request header. +.. _auth.trust.http_support_x_real_ip: + +**auth.trust.http_support_x_real_ip** + | *Default:* ``false`` + | *Runtime:* ``no`` + + If enabled, the HTTP transport will trust the ``X-Real-IP`` header sent by + the client to determine the client's IP address. This is useful when CrateDB + is running behind a reverse proxy or load-balancer. For improved security, + any ``_local_`` IP address (``127.0.0.1`` and ``::1``) defined in this header + will be ignored. + +.. warning:: + + Enabling this setting can be a security risk, as it allows clients to + impersonate other clients by sending a fake ``X-Real-IP`` header. + + Host-based authentication -------------------------
.github/styles/CrateDB/Terminology.yml+1 −0 modified@@ -19,3 +19,4 @@ swap: Github: GitHub Hotspot: HotSpot hotspot: HotSpot + proxied: proxied
server/src/main/java/io/crate/auth/AuthSettings.java+8 −3 modified@@ -21,13 +21,13 @@ package io.crate.auth; -import io.crate.types.DataTypes; -import io.netty.handler.ssl.ClientAuth; +import java.util.function.Function; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; -import java.util.function.Function; +import io.crate.types.DataTypes; +import io.netty.handler.ssl.ClientAuth; public final class AuthSettings { @@ -54,6 +54,11 @@ private AuthSettings() { ); public static final String HTTP_HEADER_REAL_IP = "X-Real-Ip"; + public static final Setting<Boolean> AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP = Setting.boolSetting( + "auth.trust.http_support_x_real_ip", + false, + Setting.Property.NodeScope + ); public static ClientAuth resolveClientAuth(Settings settings, Protocol protocol) { Settings hbaSettings = AUTH_HOST_BASED_CONFIG_SETTING.get(settings);
server/src/main/java/io/crate/auth/HttpAuthUpstreamHandler.java+28 −21 modified@@ -21,7 +21,28 @@ package io.crate.auth; +import static io.crate.protocols.SSL.getSession; +import static io.netty.buffer.Unpooled.copiedBuffer; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.security.cert.Certificate; +import java.util.List; +import java.util.Locale; + +import javax.annotation.Nullable; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.common.network.InetAddresses; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.http.netty4.Netty4HttpServerTransport; + import io.crate.common.annotations.VisibleForTesting; +import io.crate.common.collections.Tuple; import io.crate.protocols.SSL; import io.crate.protocols.http.Headers; import io.crate.protocols.postgres.ConnectionProperties; @@ -38,33 +59,17 @@ import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.HttpVersion; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import io.crate.common.collections.Tuple; -import org.elasticsearch.common.network.InetAddresses; -import org.elasticsearch.common.settings.SecureString; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.http.netty4.Netty4HttpServerTransport; - -import javax.annotation.Nullable; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.security.cert.Certificate; -import java.util.Locale; - -import static io.crate.protocols.SSL.getSession; -import static io.netty.buffer.Unpooled.copiedBuffer; public class HttpAuthUpstreamHandler extends SimpleChannelInboundHandler<Object> { private static final Logger LOGGER = LogManager.getLogger(HttpAuthUpstreamHandler.class); @VisibleForTesting - // realm-value should not contain any special characters static final String WWW_AUTHENTICATE_REALM_MESSAGE = "Basic realm=\"CrateDB Authenticator\""; + + private static final List<String> REAL_IP_HEADER_BLACKLIST = List.of("127.0.0.1", "::1"); + private final Authentication authService; private final Settings settings; private String authorizedUser = null; @@ -185,8 +190,10 @@ static Tuple<String, SecureString> credentialsFromRequest(HttpRequest request, @ } private InetAddress addressFromRequestOrChannel(HttpRequest request, Channel channel) { - if (request.headers().contains(AuthSettings.HTTP_HEADER_REAL_IP)) { - return InetAddresses.forString(request.headers().get(AuthSettings.HTTP_HEADER_REAL_IP)); + boolean supportXRealIp = AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.get(settings); + var realIP = request.headers().get(AuthSettings.HTTP_HEADER_REAL_IP); + if (supportXRealIp && realIP != null && !REAL_IP_HEADER_BLACKLIST.contains(realIP)) { + return InetAddresses.forString(realIP); } else { return Netty4HttpServerTransport.getRemoteAddress(channel); }
server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java+1 −0 modified@@ -435,6 +435,7 @@ public void apply(Settings value, Settings current, Settings previous) { AuthSettings.AUTH_HOST_BASED_ENABLED_SETTING, AuthSettings.AUTH_HOST_BASED_CONFIG_SETTING, AuthSettings.AUTH_TRUST_HTTP_DEFAULT_HEADER, + AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP, SslSettings.SSL_TRANSPORT_MODE, SslSettings.SSL_HTTP_ENABLED, SslSettings.SSL_PSQL_ENABLED,
server/src/test/java/io/crate/auth/HttpAuthUpstreamHandlerTest.java+67 −1 modified@@ -142,7 +142,51 @@ public void testNotNoHbaConfig() throws Exception { DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); - request.headers().add("X-Real-Ip", "10.1.0.100"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized(), is(false)); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + + /** + * Ensure that the {@code X-Real-IP} header is ignored by default as this allows to by-pass HBA rules. + * See https://github.com/crate/crate/issues/15231. + */ + @Test + public void test_real_ip_header_is_ignored_by_default() { + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(Settings.EMPTY, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "10.1.0.100"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized(), is(false)); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + + @Test + public void test_real_ip_header_is_used_if_enabled() { + var settings = Settings.builder() + .put(AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.getKey(), true) + .build(); + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(settings, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "10.1.0.100"); ch.writeInbound(request); ch.releaseInbound(); @@ -153,6 +197,28 @@ public void testNotNoHbaConfig() throws Exception { "No valid auth.host_based.config entry found for host \"10.1.0.100\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); } + @Test + public void test_real_ip_header_blacklist() { + var settings = Settings.builder() + .put(AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.getKey(), true) + .build(); + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(settings, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "::1"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized(), is(false)); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + @Test public void testUnauthorizedUser() throws Exception { HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(Settings.EMPTY, authService);
5be7b3864137Disable trust of HTTP ``X-Real-IP`` header by default.
8 files changed · +139 −27
docs/admin/auth/hba.rst+5 −2 modified@@ -49,8 +49,11 @@ username, IP address, protocol and connection scheme against these entries to determine which authentication method is required. If no entry matches, the client authentication request will be denied. -For HTTP connections the ``X-REAL-IP`` request header has priority over the -actual client IP address in order to allow proxied clients to authenticate. +To support proxied clients to authenticate, the ``X-REAL-IP`` request header +can be used. For security reasons, this is disabled by default as it allows +clients to impersonate other clients. To enable this feature, +set :ref:`auth.trust.http_support_x_real_ip` to ``true``. If enabled, the +``X-REAL-IP`` request header has priority over the actual client IP address. If ``auth.host_based`` is not set, the host based authentication is disabled. In this case CrateDB **trusts all connections** and accepts the user provided by
docs/appendices/release-notes/5.4.7.rst+11 −0 modified@@ -44,6 +44,17 @@ See the :ref:`version_5.4.0` release notes for a full list of changes in the 5.4 series. +Security Fixes +============== + +- The HTTP transport will not trust any ``X-Real-IP`` header by default anymore. + This prevents a client from spoofing its IP address by setting these headers + and thus bypassing IP based authentication with is enabled by default for the + ``crate`` superuser. + To keep allowing the ``X-Real-IP`` header to be trusted, you have to + explicitly enable it via the :ref:`auth.trust.http_support_x_real_ip` node + setting. + Fixes =====
docs/config/node.rst+18 −0 modified@@ -517,6 +517,24 @@ Trust authentication to CrateDB via HTTP protocol and they do not specify a user via the ``Authorization`` request header. +.. _auth.trust.http_support_x_real_ip: + +**auth.trust.http_support_x_real_ip** + | *Default:* ``false`` + | *Runtime:* ``no`` + + If enabled, the HTTP transport will trust the ``X-Real-IP`` header sent by + the client to determine the client's IP address. This is useful when CrateDB + is running behind a reverse proxy or load-balancer. For improved security, + any ``_local_`` IP address (``127.0.0.1`` and ``::1``) defined in this header + will be ignored. + +.. warning:: + + Enabling this setting can be a security risk, as it allows clients to + impersonate other clients by sending a fake ``X-Real-IP`` header. + + Host-based authentication -------------------------
.github/styles/CrateDB/Terminology.yml+1 −0 modified@@ -19,3 +19,4 @@ swap: Github: GitHub Hotspot: HotSpot hotspot: HotSpot + proxied: proxied
server/src/main/java/io/crate/auth/AuthSettings.java+8 −3 modified@@ -21,13 +21,13 @@ package io.crate.auth; -import io.crate.types.DataTypes; -import io.netty.handler.ssl.ClientAuth; +import java.util.function.Function; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; -import java.util.function.Function; +import io.crate.types.DataTypes; +import io.netty.handler.ssl.ClientAuth; public final class AuthSettings { @@ -54,6 +54,11 @@ private AuthSettings() { ); public static final String HTTP_HEADER_REAL_IP = "X-Real-Ip"; + public static final Setting<Boolean> AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP = Setting.boolSetting( + "auth.trust.http_support_x_real_ip", + false, + Setting.Property.NodeScope + ); public static ClientAuth resolveClientAuth(Settings settings, Protocol protocol) { Settings hbaSettings = AUTH_HOST_BASED_CONFIG_SETTING.get(settings);
server/src/main/java/io/crate/auth/HttpAuthUpstreamHandler.java+28 −21 modified@@ -21,7 +21,28 @@ package io.crate.auth; +import static io.crate.protocols.SSL.getSession; +import static io.netty.buffer.Unpooled.copiedBuffer; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.security.cert.Certificate; +import java.util.List; +import java.util.Locale; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.common.network.InetAddresses; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.http.netty4.Netty4HttpServerTransport; +import org.jetbrains.annotations.Nullable; + import io.crate.common.annotations.VisibleForTesting; +import io.crate.common.collections.Tuple; import io.crate.protocols.SSL; import io.crate.protocols.http.Headers; import io.crate.protocols.postgres.ConnectionProperties; @@ -38,33 +59,17 @@ import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.HttpVersion; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import io.crate.common.collections.Tuple; -import org.elasticsearch.common.network.InetAddresses; -import org.elasticsearch.common.settings.SecureString; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.http.netty4.Netty4HttpServerTransport; - -import org.jetbrains.annotations.Nullable; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.security.cert.Certificate; -import java.util.Locale; - -import static io.crate.protocols.SSL.getSession; -import static io.netty.buffer.Unpooled.copiedBuffer; public class HttpAuthUpstreamHandler extends SimpleChannelInboundHandler<Object> { private static final Logger LOGGER = LogManager.getLogger(HttpAuthUpstreamHandler.class); @VisibleForTesting - // realm-value should not contain any special characters static final String WWW_AUTHENTICATE_REALM_MESSAGE = "Basic realm=\"CrateDB Authenticator\""; + + private static final List<String> REAL_IP_HEADER_BLACKLIST = List.of("127.0.0.1", "::1"); + private final Authentication authService; private final Settings settings; private String authorizedUser = null; @@ -185,8 +190,10 @@ static Tuple<String, SecureString> credentialsFromRequest(HttpRequest request, @ } private InetAddress addressFromRequestOrChannel(HttpRequest request, Channel channel) { - if (request.headers().contains(AuthSettings.HTTP_HEADER_REAL_IP)) { - return InetAddresses.forString(request.headers().get(AuthSettings.HTTP_HEADER_REAL_IP)); + boolean supportXRealIp = AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.get(settings); + var realIP = request.headers().get(AuthSettings.HTTP_HEADER_REAL_IP); + if (supportXRealIp && realIP != null && !REAL_IP_HEADER_BLACKLIST.contains(realIP)) { + return InetAddresses.forString(realIP); } else { return Netty4HttpServerTransport.getRemoteAddress(channel); }
server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java+1 −0 modified@@ -436,6 +436,7 @@ public void apply(Settings value, Settings current, Settings previous) { AuthSettings.AUTH_HOST_BASED_ENABLED_SETTING, AuthSettings.AUTH_HOST_BASED_CONFIG_SETTING, AuthSettings.AUTH_TRUST_HTTP_DEFAULT_HEADER, + AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP, SslSettings.SSL_TRANSPORT_MODE, SslSettings.SSL_HTTP_ENABLED, SslSettings.SSL_PSQL_ENABLED,
server/src/test/java/io/crate/auth/HttpAuthUpstreamHandlerTest.java+67 −1 modified@@ -142,7 +142,51 @@ public void testNotNoHbaConfig() throws Exception { DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); - request.headers().add("X-Real-Ip", "10.1.0.100"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized(), is(false)); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + + /** + * Ensure that the {@code X-Real-IP} header is ignored by default as this allows to by-pass HBA rules. + * See https://github.com/crate/crate/issues/15231. + */ + @Test + public void test_real_ip_header_is_ignored_by_default() { + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(Settings.EMPTY, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "10.1.0.100"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized(), is(false)); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + + @Test + public void test_real_ip_header_is_used_if_enabled() { + var settings = Settings.builder() + .put(AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.getKey(), true) + .build(); + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(settings, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "10.1.0.100"); ch.writeInbound(request); ch.releaseInbound(); @@ -153,6 +197,28 @@ public void testNotNoHbaConfig() throws Exception { "No valid auth.host_based.config entry found for host \"10.1.0.100\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); } + @Test + public void test_real_ip_header_blacklist() { + var settings = Settings.builder() + .put(AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.getKey(), true) + .build(); + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(settings, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "::1"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized(), is(false)); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + @Test public void testUnauthorizedUser() throws Exception { HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(Settings.EMPTY, authService);
da59311ca920Disable trust of HTTP ``X-Real-IP`` header by default.
9 files changed · +193 −27
docs/admin/auth/hba.rst+6 −2 modified@@ -49,8 +49,12 @@ username, IP address, protocol and connection scheme against these entries to determine which authentication method is required. If no entry matches, the client authentication request will be denied. -For HTTP connections the ``X-REAL-IP`` request header has priority over the -actual client IP address in order to allow proxied clients to authenticate. +To support proxied clients to authenticate, the ``X-REAL-IP`` request header +can be used. For security reasons, this is disabled by default as it allows +clients to impersonate other clients. To enable this feature, +set :ref:`auth.trust.http_support_x_real_ip <auth.trust.http_support_x_real_ip>` +to ``true``. If enabled, the +``X-REAL-IP`` request header has priority over the actual client IP address. If ``auth.host_based`` is not set, the host based authentication is disabled. In this case CrateDB **trusts all connections** and accepts the user provided by
docs/appendices/release-notes/5.2.11.rst+63 −0 added@@ -0,0 +1,63 @@ +.. _version_5.2.11: + +=========================== +Version 5.2.11 - Unreleased +=========================== + +.. comment 1. Remove the " - Unreleased" from the header above and adjust the == +.. comment 2. Remove the NOTE below and replace with: "Released on 20XX-XX-XX." +.. comment (without a NOTE entry, simply starting from col 1 of the line) + +.. NOTE:: + + In development. 5.2.11 isn't released yet. These are the release notes for + the upcoming release. + +.. NOTE:: + + If you are upgrading a cluster, you must be running CrateDB 4.0.2 or higher + before you upgrade to 5.2.11. + + We recommend that you upgrade to the latest 5.1 release before moving to + 5.2.11. + + A rolling upgrade from 5.1.x to 5.2.11 is supported. + Before upgrading, you should `back up your data`_. + +.. WARNING:: + + Tables that were created before CrateDB 4.x will not function with 5.x + and must be recreated before moving to 5.x.x. + + You can recreate tables using ``COPY TO`` and ``COPY FROM`` or by + `inserting the data into a new table`_. + +.. _back up your data: https://crate.io/docs/crate/reference/en/latest/admin/snapshots.html +.. _inserting the data into a new table: https://crate.io/docs/crate/reference/en/latest/admin/system-information.html#tables-need-to-be-recreated + + + +.. rubric:: Table of Contents + +.. contents:: + :local: + +See the :ref:`version_5.2.0` release notes for a full list of changes in the +5.2 series. + +Security Fixes +============== + +- The HTTP transport will not trust any ``X-Real-IP`` header by default anymore. + This prevents a client from spoofing its IP address by setting these headers + and thus bypassing IP based authentication with is enabled by default for the + ``crate`` superuser. + To keep allowing the ``X-Real-IP`` header to be trusted, you have to + explicitly enable it via the + :ref:`auth.trust.http_support_x_real_ip <auth.trust.http_support_x_real_ip>` + node setting. + +Fixes +===== + +None
docs/appendices/release-notes/index.rst+1 −0 modified@@ -31,6 +31,7 @@ Versions .. toctree:: :maxdepth: 1 + 5.2.11 5.2.10 5.2.9 5.2.8
docs/config/node.rst+18 −0 modified@@ -495,6 +495,24 @@ Trust authentication to CrateDB via HTTP protocol and they do not specify a user via the ``Authorization`` request header. +.. _auth.trust.http_support_x_real_ip: + +**auth.trust.http_support_x_real_ip** + | *Default:* ``false`` + | *Runtime:* ``no`` + + If enabled, the HTTP transport will trust the ``X-Real-IP`` header sent by + the client to determine the client's IP address. This is useful when CrateDB + is running behind a reverse proxy or load-balancer. For improved security, + any ``_local_`` IP address (``127.0.0.1`` and ``::1``) defined in this header + will be ignored. + +.. warning:: + + Enabling this setting can be a security risk, as it allows clients to + impersonate other clients by sending a fake ``X-Real-IP`` header. + + Host-based authentication -------------------------
.github/styles/Vocab/CrateDB/accept.txt+1 −0 modified@@ -145,3 +145,4 @@ upserts Upserts URIs vnet +proxied
server/src/main/java/io/crate/auth/AuthSettings.java+8 −3 modified@@ -21,13 +21,13 @@ package io.crate.auth; -import io.crate.types.DataTypes; -import io.netty.handler.ssl.ClientAuth; +import java.util.function.Function; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; -import java.util.function.Function; +import io.crate.types.DataTypes; +import io.netty.handler.ssl.ClientAuth; public final class AuthSettings { @@ -54,6 +54,11 @@ private AuthSettings() { ); public static final String HTTP_HEADER_REAL_IP = "X-Real-Ip"; + public static final Setting<Boolean> AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP = Setting.boolSetting( + "auth.trust.http_support_x_real_ip", + false, + Setting.Property.NodeScope + ); public static ClientAuth resolveClientAuth(Settings settings, Protocol protocol) { Settings hbaSettings = AUTH_HOST_BASED_CONFIG_SETTING.get(settings);
server/src/main/java/io/crate/auth/HttpAuthUpstreamHandler.java+28 −21 modified@@ -21,7 +21,28 @@ package io.crate.auth; +import static io.crate.protocols.SSL.getSession; +import static io.netty.buffer.Unpooled.copiedBuffer; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.security.cert.Certificate; +import java.util.List; +import java.util.Locale; + +import javax.annotation.Nullable; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.common.network.InetAddresses; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.http.netty4.Netty4HttpServerTransport; + import io.crate.common.annotations.VisibleForTesting; +import io.crate.common.collections.Tuple; import io.crate.protocols.SSL; import io.crate.protocols.http.Headers; import io.crate.protocols.postgres.ConnectionProperties; @@ -38,33 +59,17 @@ import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.HttpVersion; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import io.crate.common.collections.Tuple; -import org.elasticsearch.common.network.InetAddresses; -import org.elasticsearch.common.settings.SecureString; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.http.netty4.Netty4HttpServerTransport; - -import javax.annotation.Nullable; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.security.cert.Certificate; -import java.util.Locale; - -import static io.crate.protocols.SSL.getSession; -import static io.netty.buffer.Unpooled.copiedBuffer; public class HttpAuthUpstreamHandler extends SimpleChannelInboundHandler<Object> { private static final Logger LOGGER = LogManager.getLogger(HttpAuthUpstreamHandler.class); @VisibleForTesting - // realm-value should not contain any special characters static final String WWW_AUTHENTICATE_REALM_MESSAGE = "Basic realm=\"CrateDB Authenticator\""; + + private static final List<String> REAL_IP_HEADER_BLACKLIST = List.of("127.0.0.1", "::1"); + private final Authentication authService; private final Settings settings; private String authorizedUser = null; @@ -185,8 +190,10 @@ static Tuple<String, SecureString> credentialsFromRequest(HttpRequest request, @ } private InetAddress addressFromRequestOrChannel(HttpRequest request, Channel channel) { - if (request.headers().contains(AuthSettings.HTTP_HEADER_REAL_IP)) { - return InetAddresses.forString(request.headers().get(AuthSettings.HTTP_HEADER_REAL_IP)); + boolean supportXRealIp = AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.get(settings); + var realIP = request.headers().get(AuthSettings.HTTP_HEADER_REAL_IP); + if (supportXRealIp && realIP != null && !REAL_IP_HEADER_BLACKLIST.contains(realIP)) { + return InetAddresses.forString(realIP); } else { return Netty4HttpServerTransport.getRemoteAddress(channel); }
server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java+1 −0 modified@@ -436,6 +436,7 @@ public void apply(Settings value, Settings current, Settings previous) { AuthSettings.AUTH_HOST_BASED_ENABLED_SETTING, AuthSettings.AUTH_HOST_BASED_CONFIG_SETTING, AuthSettings.AUTH_TRUST_HTTP_DEFAULT_HEADER, + AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP, SslSettings.SSL_TRANSPORT_MODE, SslSettings.SSL_HTTP_ENABLED, SslSettings.SSL_PSQL_ENABLED,
server/src/test/java/io/crate/auth/HttpAuthUpstreamHandlerTest.java+67 −1 modified@@ -142,7 +142,51 @@ public void testNotNoHbaConfig() throws Exception { DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); - request.headers().add("X-Real-Ip", "10.1.0.100"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized(), is(false)); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + + /** + * Ensure that the {@code X-Real-IP} header is ignored by default as this allows to by-pass HBA rules. + * See https://github.com/crate/crate/issues/15231. + */ + @Test + public void test_real_ip_header_is_ignored_by_default() { + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(Settings.EMPTY, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "10.1.0.100"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized(), is(false)); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + + @Test + public void test_real_ip_header_is_used_if_enabled() { + var settings = Settings.builder() + .put(AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.getKey(), true) + .build(); + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(settings, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "10.1.0.100"); ch.writeInbound(request); ch.releaseInbound(); @@ -153,6 +197,28 @@ public void testNotNoHbaConfig() throws Exception { "No valid auth.host_based.config entry found for host \"10.1.0.100\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); } + @Test + public void test_real_ip_header_blacklist() { + var settings = Settings.builder() + .put(AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.getKey(), true) + .build(); + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(settings, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "::1"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized(), is(false)); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + @Test public void testUnauthorizedUser() throws Exception { HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(Settings.EMPTY, authService);
b8b4cec49a1cDisable trust of HTTP ``X-Real-IP`` header by default.
8 files changed · +139 −27
docs/admin/auth/hba.rst+5 −2 modified@@ -49,8 +49,11 @@ username, IP address, protocol and connection scheme against these entries to determine which authentication method is required. If no entry matches, the client authentication request will be denied. -For HTTP connections the ``X-REAL-IP`` request header has priority over the -actual client IP address in order to allow proxied clients to authenticate. +To support proxied clients to authenticate, the ``X-REAL-IP`` request header +can be used. For security reasons, this is disabled by default as it allows +clients to impersonate other clients. To enable this feature, +set :ref:`auth.trust.http_support_x_real_ip` to ``true``. If enabled, the +``X-REAL-IP`` request header has priority over the actual client IP address. If ``auth.host_based`` is not set, the host based authentication is disabled. In this case CrateDB **trusts all connections** and accepts the user provided by
docs/appendices/release-notes/5.5.2.rst+11 −0 modified@@ -46,6 +46,17 @@ See the :ref:`version_5.5.0` release notes for a full list of changes in the 5.5 series. +Security Fixes +============== + +- The HTTP transport will not trust any ``X-Real-IP`` header by default anymore. + This prevents a client from spoofing its IP address by setting these headers + and thus bypassing IP based authentication with is enabled by default for the + ``crate`` superuser. + To keep allowing the ``X-Real-IP`` header to be trusted, you have to + explicitly enable it via the :ref:`auth.trust.http_support_x_real_ip` node + setting. + Packaging Changes =================
docs/config/node.rst+18 −0 modified@@ -517,6 +517,24 @@ Trust authentication to CrateDB via HTTP protocol and they do not specify a user via the ``Authorization`` request header. +.. _auth.trust.http_support_x_real_ip: + +**auth.trust.http_support_x_real_ip** + | *Default:* ``false`` + | *Runtime:* ``no`` + + If enabled, the HTTP transport will trust the ``X-Real-IP`` header sent by + the client to determine the client's IP address. This is useful when CrateDB + is running behind a reverse proxy or load-balancer. For improved security, + any ``_local_`` IP address (``127.0.0.1`` and ``::1``) defined in this header + will be ignored. + +.. warning:: + + Enabling this setting can be a security risk, as it allows clients to + impersonate other clients by sending a fake ``X-Real-IP`` header. + + Host-based authentication -------------------------
.github/styles/CrateDB/Terminology.yml+1 −0 modified@@ -19,3 +19,4 @@ swap: Github: GitHub Hotspot: HotSpot hotspot: HotSpot + proxied: proxied
server/src/main/java/io/crate/auth/AuthSettings.java+8 −3 modified@@ -21,13 +21,13 @@ package io.crate.auth; -import io.crate.types.DataTypes; -import io.netty.handler.ssl.ClientAuth; +import java.util.function.Function; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; -import java.util.function.Function; +import io.crate.types.DataTypes; +import io.netty.handler.ssl.ClientAuth; public final class AuthSettings { @@ -54,6 +54,11 @@ private AuthSettings() { ); public static final String HTTP_HEADER_REAL_IP = "X-Real-Ip"; + public static final Setting<Boolean> AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP = Setting.boolSetting( + "auth.trust.http_support_x_real_ip", + false, + Setting.Property.NodeScope + ); public static ClientAuth resolveClientAuth(Settings settings, Protocol protocol) { Settings hbaSettings = AUTH_HOST_BASED_CONFIG_SETTING.get(settings);
server/src/main/java/io/crate/auth/HttpAuthUpstreamHandler.java+28 −21 modified@@ -21,7 +21,28 @@ package io.crate.auth; +import static io.crate.protocols.SSL.getSession; +import static io.netty.buffer.Unpooled.copiedBuffer; + +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.security.cert.Certificate; +import java.util.List; +import java.util.Locale; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.common.network.InetAddresses; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.http.netty4.Netty4HttpServerTransport; +import org.jetbrains.annotations.Nullable; + import io.crate.common.annotations.VisibleForTesting; +import io.crate.common.collections.Tuple; import io.crate.protocols.SSL; import io.crate.protocols.http.Headers; import io.crate.protocols.postgres.ConnectionProperties; @@ -38,33 +59,17 @@ import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.HttpVersion; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import io.crate.common.collections.Tuple; -import org.elasticsearch.common.network.InetAddresses; -import org.elasticsearch.common.settings.SecureString; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.http.netty4.Netty4HttpServerTransport; - -import org.jetbrains.annotations.Nullable; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import java.net.InetAddress; -import java.nio.charset.StandardCharsets; -import java.security.cert.Certificate; -import java.util.Locale; - -import static io.crate.protocols.SSL.getSession; -import static io.netty.buffer.Unpooled.copiedBuffer; public class HttpAuthUpstreamHandler extends SimpleChannelInboundHandler<Object> { private static final Logger LOGGER = LogManager.getLogger(HttpAuthUpstreamHandler.class); @VisibleForTesting - // realm-value should not contain any special characters static final String WWW_AUTHENTICATE_REALM_MESSAGE = "Basic realm=\"CrateDB Authenticator\""; + + private static final List<String> REAL_IP_HEADER_BLACKLIST = List.of("127.0.0.1", "::1"); + private final Authentication authService; private final Settings settings; private String authorizedUser = null; @@ -185,8 +190,10 @@ static Tuple<String, SecureString> credentialsFromRequest(HttpRequest request, @ } private InetAddress addressFromRequestOrChannel(HttpRequest request, Channel channel) { - if (request.headers().contains(AuthSettings.HTTP_HEADER_REAL_IP)) { - return InetAddresses.forString(request.headers().get(AuthSettings.HTTP_HEADER_REAL_IP)); + boolean supportXRealIp = AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.get(settings); + var realIP = request.headers().get(AuthSettings.HTTP_HEADER_REAL_IP); + if (supportXRealIp && realIP != null && !REAL_IP_HEADER_BLACKLIST.contains(realIP)) { + return InetAddresses.forString(realIP); } else { return Netty4HttpServerTransport.getRemoteAddress(channel); }
server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java+1 −0 modified@@ -435,6 +435,7 @@ public void apply(Settings value, Settings current, Settings previous) { AuthSettings.AUTH_HOST_BASED_ENABLED_SETTING, AuthSettings.AUTH_HOST_BASED_CONFIG_SETTING, AuthSettings.AUTH_TRUST_HTTP_DEFAULT_HEADER, + AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP, SslSettings.SSL_TRANSPORT_MODE, SslSettings.SSL_HTTP_ENABLED, SslSettings.SSL_PSQL_ENABLED,
server/src/test/java/io/crate/auth/HttpAuthUpstreamHandlerTest.java+67 −1 modified@@ -139,7 +139,51 @@ public void testNotNoHbaConfig() throws Exception { DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); - request.headers().add("X-Real-Ip", "10.1.0.100"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized()).isFalse(); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + + /** + * Ensure that the {@code X-Real-IP} header is ignored by default as this allows to by-pass HBA rules. + * See https://github.com/crate/crate/issues/15231. + */ + @Test + public void test_real_ip_header_is_ignored_by_default() { + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(Settings.EMPTY, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "10.1.0.100"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized()).isFalse(); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + + @Test + public void test_real_ip_header_is_used_if_enabled() { + var settings = Settings.builder() + .put(AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.getKey(), true) + .build(); + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(settings, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "10.1.0.100"); ch.writeInbound(request); ch.releaseInbound(); @@ -150,6 +194,28 @@ public void testNotNoHbaConfig() throws Exception { "No valid auth.host_based.config entry found for host \"10.1.0.100\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); } + @Test + public void test_real_ip_header_blacklist() { + var settings = Settings.builder() + .put(AuthSettings.AUTH_TRUST_HTTP_SUPPORT_X_REAL_IP.getKey(), true) + .build(); + HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(settings, authService); + EmbeddedChannel ch = new EmbeddedChannel(handler); + + DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/_sql"); + request.headers().add(HttpHeaderNames.AUTHORIZATION.toString(), "Basic QWxhZGRpbjpPcGVuU2VzYW1l"); + + request.headers().add("X-Real-IP", "::1"); + + ch.writeInbound(request); + ch.releaseInbound(); + assertThat(handler.authorized()).isFalse(); + + assertUnauthorized( + ch.readOutbound(), + "No valid auth.host_based.config entry found for host \"127.0.0.1\", user \"Aladdin\", protocol \"http\". Did you enable TLS in your client?\n"); + } + @Test public void testUnauthorizedUser() throws Exception { HttpAuthUpstreamHandler handler = new HttpAuthUpstreamHandler(Settings.EMPTY, authService);
Vulnerability mechanics
Root cause
"The application incorrectly trusted the user-controlled `X-Real-IP` HTTP header by default to determine the client's IP address for authentication, allowing for IP spoofing."
Attack vector
An attacker can bypass IP-based authentication in the Admin UI by sending a crafted HTTP request containing an `X-Real-IP` header. By setting this header to a value that satisfies the host-based authentication (HBA) rules, the attacker can impersonate a trusted client or the default superuser. This is possible because the application previously trusted this user-controlled header by default to determine the client's source IP. [patch_id=25673]
Affected code
The vulnerability exists in `server/src/main/java/io/crate/auth/HttpAuthUpstreamHandler.java`, specifically within the `addressFromRequestOrChannel` method. This method was responsible for determining the client's IP address for authentication purposes by trusting the `X-Real-IP` HTTP header by default. [patch_id=25673]
What the fix does
The patch modifies `HttpAuthUpstreamHandler.java` to stop trusting the `X-Real-IP` header by default. It introduces a new node setting, `auth.trust.http_support_x_real_ip`, which must be explicitly enabled to allow the use of this header. Additionally, if the setting is enabled, the code now explicitly blacklists local addresses (`127.0.0.1` and `::1`) to prevent potential security risks. [patch_id=25673]
Preconditions
- configThe CrateDB instance must be configured with password authentication and host-based authentication (HBA) rules that rely on IP addresses.
Generated on May 17, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-7mgx-gvjw-m3w3ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-51982ghsaADVISORY
- github.com/crate/crate/commit/0c166ef083bec4d64dd55c1d8cb9b3dec350d241ghsaWEB
- github.com/crate/crate/commit/5be7b3864137c23305ece10df3f7c311ee50ae4dghsaWEB
- github.com/crate/crate/commit/b8b4cec49a1c7eb2b5af568400bd571d194dc03eghsaWEB
- github.com/crate/crate/commit/da59311ca920743ebc58ee64c29cfe5723487f56ghsaWEB
- github.com/crate/crate/issues/15231ghsaWEB
- github.com/crate/crate/pull/15234ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/crate/PYSEC-2024-27.yamlghsaWEB
News mentions
0No linked articles in our index yet.