Skip to content

Commit

Permalink
[ASM] Stack trace leak vulnerability detection (#5067)
Browse files Browse the repository at this point in the history
* Add sample code

* Add stacktrace leak sinks and metric

* Add asp net >= 5 case

* Vulnerability implementation

* Add integration tests

* Fix error

* add snapshots

* Update test

* add snapshot

* Fix

* Add nuget integration package

* EveryMemberOfTypeNamesIsRepresented fix

* Add integration extension

* Fix unit test

* stacktrace leak sink tag

* update snapshot

* Set developer tests flag

* remove not needed file

* Fix snapshots

* Update autogenerated files

* Update controllers code

* update controllers

* Update autogenerated files

* Use 3 digits format in versions

* Add not vulnerable test case.

* Update tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/StackTraceLeak/DeveloperExceptionPageMiddlewareIntegrationBis.cs

Co-authored-by: Andrew Lock <andrew.lock@datadoghq.com>

* Fix

* nomenclature change.

---------

Co-authored-by: Andrew Lock <andrew.lock@datadoghq.com>
  • Loading branch information
NachoEchevarria and andrewlock authored Jan 24, 2024
1 parent cca9324 commit a00c0fa
Show file tree
Hide file tree
Showing 51 changed files with 1,017 additions and 292 deletions.
1 change: 1 addition & 0 deletions tracer/build/_build/Honeypot/IntegrationGroups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ static IntegrationMap()
NugetPackages.Add("OpenTelemetry", new [] { "OpenTelemetry" });
NugetPackages.Add("Microsoft.AspNetCore.Server.IIS", new[] { "Microsoft.AspNetCore.Server.IIS" });
NugetPackages.Add("Microsoft.AspNetCore.Server.Kestrel.Core", new string[] { "Microsoft.AspNetCore.Server.Kestrel.Core" });
NugetPackages.Add("Microsoft.AspNetCore.Diagnostics", new[] { "Microsoft.AspNetCore.Diagnostics" });
NugetPackages.Add("Azure.Messaging.ServiceBus", new string[] { "Azure.Messaging.ServiceBus" });
NugetPackages.Add("amqmdnetstd", new [] { "IBMMQDotnetClient" });
NugetPackages.Add("Yarp.ReverseProxy", new [] { "Yarp.ReverseProxy" });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<assembly fullname="HotChocolate.Execution" />
<assembly fullname="log4net" />
<assembly fullname="Microsoft.AspNetCore.Authentication.Abstractions" />
<assembly fullname="Microsoft.AspNetCore.Diagnostics" />
<assembly fullname="Microsoft.AspNetCore.Http" />
<assembly fullname="Microsoft.AspNetCore.Http.Abstractions">
<type fullname="Microsoft.AspNetCore.Http.ConnectionInfo" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// <copyright file="DeveloperExceptionPageMiddlewareIntegration.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

#if !NETFRAMEWORK

using System;
using System.ComponentModel;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.Configuration;
using Microsoft.AspNetCore.Http;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.StackTraceLeak;

/// <summary>
/// DeveloperExceptionPageMiddleware integration
/// </summary>
[InstrumentMethod(
AssemblyName = "Microsoft.AspNetCore.Diagnostics",
TypeName = "Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware",
ParameterTypeNames = new[] { "Microsoft.AspNetCore.Http.HttpContext", ClrNames.Exception },
MethodName = "DisplayException",
ReturnTypeName = ClrNames.Task,
MinimumVersion = "2.0.0",
MaximumVersion = "2.*.*",
IntegrationName = nameof(IntegrationId.StackTraceLeak),
InstrumentationCategory = InstrumentationCategory.Iast)]

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static class DeveloperExceptionPageMiddlewareIntegration
{
/// <summary>
/// OnMethodBegin callback
/// </summary>
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param>
/// <param name="context">The context of the error.</param>
/// <param name="exception">The exception to be shown.</param>
/// <typeparam name="TTarget">Type of the target</typeparam>
/// <returns>Calltarget state value</returns>
internal static CallTargetState OnMethodBegin<TTarget>(TTarget instance, HttpContext context, Exception exception)
{
return StackTraceLeakIntegrationCommon.OnExceptionLeak(IntegrationId.StackTraceLeak, exception);
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// <copyright file="DeveloperExceptionPageMiddlewareIntegration_Pre_3_0_0.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

#if !NETFRAMEWORK

using System;
using System.ComponentModel;
using System.Reflection;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.Configuration;
using Datadog.Trace.DuckTyping;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.StackTraceLeak;

/// <summary>
/// DeveloperExceptionPageMiddlewareImpl integration
/// </summary>
[InstrumentMethod(
AssemblyName = "Microsoft.AspNetCore.Diagnostics",
TypeName = "Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware",
ParameterTypeNames = new[] { "Microsoft.AspNetCore.Diagnostics.ErrorContext" },
MethodName = "DisplayException",
ReturnTypeName = ClrNames.Task,
MinimumVersion = "3.0.0",
MaximumVersion = "6.*.*",
IntegrationName = nameof(IntegrationId.StackTraceLeak),
InstrumentationCategory = InstrumentationCategory.Iast)]
[InstrumentMethod(
AssemblyName = "Microsoft.AspNetCore.Diagnostics",
TypeName = "Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl",
ParameterTypeNames = new[] { "Microsoft.AspNetCore.Diagnostics.ErrorContext" },
MethodName = "DisplayException",
ReturnTypeName = ClrNames.Task,
MinimumVersion = "7.0.0",
MaximumVersion = "8.*.*",
IntegrationName = nameof(IntegrationId.StackTraceLeak),
InstrumentationCategory = InstrumentationCategory.Iast)]

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static class DeveloperExceptionPageMiddlewareIntegration_Pre_3_0_0
{
internal interface IErrorContext
{
public Exception Exception { get; }
}

/// <summary>
/// OnMethodBegin callback
/// </summary>
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param>
/// <param name="errorContext">The context of the error.</param>
/// <typeparam name="TTarget">Type of the target</typeparam>
/// <typeparam name="TContext">ErrorContext type</typeparam>
/// <returns>Calltarget state value</returns>
internal static CallTargetState OnMethodBegin<TTarget, TContext>(TTarget instance, TContext errorContext)
where TContext : IErrorContext
{
// In the current implementation ErrorContext is always non-null, as is Exception
// so this should be safe
var exception = errorContext.Exception;
return StackTraceLeakIntegrationCommon.OnExceptionLeak(IntegrationId.StackTraceLeak, exception);
}
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// <copyright file="HttpResponseIntegration.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

#if NETFRAMEWORK

using System;
using System.ComponentModel;
using System.Web;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.Configuration;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.StackTraceLeak;

/// <summary>
/// HttpResponseIntegration integration
/// </summary>
[InstrumentMethod(
AssemblyName = "System.Web",
TypeName = "System.Web.HttpResponse",
ParameterTypeNames = new[] { ClrNames.Exception, ClrNames.Bool },
MethodName = "WriteErrorMessage",
ReturnTypeName = ClrNames.Void,
MinimumVersion = "4.0.0",
MaximumVersion = "4.*.*",
IntegrationName = nameof(IntegrationId.StackTraceLeak),
InstrumentationCategory = InstrumentationCategory.Iast)]

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static class HttpResponseIntegration
{
/// <summary>
/// OnMethodBegin callback
/// </summary>
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param>
/// <param name="exception">The exception to be shown.</param>
/// <param name="dontShowSensitiveErrors">The dontShowSensitiveErrors parameter of WriteErrorMessage.</param>
/// <typeparam name="TTarget">Type of the target</typeparam>
/// <returns>Calltarget state value</returns>
internal static CallTargetState OnMethodBegin<TTarget>(TTarget instance, Exception exception, bool dontShowSensitiveErrors)
{
if (HttpRuntime.UsingIntegratedPipeline && !dontShowSensitiveErrors)
{
return StackTraceLeakIntegrationCommon.OnExceptionLeak(IntegrationId.StackTraceLeak, exception);
}

return CallTargetState.GetDefault();
}
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// <copyright file="StackTraceLeakIntegrationCommon.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

using System;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.Configuration;
using Datadog.Trace.Iast;
using Datadog.Trace.Logging;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.StackTraceLeak;

#nullable enable
internal static class StackTraceLeakIntegrationCommon
{
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(StackTraceLeakIntegrationCommon));

internal static CallTargetState OnExceptionLeak(IntegrationId integrationId, Exception exception)
{
if (!Tracer.Instance.Settings.IsIntegrationEnabled(integrationId))
{
return CallTargetState.GetDefault();
}

try
{
if (exception is not null)
{
return new CallTargetState(IastModule.OnStackTraceLeak(exception, integrationId).SingleSpan);
}
}
catch (Exception ex)
{
Log.Error(ex, $"Error in {nameof(OnExceptionLeak)}.");
}

return CallTargetState.GetDefault();
}
}
1 change: 1 addition & 0 deletions tracer/src/Datadog.Trace/ClrProfiler/ClrNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ internal static class ClrNames
public const string Int32Task = "System.Threading.Tasks.Task`1[System.Int32]";

public const string Type = "System.Type";
public const string Exception = "System.Exception";

public const string Activity = "System.Diagnostics.Activity";
public const string ByteArray = "System.Byte[]";
Expand Down
1 change: 1 addition & 0 deletions tracer/src/Datadog.Trace/Configuration/IntegrationId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,6 @@ internal enum IntegrationId
TrustBoundaryViolation,
UnvalidatedRedirect,
TestPlatformAssemblyResolver,
StackTraceLeak
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal static partial class IastInstrumentedSinksExtensions
/// The number of members in the enum.
/// This is a non-distinct count of defined names.
/// </summary>
public const int Length = 18;
public const int Length = 19;

/// <summary>
/// Returns the string representation of the <see cref="Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks"/> value.
Expand Down Expand Up @@ -48,6 +48,7 @@ public static string ToStringFast(this Datadog.Trace.Telemetry.Metrics.MetricTag
Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.TrustBoundaryViolation => "vulnerability_type:trust_boundary_violation",
Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.HstsHeaderMissing => "vulnerability_type:hsts_header_missing",
Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.HeaderInjection => "vulnerability_type:header_injection",
Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.StackTraceLeak => "vulnerability_type:stacktrace_leak",
_ => value.ToString(),
};

Expand Down Expand Up @@ -79,6 +80,7 @@ public static Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks[]
Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.TrustBoundaryViolation,
Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.HstsHeaderMissing,
Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.HeaderInjection,
Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.StackTraceLeak,
};

/// <summary>
Expand Down Expand Up @@ -110,6 +112,7 @@ public static string[] GetNames()
nameof(Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.TrustBoundaryViolation),
nameof(Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.HstsHeaderMissing),
nameof(Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.HeaderInjection),
nameof(Datadog.Trace.Telemetry.Metrics.MetricTags.IastInstrumentedSinks.StackTraceLeak),
};

/// <summary>
Expand Down Expand Up @@ -141,5 +144,6 @@ public static string[] GetDescriptions()
"vulnerability_type:trust_boundary_violation",
"vulnerability_type:hsts_header_missing",
"vulnerability_type:header_injection",
"vulnerability_type:stacktrace_leak",
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal static partial class IntegrationIdExtensions
/// The number of members in the enum.
/// This is a non-distinct count of defined names.
/// </summary>
public const int Length = 60;
public const int Length = 61;

/// <summary>
/// Returns the string representation of the <see cref="Datadog.Trace.Configuration.IntegrationId"/> value.
Expand Down Expand Up @@ -90,6 +90,7 @@ public static string ToStringFast(this Datadog.Trace.Configuration.IntegrationId
Datadog.Trace.Configuration.IntegrationId.TrustBoundaryViolation => nameof(Datadog.Trace.Configuration.IntegrationId.TrustBoundaryViolation),
Datadog.Trace.Configuration.IntegrationId.UnvalidatedRedirect => nameof(Datadog.Trace.Configuration.IntegrationId.UnvalidatedRedirect),
Datadog.Trace.Configuration.IntegrationId.TestPlatformAssemblyResolver => nameof(Datadog.Trace.Configuration.IntegrationId.TestPlatformAssemblyResolver),
Datadog.Trace.Configuration.IntegrationId.StackTraceLeak => nameof(Datadog.Trace.Configuration.IntegrationId.StackTraceLeak),
_ => value.ToString(),
};

Expand Down Expand Up @@ -163,6 +164,7 @@ public static Datadog.Trace.Configuration.IntegrationId[] GetValues()
Datadog.Trace.Configuration.IntegrationId.TrustBoundaryViolation,
Datadog.Trace.Configuration.IntegrationId.UnvalidatedRedirect,
Datadog.Trace.Configuration.IntegrationId.TestPlatformAssemblyResolver,
Datadog.Trace.Configuration.IntegrationId.StackTraceLeak,
};

/// <summary>
Expand Down Expand Up @@ -236,5 +238,6 @@ public static string[] GetNames()
nameof(Datadog.Trace.Configuration.IntegrationId.TrustBoundaryViolation),
nameof(Datadog.Trace.Configuration.IntegrationId.UnvalidatedRedirect),
nameof(Datadog.Trace.Configuration.IntegrationId.TestPlatformAssemblyResolver),
nameof(Datadog.Trace.Configuration.IntegrationId.StackTraceLeak),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,9 @@ static InstrumentationDefinitions()
new (NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("StackExchange.Redis.StrongName"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("StackExchange.Redis.RedisTransaction"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("ExecuteAsync"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16StringArray("System.Threading.Tasks.Task`1<!!0>", "StackExchange.Redis.Message", "StackExchange.Redis.ResultProcessor`1[!!0]", "StackExchange.Redis.ServerEndPoint"), 4, 1, 0, 0, 2, 65535, 65535, NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String(assemblyFullName), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("Datadog.Trace.ClrProfiler.AutoInstrumentation.Redis.StackExchange.RedisExecuteAsyncIntegration"), 0, 1),
new (NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("StackExchange.Redis.StrongName"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("StackExchange.Redis.RedisTransaction"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("ExecuteAsync"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16StringArray("System.Threading.Tasks.Task`1<!!0>", "StackExchange.Redis.Message", "StackExchange.Redis.ResultProcessor`1[!!0]", "!!0", "StackExchange.Redis.ServerEndPoint"), 5, 2, 0, 0, 2, 65535, 65535, NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String(assemblyFullName), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("Datadog.Trace.ClrProfiler.AutoInstrumentation.Redis.StackExchange.RedisExecuteAsyncIntegration_2_6_48"), 0, 1),

// StackTraceLeak
new (NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("System.Web"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("System.Web.HttpResponse"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("WriteErrorMessage"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16StringArray("System.Void", "System.Exception", "System.Boolean"), 3, 4, 0, 0, 4, 65535, 65535, NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String(assemblyFullName), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("Datadog.Trace.ClrProfiler.AutoInstrumentation.StackTraceLeak.HttpResponseIntegration"), 0, 4),

// TestPlatformAssemblyResolver
new (NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("Microsoft.VisualStudio.TestPlatform.Common"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("Microsoft.VisualStudio.TestPlatform.Common.Utilities.AssemblyResolver"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String(".ctor"), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16StringArray("System.Void", "System.Collections.Generic.IEnumerable`1[System.String]"), 2, 15, 0, 0, 15, 65535, 65535, NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String(assemblyFullName), NativeCallTargetUnmanagedMemoryHelper.AllocateAndWriteUtf16String("Datadog.Trace.ClrProfiler.AutoInstrumentation.Testing.AssemblyResolverCtorIntegration"), 0, 1),

Expand Down Expand Up @@ -825,6 +828,8 @@ internal static bool IsInstrumentedAssembly(string assemblyName)
or "Datadog.Trace.ClrProfiler.AutoInstrumentation.Redis.StackExchange.RedisExecuteAsyncIntegration"
or "Datadog.Trace.ClrProfiler.AutoInstrumentation.Redis.StackExchange.RedisExecuteAsyncIntegration_2_6_48"
=> Datadog.Trace.Configuration.IntegrationId.StackExchangeRedis,
"Datadog.Trace.ClrProfiler.AutoInstrumentation.StackTraceLeak.HttpResponseIntegration"
=> Datadog.Trace.Configuration.IntegrationId.StackTraceLeak,
"Datadog.Trace.ClrProfiler.AutoInstrumentation.Testing.AssemblyResolverCtorIntegration"
=> Datadog.Trace.Configuration.IntegrationId.TestPlatformAssemblyResolver,
"Datadog.Trace.ClrProfiler.AutoInstrumentation.Wcf.AsyncMethodInvoker_InvokeBegin_Integration"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace Datadog.Trace.Telemetry;
internal partial class CiVisibilityMetricsTelemetryCollector
{
private const int CountSharedLength = 195;
private const int CountSharedLength = 198;

/// <summary>
/// Creates the buffer for the <see cref="Datadog.Trace.Telemetry.Metrics.CountShared" /> values.
Expand Down Expand Up @@ -215,6 +215,9 @@ private static AggregatedMetric[] GetCountSharedBuffer()
new(new[] { "integration_name:testplatformassemblyresolver", "error_type:duck_typing" }),
new(new[] { "integration_name:testplatformassemblyresolver", "error_type:invoker" }),
new(new[] { "integration_name:testplatformassemblyresolver", "error_type:execution" }),
new(new[] { "integration_name:stacktraceleak", "error_type:duck_typing" }),
new(new[] { "integration_name:stacktraceleak", "error_type:invoker" }),
new(new[] { "integration_name:stacktraceleak", "error_type:execution" }),
};

/// <summary>
Expand All @@ -223,7 +226,7 @@ private static AggregatedMetric[] GetCountSharedBuffer()
/// It is equal to the cardinality of the tag combinations (or 1 if there are no tags)
/// </summary>
private static int[] CountSharedEntryCounts { get; }
= new int[]{ 195, };
= new int[]{ 198, };

public void RecordCountSharedIntegrationsError(Datadog.Trace.Telemetry.Metrics.MetricTags.IntegrationName tag1, Datadog.Trace.Telemetry.Metrics.MetricTags.InstrumentationError tag2, int increment = 1)
{
Expand Down
Loading

0 comments on commit a00c0fa

Please sign in to comment.