CVE-2024-32028 (C#, published 2024-04-12T23:15Z)
Description: value: OpenTelemetry dotnet is a dotnet telemetry framework. In affected versions of `OpenTelemetry.Instrumentation.Http` and `OpenTelemetry.Instrumentation.AspNetCore` the `url.full` writes attribute/tag on spans (`Activity`) when tracing is enabled for outgoing http requests and `OpenTelemetry.Instrumentation.AspNetCore` writes the `url.query` attribute/tag on spans (`Activity`) when tracing is enabled for incoming http requests. These attributes are defined by the Semantic Conventions for HTTP Spans. Up until version `1.8.1` the values written by `OpenTelemetry.Instrumentation.Http` & `OpenTelemetry.Instrumentation.AspNetCore` will pass-through the raw query string as was sent or received (respectively). This may lead to sensitive information (e.g. EUII - End User Identifiable Information, credentials, etc.) being leaked into telemetry backends (depending on the application(s) being instrumented) which could cause privacy and/or security incidents. Note: Older versions of `OpenTelemetry.Instrumentation.Http` & `OpenTelemetry.Instrumentation.AspNetCore` may use different tag names but have the same vulnerability. The `1.8.1` versions of `OpenTelemetry.Instrumentation.Http` & `OpenTelemetry.Instrumentation.AspNetCore` will now redact by default all values detected on transmitted or received query strings. Users are advised to upgrade. There are no known workarounds for this vulnerability.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/open-telemetry/opentelemetry-dotnet
Files changed:
OpenTelemetry.sln
src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreTraceInstrumentationOptions.cs
src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
src/OpenTelemetry.Instrumentation.AspNetCore/OpenTelemetry.Instrumentation.AspNetCore.csproj
src/OpenTelemetry.Instrumentation.AspNetCore/README.md
src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationTracerProviderBuilderExtensions.cs
src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs
src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs
src/OpenTelemetry.Instrumentation.Http/Implementation/HttpInstrumentationEventSource.cs
src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs
src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs
src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj
src/OpenTelemetry.Instrumentation.Http/README.md
src/Shared/RedactionHelper.cs
test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs
test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs
test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs
test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs
test/OpenTelemetry.Tests/Internal/RedactionHelperTest.cs
test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj
Commit message: [Instrumentation.Http][Instrumentation.AspNetCore] Fix `url.full` and `url.query` attribute values (#5532)



Vulnerable code:
## Unreleased
                // QueryString should be sanitized. see: https://github.com/open-telemetry/opentelemetry-dotnet/issues/4571
                activity.SetTag(SemanticConventions.AttributeUrlQuery, request.QueryString.Value);
* `url.query`
## Unreleased
using System.Runtime.CompilerServices;
            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
internal sealed class HttpInstrumentationEventSource : EventSource
    public static string GetUriTagValueFromRequestUri(Uri uri)
        if (string.IsNullOrEmpty(uri.UserInfo))
        return string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment);
            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
* `url.full`
    [InlineData("/api/values", "?query=1", null, 200, null)]
                            .AddAspNetCoreInstrumentation(options => options.RecordException = recordException)

Fixed code:
		src\Shared\RedactionHelper.cs = src\Shared\RedactionHelper.cs

        if (configuration.TryGetBoolValue(
            AspNetCoreInstrumentationEventSource.Log,
            "OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_DISABLE_URL_QUERY_REDACTION",
            out var disableUrlQueryRedaction))
        {
            this.DisableUrlQueryRedaction = disableUrlQueryRedaction;
        }

    /// <summary>
    /// Gets or sets a value indicating whether the url query value should be redacted or not.
    /// </summary>
    /// <remarks>
    /// The query parameter values are redacted with value set as Redacted.
    /// e.g. `?key1=value1` is set as `?key1=Redacted`.
    /// The redaction can be disabled by setting this property to <see langword="true" />.
    /// </remarks>
    internal bool DisableUrlQueryRedaction { get; set; }
## 1.8.1

Released 2024-Apr-12

* **Breaking Change**: Fixed tracing instrumentation so that by default any
  values detected in the query string component of requests are replaced with
  the text `Redacted` when building the `url.query` tag. For example,
  `?key1=value1&key2=value2` becomes `?key1=Redacted&key2=Redacted`. You can
  disable this redaction by setting the environment variable
  `OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_DISABLE_URL_QUERY_REDACTION` to `true`.
  ([#5532](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5532))
                if (this.options.DisableUrlQueryRedaction)
                {
                    activity.SetTag(SemanticConventions.AttributeUrlQuery, request.QueryString.Value);
                }
                else
                {
                    activity.SetTag(SemanticConventions.AttributeUrlQuery, RedactionHelper.GetRedactedQueryString(request.QueryString.Value));
                }
    <Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
* `url.query` - By default, the values in the query component are replaced with
  the text `Redacted`. For example, `?key1=value1&key2=value2` becomes
  `?key1=Redacted&key2=Redacted`. You can disable this redaction by setting the
  environment variable
  `OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_DISABLE_URL_QUERY_REDACTION` to `true`.
## 1.8.1

Released 2024-Apr-12

* **Breaking Change**: Fixed tracing instrumentation so that by default any
  values detected in the query string component of requests are replaced with
  the text `Redacted` when building the `url.full` tag. For example,
  `?key1=value1&key2=value2` becomes `?key1=Redacted&key2=Redacted`. You can
  disable this redaction by setting the environment variable
  `OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION` to `true`.
  ([#5532](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5532))

            services.RegisterOptionsFactory(configuration => new HttpClientTraceInstrumentationOptions(configuration));
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Configuration;
    /// <summary>
    /// Initializes a new instance of the <see cref="HttpClientTraceInstrumentationOptions"/> class.
    /// </summary>
    public HttpClientTraceInstrumentationOptions()
        : this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
    {
    }

    internal HttpClientTraceInstrumentationOptions(IConfiguration configuration)
    {
        Debug.Assert(configuration != null, "configuration was null");

        if (configuration.TryGetBoolValue(
           HttpInstrumentationEventSource.Log,
           "OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION",
           out var disableUrlQueryRedaction))
        {
            this.DisableUrlQueryRedaction = disableUrlQueryRedaction;
        }
    }

    /// <summary>
    /// Gets or sets a value indicating whether the url query value should be redacted or not.
    /// </summary>
    /// <remarks>
    /// The query parameter values are redacted with value set as Redacted.
    /// e.g. `?key1=value1` is set as `?key1=Redacted`.
    /// The redaction can be disabled by setting this property to <see langword="true" />.
    /// </remarks>
    internal bool DisableUrlQueryRedaction { get; set; }

            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri, this.options.DisableUrlQueryRedaction));
using Microsoft.Extensions.Configuration;
internal sealed class HttpInstrumentationEventSource : EventSource, IConfigurationExtensionsLogger

    [Event(8, Message = "Configuration key '{0}' has an invalid value: '{1}'", Level = EventLevel.Warning)]
    public void InvalidConfigurationValue(string key, string value)
    {
        this.WriteEvent(8, key, value);
    }

    void IConfigurationExtensionsLogger.LogInvalidConfigurationValue(string key, string value)
    {
        this.InvalidConfigurationValue(key, value);
    }
using OpenTelemetry.Internal;

    /// <param name="disableQueryRedaction">Indicates whether query parameter should be redacted or not.</param>
    public static string GetUriTagValueFromRequestUri(Uri uri, bool disableQueryRedaction)
        if (string.IsNullOrEmpty(uri.UserInfo) && disableQueryRedaction)
        var query = disableQueryRedaction ? uri.Query : RedactionHelper.GetRedactedQueryString(uri.Query);

        return string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.AbsolutePath, query, uri.Fragment);
            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri, tracingOptions.DisableUrlQueryRedaction));
    <Compile Include="$(RepoRoot)\src\Shared\Configuration\*.cs" Link="Includes\Configuration\%(Filename).cs" />
    <Compile Include="$(RepoRoot)\src\Shared\EnvironmentVariables\*.cs" Link="Includes\EnvironmentVariables\%(Filename).cs" />
    <Compile Include="$(RepoRoot)\src\Shared\Options\*.cs" Link="Includes\Options\%(Filename).cs" />
    <Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
    <PackageReference Include="Microsoft.Extensions.Configuration" />
