diff --git a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs index 84fe19980189..2335b7b3e665 100644 --- a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs @@ -42,6 +42,10 @@ internal static partial class AspectDefinitions " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoDatabase::RunCommandAsync(MongoDB.Driver.Command`1,MongoDB.Driver.ReadPreference,System.Threading.CancellationToken)\",\"\",[2],[False],[None],Default,[])] AnalyzeCommand(System.Object)", " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoCollectionExtensions::Find(MongoDB.Driver.IMongoCollection`1,MongoDB.Driver.FilterDefinition`1,MongoDB.Driver.FindOptions)\",\"\",[1],[False],[None],Default,[])] AnalyzeCommand(System.Object)", " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoCollectionExtensions::FindAsync(MongoDB.Driver.IMongoCollection`1,MongoDB.Driver.FilterDefinition`1,MongoDB.Driver.FindOptions`2,System.Threading.CancellationToken)\",\"\",[2],[False],[None],Default,[])] AnalyzeCommand(System.Object)", +"[AspectClass(\"Newtonsoft.Json\",[None],Propagation,[])] Datadog.Trace.Iast.Aspects.Newtonsoft.Json.NewtonsoftJsonAspects", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JObject::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseObject(System.String)", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JArray::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseArray(System.String)", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JToken::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseToken(System.String)", "[AspectClass(\"System,System.Runtime\",[StringOptimization],Propagation,[])] Datadog.Trace.Iast.Aspects.System.UriAspect", " [AspectCtorReplace(\"System.Uri::.ctor(System.String)\",\"\",[0],[False],[StringLiteral_1],Default,[])] Init(System.String)", " [AspectCtorReplace(\"System.Uri::.ctor(System.Uri,System.String,System.Boolean)\",\"\",[0],[False],[None],Default,[])] Init(System.Uri,System.String,System.Boolean)", diff --git a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs index e71961e38c68..4efb2df48bc3 100644 --- a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs @@ -52,6 +52,10 @@ internal static partial class AspectDefinitions " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoDatabase::RunCommandAsync(MongoDB.Driver.Command`1,MongoDB.Driver.ReadPreference,System.Threading.CancellationToken)\",\"\",[2],[False],[None],Default,[])] AnalyzeCommand(System.Object)", " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoCollectionExtensions::Find(MongoDB.Driver.IMongoCollection`1,MongoDB.Driver.FilterDefinition`1,MongoDB.Driver.FindOptions)\",\"\",[1],[False],[None],Default,[])] AnalyzeCommand(System.Object)", " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoCollectionExtensions::FindAsync(MongoDB.Driver.IMongoCollection`1,MongoDB.Driver.FilterDefinition`1,MongoDB.Driver.FindOptions`2,System.Threading.CancellationToken)\",\"\",[2],[False],[None],Default,[])] AnalyzeCommand(System.Object)", +"[AspectClass(\"Newtonsoft.Json\",[None],Propagation,[])] Datadog.Trace.Iast.Aspects.Newtonsoft.Json.NewtonsoftJsonAspects", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JObject::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseObject(System.String)", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JArray::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseArray(System.String)", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JToken::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseToken(System.String)", "[AspectClass(\"System,System.Runtime\",[StringOptimization],Propagation,[])] Datadog.Trace.Iast.Aspects.System.UriAspect", " [AspectCtorReplace(\"System.Uri::.ctor(System.String)\",\"\",[0],[False],[StringLiteral_1],Default,[])] Init(System.String)", " [AspectCtorReplace(\"System.Uri::.ctor(System.Uri,System.String,System.Boolean)\",\"\",[0],[False],[None],Default,[])] Init(System.Uri,System.String,System.Boolean)", diff --git a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs index bf83f89fa60f..69d17b4f8476 100644 --- a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs @@ -52,6 +52,10 @@ internal static partial class AspectDefinitions " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoDatabase::RunCommandAsync(MongoDB.Driver.Command`1,MongoDB.Driver.ReadPreference,System.Threading.CancellationToken)\",\"\",[2],[False],[None],Default,[])] AnalyzeCommand(System.Object)", " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoCollectionExtensions::Find(MongoDB.Driver.IMongoCollection`1,MongoDB.Driver.FilterDefinition`1,MongoDB.Driver.FindOptions)\",\"\",[1],[False],[None],Default,[])] AnalyzeCommand(System.Object)", " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoCollectionExtensions::FindAsync(MongoDB.Driver.IMongoCollection`1,MongoDB.Driver.FilterDefinition`1,MongoDB.Driver.FindOptions`2,System.Threading.CancellationToken)\",\"\",[2],[False],[None],Default,[])] AnalyzeCommand(System.Object)", +"[AspectClass(\"Newtonsoft.Json\",[None],Propagation,[])] Datadog.Trace.Iast.Aspects.Newtonsoft.Json.NewtonsoftJsonAspects", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JObject::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseObject(System.String)", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JArray::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseArray(System.String)", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JToken::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseToken(System.String)", "[AspectClass(\"System,System.Runtime\",[StringOptimization],Propagation,[])] Datadog.Trace.Iast.Aspects.System.UriAspect", " [AspectCtorReplace(\"System.Uri::.ctor(System.String)\",\"\",[0],[False],[StringLiteral_1],Default,[])] Init(System.String)", " [AspectCtorReplace(\"System.Uri::.ctor(System.Uri,System.String,System.Boolean)\",\"\",[0],[False],[None],Default,[])] Init(System.Uri,System.String,System.Boolean)", diff --git a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs index 58faa4992416..33df73ee6505 100644 --- a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/AspectsDefinitionsGenerator/AspectsDefinitions.g.cs @@ -52,6 +52,10 @@ internal static partial class AspectDefinitions " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoDatabase::RunCommandAsync(MongoDB.Driver.Command`1,MongoDB.Driver.ReadPreference,System.Threading.CancellationToken)\",\"\",[2],[False],[None],Default,[])] AnalyzeCommand(System.Object)", " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoCollectionExtensions::Find(MongoDB.Driver.IMongoCollection`1,MongoDB.Driver.FilterDefinition`1,MongoDB.Driver.FindOptions)\",\"\",[1],[False],[None],Default,[])] AnalyzeCommand(System.Object)", " [AspectMethodInsertBefore(\"MongoDB.Driver.IMongoCollectionExtensions::FindAsync(MongoDB.Driver.IMongoCollection`1,MongoDB.Driver.FilterDefinition`1,MongoDB.Driver.FindOptions`2,System.Threading.CancellationToken)\",\"\",[2],[False],[None],Default,[])] AnalyzeCommand(System.Object)", +"[AspectClass(\"Newtonsoft.Json\",[None],Propagation,[])] Datadog.Trace.Iast.Aspects.Newtonsoft.Json.NewtonsoftJsonAspects", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JObject::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseObject(System.String)", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JArray::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseArray(System.String)", +" [AspectMethodReplace(\"Newtonsoft.Json.Linq.JToken::Parse(System.String)\",\"\",[0],[False],[None],Default,[])] ParseToken(System.String)", "[AspectClass(\"System,System.Runtime\",[StringOptimization],Propagation,[])] Datadog.Trace.Iast.Aspects.System.UriAspect", " [AspectCtorReplace(\"System.Uri::.ctor(System.String)\",\"\",[0],[False],[StringLiteral_1],Default,[])] Init(System.String)", " [AspectCtorReplace(\"System.Uri::.ctor(System.Uri,System.String,System.Boolean)\",\"\",[0],[False],[None],Default,[])] Init(System.Uri,System.String,System.Boolean)", diff --git a/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/ICanParse.cs b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/ICanParse.cs new file mode 100644 index 000000000000..5b9ca76094f4 --- /dev/null +++ b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/ICanParse.cs @@ -0,0 +1,13 @@ +// +// 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. +// + +#nullable enable + +namespace Datadog.Trace.Iast.Aspects.Newtonsoft.Json; + +internal interface ICanParse +{ + object Parse(string json); +} diff --git a/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/IJObject.cs b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/IJObject.cs new file mode 100644 index 000000000000..2a59fddef119 --- /dev/null +++ b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/IJObject.cs @@ -0,0 +1,15 @@ +// +// 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. +// + +#nullable enable + +using System.Collections.Generic; + +namespace Datadog.Trace.Iast.Aspects.Newtonsoft.Json; + +internal interface IJObject +{ + public IEnumerable Properties(); +} diff --git a/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/IJToken.cs b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/IJToken.cs new file mode 100644 index 000000000000..cf9963587461 --- /dev/null +++ b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/IJToken.cs @@ -0,0 +1,13 @@ +// +// 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. +// + +#nullable enable + +namespace Datadog.Trace.Iast.Aspects.Newtonsoft.Json; + +internal interface IJToken +{ + public JTokenTypeProxy Type { get; } +} diff --git a/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/IJValue.cs b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/IJValue.cs new file mode 100644 index 000000000000..5ee5ed70f4cd --- /dev/null +++ b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/IJValue.cs @@ -0,0 +1,15 @@ +// +// 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. +// + +#nullable enable + +namespace Datadog.Trace.Iast.Aspects.Newtonsoft.Json; + +internal interface IJValue +{ + object Value { get; } // as an interface because in a struct it would fail with an AmbiguousMatchException + + JTokenTypeProxy Type { get; } +} diff --git a/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/JPropertyStruct.cs b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/JPropertyStruct.cs new file mode 100644 index 000000000000..ff535c63a737 --- /dev/null +++ b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/JPropertyStruct.cs @@ -0,0 +1,16 @@ +// +// 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. +// + +#nullable enable + +using Datadog.Trace.DuckTyping; + +namespace Datadog.Trace.Iast.Aspects.Newtonsoft.Json; + +[DuckCopy] +internal struct JPropertyStruct +{ + public object Value; +} diff --git a/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/JTokenTypeProxy.cs b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/JTokenTypeProxy.cs new file mode 100644 index 000000000000..3d1975473e46 --- /dev/null +++ b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/JTokenTypeProxy.cs @@ -0,0 +1,101 @@ +// +// 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. +// + +#nullable enable + +namespace Datadog.Trace.Iast.Aspects.Newtonsoft.Json; + +internal enum JTokenTypeProxy +{ + /// + /// No token type has been set. + /// + None = 0, + + /// + /// A JSON object. + /// + Object = 1, + + /// + /// A JSON array. + /// + Array = 2, + + /// + /// A JSON constructor. + /// + Constructor = 3, + + /// + /// A JSON object property. + /// + Property = 4, + + /// + /// A comment. + /// + Comment = 5, + + /// + /// An integer value. + /// + Integer = 6, + + /// + /// A float value. + /// + Float = 7, + + /// + /// A string value. + /// + String = 8, + + /// + /// A boolean value. + /// + Boolean = 9, + + /// + /// A null value. + /// + Null = 10, + + /// + /// An undefined value. + /// + Undefined = 11, + + /// + /// A date value. + /// + Date = 12, + + /// + /// A raw JSON value. + /// + Raw = 13, + + /// + /// A collection of bytes value. + /// + Bytes = 14, + + /// + /// A Guid value. + /// + Guid = 15, + + /// + /// A Uri value. + /// + Uri = 16, + + /// + /// A TimeSpan value. + /// + TimeSpan = 17 +} diff --git a/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/NewtonsoftJsonAspects.cs b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/NewtonsoftJsonAspects.cs new file mode 100644 index 000000000000..6906d2320c98 --- /dev/null +++ b/tracer/src/Datadog.Trace/Iast/Aspects/Newtonsoft.Json/NewtonsoftJsonAspects.cs @@ -0,0 +1,219 @@ +// +// 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. +// + +using System; +using System.Collections; +using Datadog.Trace.DuckTyping; +using Datadog.Trace.Iast.Dataflow; +using Datadog.Trace.Logging; + +#nullable enable + +namespace Datadog.Trace.Iast.Aspects.Newtonsoft.Json; + +/// Newtonsoft.Json class aspects +[AspectClass("Newtonsoft.Json")] +[global::System.ComponentModel.Browsable(false)] +[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] +public class NewtonsoftJsonAspects +{ + private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(); + + private static readonly ICanParse? JObjectProxy; + private static readonly ICanParse? JArrayProxy; + private static readonly ICanParse? JTokenProxy; + + static NewtonsoftJsonAspects() + { + try + { + var jObjectType = Type.GetType("Newtonsoft.Json.Linq.JObject, Newtonsoft.Json")!; + var jArrayType = Type.GetType("Newtonsoft.Json.Linq.JArray, Newtonsoft.Json")!; + var jTokenType = Type.GetType("Newtonsoft.Json.Linq.JToken, Newtonsoft.Json")!; + + var jObjectProxyResult = DuckType.GetOrCreateProxyType(typeof(ICanParse), jObjectType); + if (jObjectProxyResult.Success) + { + JObjectProxy = (ICanParse)jObjectProxyResult.CreateInstance(null!); + } + else + { + Log.Warning("Failed to create JObject proxy"); + } + + var jArrayProxyResult = DuckType.GetOrCreateProxyType(typeof(ICanParse), jArrayType); + if (jArrayProxyResult.Success) + { + JArrayProxy = (ICanParse)jArrayProxyResult.CreateInstance(null!); + } + else + { + Log.Warning("Failed to create JArray proxy"); + } + + var jTokenProxyResult = DuckType.GetOrCreateProxyType(typeof(ICanParse), jTokenType); + if (jTokenProxyResult.Success) + { + JTokenProxy = (ICanParse)jTokenProxyResult.CreateInstance(null!); + } + else + { + Log.Warning("Failed to create JToken proxy"); + } + } + catch (Exception ex) + { + Log.Warning(ex, "Error while initializing NewtonsoftJsonAspects"); + } + } + + /// + /// JObject Parse aspect. + /// + /// The parsed Json string. + /// The parsed JObject instance created. + [AspectMethodReplace("Newtonsoft.Json.Linq.JObject::Parse(System.String)")] + public static object? ParseObject(string json) + { + var result = JObjectProxy?.Parse(json); + + try + { + var duckedResult = result.DuckCast(); + var taintedObjects = IastModule.GetIastContext()?.GetTaintedObjects(); + RecursiveJObjectStringTaint(duckedResult, taintedObjects); + } + catch (Exception ex) + { + Log.Warning(ex, "Error while tainting the JObject"); + } + + return result; + } + + /// + /// JArray Parse aspect. + /// + /// The parsed Json string. + /// The parsed JArray instance created. + [AspectMethodReplace("Newtonsoft.Json.Linq.JArray::Parse(System.String)")] + public static object? ParseArray(string json) + { + var result = JArrayProxy?.Parse(json); + + try + { + if (result is null) + { + return null; + } + + var taintedObjects = IastModule.GetIastContext()?.GetTaintedObjects(); + RecursiveJArrayStringTaint((IEnumerable)result, taintedObjects); + } + catch (Exception ex) + { + Log.Warning(ex, "Error while tainting the JArray"); + } + + return result; + } + + /// + /// JToken Parse aspect. + /// + /// The parsed Json string. + /// The parsed JToken instance created. + [AspectMethodReplace("Newtonsoft.Json.Linq.JToken::Parse(System.String)")] + public static object? ParseToken(string json) + { + var result = JTokenProxy?.Parse(json); + + try + { + var taintedObjects = IastModule.GetIastContext()?.GetTaintedObjects(); + RecursiveJTokenStringTaint(result, taintedObjects); + } + catch (Exception ex) + { + Log.Warning(ex, "Error while tainting the JToken"); + } + + return result; + } + + private static void RecursiveJTokenStringTaint(object? token, TaintedObjects? taintedObjects) + { + if (token == null || taintedObjects == null) + { + return; + } + + if (!token.TryDuckCast(out var jToken)) + { + return; + } + + switch (jToken.Type) + { + case JTokenTypeProxy.Object: + RecursiveJObjectStringTaint(token.DuckCast(), taintedObjects); + break; + + case JTokenTypeProxy.Array: + RecursiveJArrayStringTaint((IEnumerable)token, taintedObjects); + break; + + case JTokenTypeProxy.String: + RecursiveJValueStringTaint(token.DuckCast(), taintedObjects); + break; + } + } + + private static void RecursiveJArrayStringTaint(IEnumerable? array, TaintedObjects? taintedObjects) + { + if (array == null || taintedObjects == null) + { + return; + } + + foreach (var value in array) + { + RecursiveJTokenStringTaint(value, taintedObjects); + } + } + + private static void RecursiveJObjectStringTaint(IJObject? obj, TaintedObjects? taintedObjects) + { + if (obj == null || taintedObjects == null) + { + return; + } + + foreach (var value in obj.Properties()) + { + if (value == null) + { + continue; + } + + var duckedProperty = value.DuckCast(); + RecursiveJTokenStringTaint(duckedProperty.Value, taintedObjects); + } + } + + private static void RecursiveJValueStringTaint(IJValue? value, TaintedObjects? taintedObjects) + { + if (value == null || taintedObjects == null) + { + return; + } + + if (value is { Type: JTokenTypeProxy.String, Value: string str }) + { + taintedObjects.Taint(str, [new Range(0, str.Length)]); + } + } +} diff --git a/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastTests.cs b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastTests.cs index a44e7b4fde0f..c0716d10fd43 100644 --- a/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastTests.cs +++ b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastTests.cs @@ -311,6 +311,24 @@ await VerifyHelper.VerifySpans(spansFiltered, settings) .UseFileName(filename) .DisableRequireUniquePrefix(); } + + [Fact] + [Trait("RunOnWindows", "True")] + public async Task TestJsonParseTainting() + { + var filename = "Iast.NewtonsoftJsonParseTainting.AspNetCore5.IastEnabled"; + var url = "/Iast/NewtonsoftJsonParseTainting?json={\"key\": \"value\"}"; + IncludeAllHttpSpans = true; + await TryStartApp(); + var agent = Fixture.Agent; + var spans = await SendRequestsAsync(agent, [url]); + var spansFiltered = spans.Where(x => x.Type == SpanTypes.Web).ToList(); + var settings = VerifyHelper.GetSpanVerifierSettings(); + settings.AddIastScrubbing(); + await VerifyHelper.VerifySpans(spansFiltered, settings) + .UseFileName(filename) + .DisableRequireUniquePrefix(); + } } public class AspNetCore5IastTestsFullSamplingIastDisabled : AspNetCore5IastTestsFullSampling diff --git a/tracer/test/snapshots/Iast.NewtonsoftJsonParseTainting.AspNetCore5.IastEnabled.verified.txt b/tracer/test/snapshots/Iast.NewtonsoftJsonParseTainting.AspNetCore5.IastEnabled.verified.txt new file mode 100644 index 000000000000..794bb4ae1b58 --- /dev/null +++ b/tracer/test/snapshots/Iast.NewtonsoftJsonParseTainting.AspNetCore5.IastEnabled.verified.txt @@ -0,0 +1,73 @@ +[ + { + TraceId: Id_1, + SpanId: Id_2, + Name: aspnet_core.request, + Resource: GET /iast/newtonsoftjsonparsetainting, + Service: Samples.Security.AspNetCore5, + Type: web, + Tags: { + aspnet_core.endpoint: Samples.Security.AspNetCore5.Controllers.IastController.NewtonsoftJsonParseTainting (Samples.Security.AspNetCore5), + aspnet_core.route: iast/newtonsoftjsonparsetainting, + component: aspnet_core, + env: integration_tests, + http.method: GET, + http.request.headers.host: localhost:00000, + http.route: iast/newtonsoftjsonparsetainting, + http.status_code: 200, + http.url: http://localhost:00000/Iast/NewtonsoftJsonParseTainting?json=%7B%22key%22:%20%22value%22%7D, + http.useragent: Mistake Not..., + language: dotnet, + runtime-id: Guid_1, + span.kind: server, + _dd.iast.enabled: 1, + _dd.iast.json: +{ + "vulnerabilities": [ + { + "type": "COMMAND_INJECTION", + "hash": -177455026, + "location": { + "spanId": XXX, + "path": "Samples.Security.AspNetCore5.Controllers.IastController", + "method": "ExecuteCommandInternal" + }, + "evidence": { + "valueParts": [ + { + "value": "value" + } + ] + } + } + ], + "sources": [] +} + }, + Metrics: { + process_id: 0, + _dd.agent_psr: 1.0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 1.0 + } + }, + { + TraceId: Id_1, + SpanId: Id_3, + Name: aspnet_core_mvc.request, + Resource: GET /iast/newtonsoftjsonparsetainting, + Service: Samples.Security.AspNetCore5, + Type: web, + ParentId: Id_2, + Tags: { + aspnet_core.action: newtonsoftjsonparsetainting, + aspnet_core.controller: iast, + aspnet_core.route: iast/newtonsoftjsonparsetainting, + component: aspnet_core, + env: integration_tests, + language: dotnet, + span.kind: server + } + } +] \ No newline at end of file diff --git a/tracer/test/test-applications/integrations/Samples.InstrumentedTests/Samples.InstrumentedTests.csproj b/tracer/test/test-applications/integrations/Samples.InstrumentedTests/Samples.InstrumentedTests.csproj index dfc8e57f6034..8225ee2f4423 100644 --- a/tracer/test/test-applications/integrations/Samples.InstrumentedTests/Samples.InstrumentedTests.csproj +++ b/tracer/test/test-applications/integrations/Samples.InstrumentedTests/Samples.InstrumentedTests.csproj @@ -30,6 +30,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tracer/test/test-applications/integrations/Samples.InstrumentedTests/Vulnerabilities/Json/Newtonsoft.Json/ParseTests.cs b/tracer/test/test-applications/integrations/Samples.InstrumentedTests/Vulnerabilities/Json/Newtonsoft.Json/ParseTests.cs new file mode 100644 index 000000000000..21c96fef9a39 --- /dev/null +++ b/tracer/test/test-applications/integrations/Samples.InstrumentedTests/Vulnerabilities/Json/Newtonsoft.Json/ParseTests.cs @@ -0,0 +1,115 @@ +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Samples.InstrumentedTests.Iast.Vulnerabilities.Json.Newtonsoft.Json; + +public class ParseTests : InstrumentationTestsBase +{ + private readonly string _taintedJson = """{ "key": "value" }"""; + private readonly string _taintedJsonObjectWithArray = """{"key": ["value1", "value2", {"key2": "value"}]}"""; + private readonly string _taintedJsonArray = """["value1", "value2"]"""; + private readonly string _taintedJsonDifferentTypes = """{ "name": "Chris", "age": 23, "address": { "city": "New York", "country": "America" }, "friends": [ { "name": "Emily", "hobbies": [ "biking", "music", "gaming" ] }, { "name": "John", "hobbies": [ "soccer", "gaming" ] }, [ "aString", { "obj": "val" } ] ] }"""; + + public ParseTests() + { + // Add all tainted values + AddTainted(_taintedJson); + AddTainted(_taintedJsonObjectWithArray); + AddTainted(_taintedJsonArray); + AddTainted(_taintedJsonDifferentTypes); + } + + [Fact] + public void GivenASimpleJSON_WhenParsing_Value_Vulnerable() + { + var json = JObject.Parse(_taintedJson); + var keyStr = json.Value("key"); + AssertTainted(keyStr); + } + + [Fact] + public void GivenASimpleJSON_WhenParsing_TryGetValue_Vulnerable() + { + var json = JObject.Parse(_taintedJson); + var keyStr = json.TryGetValue("key", out var value) ? value.ToString() : null; + AssertTainted(keyStr); + } + + [Fact] + public void GivenASimpleJSON_WhenParsing_ArrayAccess_Vulnerable() + { + var json = JObject.Parse(_taintedJson); + var keyStr = json["key"]?.ToString(); + AssertTainted(keyStr); + } + + [Fact] + public void GivenObjectArrayJSON_WhenParsing_Vulnerable() + { + var json = JObject.Parse(_taintedJsonObjectWithArray); + var val1 = json["key"]?[0]?.ToString(); + var val2 = json["key"]?[1]?.ToString(); + + Assert.Equal("value1", val1); + AssertTainted(val1); + Assert.Equal("value2", val2); + AssertTainted(val2); + } + + [Fact] + public void GivenAllTypesJSON_WhenParsing_Vulnerable() + { + var json = JObject.Parse(_taintedJsonDifferentTypes); + var name = json["name"]?.ToString(); + var city = json["address"]?["city"]?.ToString(); + var country = json["address"]?["country"]?.ToString(); + var friend1Name = json["friends"]?[0]?["name"]?.ToString(); + var friend1Hobbies = json["friends"]?[0]?["hobbies"]?[0]?.ToString(); + var friend2Name = json["friends"]?[1]?["name"]?.ToString(); + var friend2Hobbies = json["friends"]?[1]?["hobbies"]?[0]?.ToString(); + var friend2Hobbies2 = json["friends"]?[1]?["hobbies"]?[1]?.ToString(); + var lastFriendArrayString = json["friends"]?[2]?[0]?.ToString(); + var lastFriendArrayObject = json["friends"]?[2]?[1]?["obj"]?.ToString(); + + Assert.Equal("Chris", name); + AssertTainted(name); + Assert.Equal("New York", city); + AssertTainted(city); + Assert.Equal("America", country); + AssertTainted(country); + Assert.Equal("Emily", friend1Name); + AssertTainted(friend1Name); + Assert.Equal("biking", friend1Hobbies); + AssertTainted(friend1Hobbies); + Assert.Equal("John", friend2Name); + AssertTainted(friend2Name); + Assert.Equal("soccer", friend2Hobbies); + AssertTainted(friend2Hobbies); + Assert.Equal("gaming", friend2Hobbies2); + AssertTainted(friend2Hobbies2); + Assert.Equal("aString", lastFriendArrayString); + AssertTainted(lastFriendArrayString); + Assert.Equal("val", lastFriendArrayObject); + } + + [Fact] + public void GivenASimpleJSON_WhenJArrayParsing_Vulnerable() + { + var json = JArray.Parse(_taintedJsonArray); + var val1 = json[0]?.ToString(); + var val2 = json[1]?.ToString(); + Assert.Equal("value1", val1); + AssertTainted(val1); + Assert.Equal("value2", val2); + AssertTainted(val2); + } + + [Fact] + public void GivenASimpleJSON_WhenJTokenParsing_Vulnerable() + { + var json = JToken.Parse(_taintedJson); + var keyStr = json["key"]?.ToString(); + Assert.Equal("value", keyStr); + AssertTainted(keyStr); + } +} diff --git a/tracer/test/test-applications/security/Samples.Security.AspNetCore5/Controllers/IastController.cs b/tracer/test/test-applications/security/Samples.Security.AspNetCore5/Controllers/IastController.cs index 2ecf0178f77f..b2576d68e2a1 100644 --- a/tracer/test/test-applications/security/Samples.Security.AspNetCore5/Controllers/IastController.cs +++ b/tracer/test/test-applications/security/Samples.Security.AspNetCore5/Controllers/IastController.cs @@ -23,6 +23,7 @@ using MongoDB.Bson; using MongoDB.Driver; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Samples.Security.Data; namespace Samples.Security.AspNetCore5.Controllers @@ -169,6 +170,29 @@ public IActionResult NoSqlQueryMongoDb(string price, string query) return BadRequest($"No price or query was provided"); } + + [HttpGet("NewtonsoftJsonParseTainting")] + [Route("NewtonsoftJsonParseTainting")] + public IActionResult NewtonsoftJsonParseTainting(string json) + { + try + { + if (!string.IsNullOrEmpty(json)) + { + var doc = JObject.Parse(json); + var str = doc.Value("key"); + + // Trigger a vulnerability with the tainted string + return ExecuteCommandInternal(str, ""); + } + } + catch (Exception ex) + { + return StatusCode(500, IastControllerHelper.ToFormattedString(ex)); + } + + return BadRequest($"No json was provided"); + } [HttpGet("ExecuteCommand")] [Route("ExecuteCommand")] diff --git a/tracer/test/test-applications/security/Samples.Security.AspNetCore5/Views/Home/Index.cshtml b/tracer/test/test-applications/security/Samples.Security.AspNetCore5/Views/Home/Index.cshtml index c88aef2dfd2f..e517b7696952 100644 --- a/tracer/test/test-applications/security/Samples.Security.AspNetCore5/Views/Home/Index.cshtml +++ b/tracer/test/test-applications/security/Samples.Security.AspNetCore5/Views/Home/Index.cshtml @@ -122,7 +122,9 @@ - + + +