Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ASM] Stack trace leak vulnerability detection #5067

Merged
merged 28 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5aba45c
Add sample code
NachoEchevarria Jan 10, 2024
16a25b7
Add stacktrace leak sinks and metric
NachoEchevarria Jan 11, 2024
b657997
Add asp net >= 5 case
NachoEchevarria Jan 11, 2024
0d3a32b
Vulnerability implementation
NachoEchevarria Jan 12, 2024
1eb1a9b
Add integration tests
NachoEchevarria Jan 12, 2024
655c7a0
Fix error
NachoEchevarria Jan 15, 2024
45a8308
add snapshots
NachoEchevarria Jan 15, 2024
d6a872f
Update test
NachoEchevarria Jan 16, 2024
a925eab
add snapshot
NachoEchevarria Jan 16, 2024
65c4a27
Fix
NachoEchevarria Jan 16, 2024
739663c
Add nuget integration package
NachoEchevarria Jan 16, 2024
98749f9
EveryMemberOfTypeNamesIsRepresented fix
NachoEchevarria Jan 16, 2024
211611a
Add integration extension
NachoEchevarria Jan 16, 2024
3a83dc9
Fix unit test
NachoEchevarria Jan 17, 2024
cc075c0
stacktrace leak sink tag
NachoEchevarria Jan 17, 2024
eb7881f
update snapshot
NachoEchevarria Jan 17, 2024
06ca5b3
Set developer tests flag
NachoEchevarria Jan 17, 2024
d9d20a6
remove not needed file
NachoEchevarria Jan 17, 2024
2359117
Fix snapshots
NachoEchevarria Jan 18, 2024
910f72a
Update autogenerated files
NachoEchevarria Jan 18, 2024
2bfb51b
Update controllers code
NachoEchevarria Jan 18, 2024
d72ca50
update controllers
NachoEchevarria Jan 18, 2024
e53d220
Update autogenerated files
NachoEchevarria Jan 23, 2024
14f1400
Use 3 digits format in versions
NachoEchevarria Jan 23, 2024
761f943
Add not vulnerable test case.
NachoEchevarria Jan 23, 2024
eafb06b
Update tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Stack…
NachoEchevarria Jan 23, 2024
ede7ea7
Fix
NachoEchevarria Jan 23, 2024
4f9e3b9
nomenclature change.
NachoEchevarria Jan 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.0",
MaximumVersion = "2.*.*.*",
andrewlock marked this conversation as resolved.
Show resolved Hide resolved
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,68 @@
// <copyright file="DeveloperExceptionPageMiddlewareIntegrationBis.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.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.0",
MaximumVersion = "8.*.*.*",
IntegrationName = nameof(IntegrationId.StackTraceLeak),
InstrumentationCategory = InstrumentationCategory.Iast)]

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static class DeveloperExceptionPageMiddlewareIntegrationBis
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a little strange to split this across two classes? As OnMethodBegin has a different signature in each case, I think they could go in the same class. Not that it really matters, can be left as is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I prefer to split like this, as it's clear which of the instrumentations are calling which method 🙂

What I'm not a fan of is the Bis suffix 😄 I have no idea what that means 😅 Personally I would call this one DeveloperExceptionPageMiddlewareIntegration and the earlier one DeveloperExceptionPageMiddlewareIntegration_Pre_3_0_0 to make it explicit, but it's just a nit 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that using another nomenclature would be more clear. Fixed.

{
/// <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>
/// <returns>Calltarget state value</returns>
internal static CallTargetState OnMethodBegin<TTarget>(TTarget instance, object errorContext)
{
var exception = errorContext.DuckCast<ErrorContextStruct>().Exception;
NachoEchevarria marked this conversation as resolved.
Show resolved Hide resolved
return StackTraceLeakIntegrationCommon.OnExceptionLeak(IntegrationId.StackTraceLeak, exception);
}

[DuckCopy]
internal struct ErrorContextStruct
{
[Duck(BindingFlags = DuckAttribute.DefaultFlags | BindingFlags.IgnoreCase)]
public Exception Exception;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't normally nest the duck types directly in the integrations 🤔 I think I like it 😄

}

#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();
}
Comment on lines +36 to +52
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering whether UsingIntegrationPipeline is enough here 🤔 In HttpContextHelper.AddHeaderTagsFromHttpResponse() (and other places) we have to add an additional try-catch, because we still run into issues accessing the HttpContext. My gut feeling is that we don't need to do the following, because I don't think you're trying to access the response headers anyway (inside IastModule for example).

Just dropping it here for context, just in case you think it could be an issue 🙂

Suggested change
/// <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();
}
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(HttpResponseIntegration));
private static bool _canReadHttpResponseHeaders = true;
internal static CallTargetState OnMethodBegin<TTarget>(TTarget instance, Exception exception, bool dontShowSensitiveErrors)
{
try
{
if (_canReadHttpResponseHeaders && HttpRuntime.UsingIntegratedPipeline && !dontShowSensitiveErrors)
{
return StackTraceLeakIntegrationCommon.OnExceptionLeak(IntegrationId.StackTraceLeak, exception);
}
}
catch (PlatformNotSupportedException ex)
{
// Despite the HttpRuntime.UsingIntegratedPipeline check, we can still fail to access response headers, for example when using Sitefinity: "This operation requires IIS integrated pipeline mode"
Log.Error(ex, "Unable to access response headers performing StackLeak detection. Disabling for the rest of the application lifetime.");
_canReadHttpResponseHeaders = false;
}
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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we be checking that IAST is enabled too? 🤔 Or do we check that later on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's checked later in IASTModule. Still, this piece of code should not be reached if IAST is disabled because of the instrumentation category.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this piece of code should not be reached if IAST is disabled because of the instrumentation category

Good point, I had forgotten that we don't even rejit these if IAST isn't enabled 🙂

{
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";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we not have this already 😂


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 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",
};
}
Loading
Loading