* `url.full` - By default, the values in the query component of the url are
  replaced with the text `Redacted`. For example, `?key1=value1&key2=value2`
  becomes `?key1=Redacted&key2=Redacted`. You can disable this redaction by
  setting the environment variable
  `OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION` to `true`.
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#nullable enable

using System.Text;

namespace OpenTelemetry.Internal;

internal class RedactionHelper
{
    private static readonly string RedactedText = "Redacted";

    public static string? GetRedactedQueryString(string query)
    {
        int length = query.Length;
        int index = 0;

        // Preallocate some size to avoid re-sizing multiple times.
        // Since the size will increase, allocating twice as much.
        // TODO: Check to see if we can borrow from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Text/ValueStringBuilder.cs
        // to improve perf.
        StringBuilder queryBuilder = new(2 * length);
        while (index < query.Length)
        {
            // Check if the character is = for redacting value.
            if (query[index] == '=')
            {
                // Append =
                queryBuilder.Append('=');
                index++;

                // Append redactedText in place of original value.
                queryBuilder.Append(RedactedText);

                // Move until end of this key/value pair.
                while (index < length && query[index] != '&')
                {
                    index++;
                }

                // End of key/value.
                if (index < length && query[index] == '&')
                {
                    queryBuilder.Append(query[index]);
                }
            }
            else
            {
                // Keep adding to the result
                queryBuilder.Append(query[index]);
            }

            index++;
        }

        return queryBuilder.ToString();
    }
}
using Microsoft.Extensions.Configuration;
    [Theory]
    [InlineData("?a", "?a", false)]
    [InlineData("?a=bdjdjh", "?a=Redacted", false)]
    [InlineData("?a=b&", "?a=Redacted&", false)]
    [InlineData("?c=b&", "?c=Redacted&", false)]
    [InlineData("?c=a", "?c=Redacted", false)]
    [InlineData("?a=b&c", "?a=Redacted&c", false)]
    [InlineData("?a=b&c=1123456&", "?a=Redacted&c=Redacted&", false)]
    [InlineData("?a=b&c=1&a1", "?a=Redacted&c=Redacted&a1", false)]
    [InlineData("?a=ghgjgj&c=1deedd&a1=", "?a=Redacted&c=Redacted&a1=Redacted", false)]
    [InlineData("?a=b&c=11&a1=&", "?a=Redacted&c=Redacted&a1=Redacted&", false)]
    [InlineData("?c&c&c&", "?c&c&c&", false)]
    [InlineData("?a&a&a&a", "?a&a&a&a", false)]
    [InlineData("?&&&&&&&", "?&&&&&&&", false)]
    [InlineData("?c", "?c", false)]
    [InlineData("?a", "?a", true)]
    [InlineData("?a=bdfdfdf", "?a=bdfdfdf", true)]
    [InlineData("?a=b&", "?a=b&", true)]
    [InlineData("?c=b&", "?c=b&", true)]
    [InlineData("?c=a", "?c=a", true)]
    [InlineData("?a=b&c", "?a=b&c", true)]
    [InlineData("?a=b&c=111111&", "?a=b&c=111111&", true)]
    [InlineData("?a=b&c=1&a1", "?a=b&c=1&a1", true)]
    [InlineData("?a=b&c=1&a1=", "?a=b&c=1&a1=", true)]
    [InlineData("?a=b123&c=11&a1=&", "?a=b123&c=11&a1=&", true)]
    [InlineData("?c&c&c&", "?c&c&c&", true)]
    [InlineData("?a&a&a&a", "?a&a&a&a", true)]
    [InlineData("?&&&&&&&", "?&&&&&&&", true)]
    [InlineData("?c", "?c", true)]
    [InlineData("?c=%26&", "?c=Redacted&", false)]
    public async Task ValidateUrlQueryRedaction(string urlQuery, string expectedUrlQuery, bool disableQueryRedaction)
    {
        var exportedItems = new List<Activity>();

        var configuration = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string> { ["OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_DISABLE_URL_QUERY_REDACTION"] = disableQueryRedaction.ToString() })
            .Build();

        var path = "/api/values" + urlQuery;

        // Arrange
        using var traceprovider = Sdk.CreateTracerProviderBuilder()
            .ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
            .AddAspNetCoreInstrumentation()
            .AddInMemoryExporter(exportedItems)
            .Build();

        using (var client = this.factory
            .WithWebHostBuilder(builder =>
            {
                builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
            })
            .CreateClient())
        {
            try
            {
                using var response = await client.GetAsync(path);
            }
            catch (Exception)
            {
                // ignore errors
            }

            WaitForActivityExport(exportedItems, 1);
        }

        Assert.Single(exportedItems);
        var activity = exportedItems[0];

        Assert.Equal(expectedUrlQuery, activity.GetTagValue(SemanticConventions.AttributeUrlQuery));
    }

    [InlineData("/api/values", null, null, 200, null)]
                            .AddAspNetCoreInstrumentation(options =>
                            {
                                options.RecordException = recordException;
                            })
using Microsoft.Extensions.Configuration;


    [Theory]
    [InlineData("?a", "?a", false)]
    [InlineData("?a=bdjdjh", "?a=Redacted", false)]
    [InlineData("?a=b&", "?a=Redacted&", false)]
    [InlineData("?c=b&", "?c=Redacted&", false)]
    [InlineData("?c=a", "?c=Redacted", false)]
    [InlineData("?a=b&c", "?a=Redacted&c", false)]
    [InlineData("?a=b&c=1123456&", "?a=Redacted&c=Redacted&", false)]
    [InlineData("?a=b&c=1&a1", "?a=Redacted&c=Redacted&a1", false)]
    [InlineData("?a=ghgjgj&c=1deedd&a1=", "?a=Redacted&c=Redacted&a1=Redacted", false)]
    [InlineData("?a=b&c=11&a1=&", "?a=Redacted&c=Redacted&a1=Redacted&", false)]
    [InlineData("?c&c&c&", "?c&c&c&", false)]
    [InlineData("?a&a&a&a", "?a&a&a&a", false)]
    [InlineData("?&&&&&&&", "?&&&&&&&", false)]
    [InlineData("?c", "?c", false)]
    [InlineData("?a", "?a", true)]
    [InlineData("?a=bdfdfdf", "?a=bdfdfdf", true)]
    [InlineData("?a=b&", "?a=b&", true)]
    [InlineData("?c=b&", "?c=b&", true)]
    [InlineData("?c=a", "?c=a", true)]
    [InlineData("?a=b&c", "?a=b&c", true)]
    [InlineData("?a=b&c=111111&", "?a=b&c=111111&", true)]
    [InlineData("?a=b&c=1&a1", "?a=b&c=1&a1", true)]
    [InlineData("?a=b&c=1&a1=", "?a=b&c=1&a1=", true)]
    [InlineData("?a=b123&c=11&a1=&", "?a=b123&c=11&a1=&", true)]
    [InlineData("?c&c&c&", "?c&c&c&", true)]
    [InlineData("?a&a&a&a", "?a&a&a&a", true)]
    [InlineData("?&&&&&&&", "?&&&&&&&", true)]
    [InlineData("?c", "?c", true)]
    [InlineData("?c=%26&", "?c=Redacted&", false)]
    public async Task ValidateUrlQueryRedaction(string urlQuery, string expectedUrlQuery, bool disableQueryRedaction)
    {
        var exportedItems = new List<Activity>();

        var configuration = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string> { ["OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION"] = disableQueryRedaction.ToString() })
            .Build();

        // Arrange
        using var traceprovider = Sdk.CreateTracerProviderBuilder()
            .ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
            .AddHttpClientInstrumentation()
            .AddInMemoryExporter(exportedItems)
            .Build();

        using var c = new HttpClient();
        try
        {
            await c.GetStringAsync($"{this.url}path{urlQuery}");
        }
        catch
        {
        }

        Assert.Single(exportedItems);
        var activity = exportedItems[0];

        var expectedUrl = $"{this.url}path{expectedUrlQuery}";
        Assert.Equal(expectedUrl, activity.GetTagValue(SemanticConventions.AttributeUrlFull));
    }


            DisableUrlQueryRedaction = true,
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using OpenTelemetry.Internal;
using Xunit;

namespace OpenTelemetry.Tests.Internal;

public class RedactionHelperTest
{
    [Theory]
    [InlineData("?a", "?a")]
    [InlineData("?a=b", "?a=Redacted")]
    [InlineData("?a=b&", "?a=Redacted&")]
    [InlineData("?c=b&", "?c=Redacted&")]
    [InlineData("?c=a", "?c=Redacted")]
    [InlineData("?a=b&c", "?a=Redacted&c")]
    [InlineData("?a=b&c=1&", "?a=Redacted&c=Redacted&")]
    [InlineData("?a=b&c=1&a1", "?a=Redacted&c=Redacted&a1")]
    [InlineData("?a=b&c=1&a1=", "?a=Redacted&c=Redacted&a1=Redacted")]
    [InlineData("?a=b&c=11&a1=&", "?a=Redacted&c=Redacted&a1=Redacted&")]
    [InlineData("?c&c&c&", "?c&c&c&")]
    [InlineData("?a&a&a&a", "?a&a&a&a")]
    [InlineData("?&&&&&&&", "?&&&&&&&")]
    [InlineData("?c", "?c")]
    [InlineData("?=c", "?=Redacted")]
    [InlineData("?=c&=", "?=Redacted&=Redacted")]
    public void QueryStringIsRedacted(string input, string expected)
    {
        Assert.Equal(expected, RedactionHelper.GetRedactedQueryString(input));
    }
}
    <Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />