diff --git a/tracer/test/Datadog.Trace.Security.IntegrationTests/AspNetWebForms.cs b/tracer/test/Datadog.Trace.Security.IntegrationTests/AspNetWebForms.cs index f84792a39b53..ea677ecc03f4 100644 --- a/tracer/test/Datadog.Trace.Security.IntegrationTests/AspNetWebForms.cs +++ b/tracer/test/Datadog.Trace.Security.IntegrationTests/AspNetWebForms.cs @@ -4,8 +4,6 @@ // #if NETFRAMEWORK -using System.Net; -using System.Security.Policy; using System.Threading.Tasks; using Datadog.Trace.TestHelpers; using Xunit; diff --git a/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastTests.cs b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastTests.cs index e185ea999d85..559b246e02ee 100644 --- a/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastTests.cs +++ b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetCore5IastTests.cs @@ -348,6 +348,26 @@ await VerifyHelper.VerifySpans(spansFiltered, settings) newFixture.Dispose(); newFixture.SetOutput(null); } + + [Fact] + [Trait("Category", "ArmUnsupported")] + [Trait("RunOnWindows", "True")] + public async Task TestQueryParameterNameVulnerability() + { + var filename = "Iast.QueryParameterName.AspNetCore5"; + var url = "/Iast/Print?Encrypt=True&ClientDatabase=774E4D65564946426A53694E48756B592B444A6C43673D3D&p=413&ID=2376&EntityType=114&Print=True&OutputType=WORDOPENXML&SSRSReportID=1"; + 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(); + } } // Classes to test particular features diff --git a/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetMvc5IastTests.cs b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetMvc5IastTests.cs index 9c3f14b54088..7a1e8e50fb64 100644 --- a/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetMvc5IastTests.cs +++ b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetMvc5IastTests.cs @@ -99,6 +99,16 @@ public async Task TestStackTraceLeak(string test, string url) { await TestStrictTransportSecurityHeaderMissingVulnerability(test, url); } + + [Trait("Category", "EndToEnd")] + [Trait("RunOnWindows", "True")] + [Trait("LoadFromGAC", "True")] + [SkippableTheory] + [InlineData(AddressesConstants.RequestQuery, "/Iast/Print?Encrypt=True&ClientDatabase=774E4D65564946426A53694E48756B592B444A6C43673D3D&p=413&ID=2376&EntityType=114&Print=True&OutputType=WORDOPENXML&SSRSReportID=1")] + public async Task TestQueryParameterNameVulnerability(string test, string url) + { + await TestQueryParameterName(test, url); + } } [Collection("IisTests")] @@ -650,6 +660,19 @@ await VerifyHelper.VerifySpans(spansFiltered, settings) .DisableRequireUniquePrefix(); } + protected async Task TestQueryParameterName(string test, string url) + { + var sanitisedUrl = VerifyHelper.SanitisePathsForVerify(url); + var settings = VerifyHelper.GetSpanVerifierSettings(test, sanitisedUrl, null); + var spans = await SendRequestsAsync(_iisFixture.Agent, [url]); + var filename = GetFileName("QueryParameterName"); + var spansFiltered = spans.Where(x => x.Type == SpanTypes.Web).ToList(); + settings.AddIastScrubbing(); + await VerifyHelper.VerifySpans(spansFiltered, settings) + .UseFileName(filename) + .DisableRequireUniquePrefix(); + } + protected override string GetTestName() => _testName; private string GetFileName(string testName) diff --git a/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetWebFormsWithIast.cs b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetWebFormsWithIast.cs new file mode 100644 index 000000000000..712b88777f3b --- /dev/null +++ b/tracer/test/Datadog.Trace.Security.IntegrationTests/IAST/AspNetWebFormsWithIast.cs @@ -0,0 +1,84 @@ +// +// 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. +// + +#if NETFRAMEWORK +using System.Threading.Tasks; +using Datadog.Trace.Iast.Telemetry; +using Datadog.Trace.TestHelpers; +using Xunit; +using Xunit.Abstractions; + +#pragma warning disable SA1402 // File may only contain a single class +#pragma warning disable SA1649 // File name must match first type name +namespace Datadog.Trace.Security.IntegrationTests.IAST; + +[Collection("IisTests")] +public class AspNetWebFormsIntegratedWithIast : AspNetWebFormsWithIast +{ + public AspNetWebFormsIntegratedWithIast(IisFixture iisFixture, ITestOutputHelper output) + : base(iisFixture, output, classicMode: false, enableSecurity: true) + { + } +} + +[Collection("IisTests")] +public class AspNetWebFormsClassicIntegratedWithIast : AspNetWebFormsWithIast +{ + public AspNetWebFormsClassicIntegratedWithIast(IisFixture iisFixture, ITestOutputHelper output) + : base(iisFixture, output, classicMode: true, enableSecurity: true) + { + } +} + +public abstract class AspNetWebFormsWithIast : AspNetBase, IClassFixture, IAsyncLifetime +{ + private readonly IisFixture _iisFixture; + private readonly bool _classicMode; + + public AspNetWebFormsWithIast(IisFixture iisFixture, ITestOutputHelper output, bool classicMode, bool enableSecurity) + : base("WebForms", output, "/home/shutdown", @"test\test-applications\security\aspnet") + { + EnableIast(true); + EnableEvidenceRedaction(false); + EnableIastTelemetry((int)IastMetricsVerbosityLevel.Off); + SetEnvironmentVariable("DD_IAST_DEDUPLICATION_ENABLED", "false"); + SetEnvironmentVariable("DD_IAST_REQUEST_SAMPLING", "100"); + SetEnvironmentVariable("DD_IAST_MAX_CONCURRENT_REQUESTS", "100"); + SetEnvironmentVariable("DD_IAST_VULNERABILITIES_PER_REQUEST", "100"); + SetEnvironmentVariable(Configuration.ConfigurationKeys.AppSec.StackTraceEnabled, "false"); + + _iisFixture = iisFixture; + _classicMode = classicMode; + _testName = "Security." + nameof(AspNetWebForms) + + (classicMode ? ".Classic" : ".Integrated") + + ".enableSecurity=" + enableSecurity; + } + + [Trait("Category", "EndToEnd")] + [Trait("RunOnWindows", "True")] + [Trait("LoadFromGAC", "True")] + [SkippableTheory] + [InlineData("TestQueryParameterNameVulnerability")] + public async Task TestQueryParameterNameVulnerability(string test) + { + var url = "/print?Encrypt=True&ClientDatabase=774E4D65564946426A53694E48756B592B444A6C43673D3D&p=413&ID=2376&EntityType=114&Print=True&OutputType=WORDOPENXML&SSRSReportID=1"; + + var settings = VerifyHelper.GetSpanVerifierSettings(test); + settings.AddIastScrubbing(); + + await TestAppSecRequestWithVerifyAsync(_iisFixture.Agent, url, null, 1, 1, settings, userAgent: "Hello/V"); + } + + public async Task InitializeAsync() + { + await _iisFixture.TryStartIis(this, _classicMode ? IisAppType.AspNetClassic : IisAppType.AspNetIntegrated); + SetHttpPort(_iisFixture.HttpPort); + } + + public Task DisposeAsync() => Task.CompletedTask; + + protected override string GetTestName() => _testName; +} +#endif diff --git a/tracer/test/snapshots/Iast.QueryParameterName.AspNetCore5.verified.txt b/tracer/test/snapshots/Iast.QueryParameterName.AspNetCore5.verified.txt new file mode 100644 index 000000000000..e3fbc2e5f326 --- /dev/null +++ b/tracer/test/snapshots/Iast.QueryParameterName.AspNetCore5.verified.txt @@ -0,0 +1,82 @@ +[ + { + TraceId: Id_1, + SpanId: Id_2, + Name: aspnet_core.request, + Resource: GET /iast/print, + Service: Samples.Security.AspNetCore5, + Type: web, + Tags: { + aspnet_core.endpoint: Samples.Security.AspNetCore5.Controllers.IastController.PrintReport (Samples.Security.AspNetCore5), + aspnet_core.route: iast/print, + component: aspnet_core, + env: integration_tests, + http.method: GET, + http.request.headers.host: localhost:00000, + http.route: iast/print, + http.status_code: 200, + http.url: http://localhost:00000/Iast/Print?Encrypt=True&ClientDatabase=774E4D65564946426A53694E48756B592B444A6C43673D3D&p=413&ID=2376&EntityType=114&Print=True&OutputType=WORDOPENXML&SSRSReportID=1, + http.useragent: Mistake Not..., + language: dotnet, + runtime-id: Guid_1, + span.kind: server, + _dd.iast.enabled: 1, + _dd.iast.json: +{ + "vulnerabilities": [ + { + "type": "SQL_INJECTION", + "hash": -209503571, + "location": { + "spanId": XXX, + "path": "Samples.Security.AspNetCore5.Controllers.IastController", + "method": "ExecuteQuery" + }, + "evidence": { + "valueParts": [ + { + "value": "\r\nDECLARE @ClientDatabaseID INT = (SELECT ClientDatabaseID FROM[GetClientDatabase]('", + "source": 0 + }, + { + "value": "'))\r\n\r\nSELECT SSRSReports FROM [ClientCentral].[dbo].[ClientDatabases] WHERE ClientDatabaseID = @ClientDatabaseID)", + "source": 0 + } + ] + } + } + ], + "sources": [ + { + "origin": "http.request.parameter.name", + "name": "ClientDatabase" + } + ] +} + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 2.0 + } + }, + { + TraceId: Id_1, + SpanId: Id_3, + Name: aspnet_core_mvc.request, + Resource: GET /iast/print, + Service: Samples.Security.AspNetCore5, + Type: web, + ParentId: Id_2, + Tags: { + aspnet_core.action: printreport, + aspnet_core.controller: iast, + aspnet_core.route: iast/print, + component: aspnet_core, + env: integration_tests, + language: dotnet, + span.kind: server + } + } +] \ No newline at end of file diff --git a/tracer/test/snapshots/Iast.QueryParameterName.AspNetMvc5.IastEnabled.verified.txt b/tracer/test/snapshots/Iast.QueryParameterName.AspNetMvc5.IastEnabled.verified.txt new file mode 100644 index 000000000000..81b6ac4dcad1 --- /dev/null +++ b/tracer/test/snapshots/Iast.QueryParameterName.AspNetMvc5.IastEnabled.verified.txt @@ -0,0 +1,83 @@ +[ + { + TraceId: Id_1, + SpanId: Id_2, + Name: aspnet.request, + Resource: GET /iast/print, + Service: sample, + Type: web, + Tags: { + env: integration_tests, + http.method: GET, + http.request.headers.host: localhost:00000, + http.route: {controller}/{action}/{id}, + http.status_code: 200, + http.url: http://localhost:00000/Iast/Print?Encrypt=True&ClientDatabase=774E4D65564946426A53694E48756B592B444A6C43673D3D&p=413&ID=2376&EntityType=114&Print=True&OutputType=WORDOPENXML&SSRSReportID=1, + http.useragent: Mistake Not..., + language: dotnet, + runtime-id: Guid_1, + span.kind: server, + _dd.iast.enabled: 1, + _dd.iast.json: +{ + "vulnerabilities": [ + { + "type": "SQL_INJECTION", + "hash": -209503571, + "location": { + "spanId": XXX, + "path": "Samples.Security.AspNetCore5.Controllers.IastController", + "method": "ExecuteQuery" + }, + "evidence": { + "valueParts": [ + { + "value": "\r\nDECLARE @ClientDatabaseID INT = (SELECT ClientDatabaseID FROM[GetClientDatabase]('", + "source": 0 + }, + { + "value": "'))\r\n\r\nSELECT SSRSReports FROM [ClientCentral].[dbo].[ClientDatabases] WHERE ClientDatabaseID = @ClientDatabaseID)", + "source": 0 + } + ] + } + } + ], + "sources": [ + { + "origin": "http.request.parameter.name", + "name": "ClientDatabase" + } + ] +} + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 2.0 + } + }, + { + TraceId: Id_1, + SpanId: Id_3, + Name: aspnet-mvc.request, + Resource: GET /iast/print, + Service: sample, + Type: web, + ParentId: Id_2, + Tags: { + aspnet.action: print, + aspnet.controller: iast, + aspnet.route: {controller}/{action}/{id}, + env: integration_tests, + http.method: GET, + http.request.headers.host: localhost:00000, + http.status_code: 200, + http.url: http://localhost:00000/Iast/Print?Encrypt=True&ClientDatabase=774E4D65564946426A53694E48756B592B444A6C43673D3D&p=413&ID=2376&EntityType=114&Print=True&OutputType=WORDOPENXML&SSRSReportID=1, + http.useragent: Mistake Not..., + language: dotnet, + span.kind: server + } + } +] \ No newline at end of file diff --git a/tracer/test/snapshots/Security.AspNetWebForms.Classic.enableSecurity=True.__test=TestQueryParameterNameVulnerability.verified.txt b/tracer/test/snapshots/Security.AspNetWebForms.Classic.enableSecurity=True.__test=TestQueryParameterNameVulnerability.verified.txt new file mode 100644 index 000000000000..87a028652042 --- /dev/null +++ b/tracer/test/snapshots/Security.AspNetWebForms.Classic.enableSecurity=True.__test=TestQueryParameterNameVulnerability.verified.txt @@ -0,0 +1,57 @@ +[ + { + TraceId: Id_1, + SpanId: Id_2, + Name: aspnet.request, + Resource: GET /print, + Service: sample, + Type: web, + Tags: { + env: integration_tests, + http.method: GET, + http.request.headers.host: localhost:00000, + http.status_code: 200, + http.url: http://localhost:00000/print?Encrypt=True&ClientDatabase=774E4D65564946426A53694E48756B592B444A6C43673D3D&p=413&ID=2376&EntityType=114&Print=True&OutputType=WORDOPENXML&SSRSReportID=1, + http.useragent: Mistake Not... Hello/V, + language: dotnet, + runtime-id: Guid_1, + span.kind: server, + _dd.iast.enabled: 1, + _dd.iast.json: +{ + "vulnerabilities": [ + { + "type": "PATH_TRAVERSAL", + "hash": -1368908679, + "location": { + "spanId": XXX, + "path": "Iast_Print", + "method": "Page_Load", + "line": XXX + }, + "evidence": { + "valueParts": [ + { + "value": "ClientDatabase", + "source": 0 + } + ] + } + } + ], + "sources": [ + { + "origin": "http.request.parameter.name", + "name": "ClientDatabase" + } + ] +} + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 2.0 + } + } +] \ No newline at end of file diff --git a/tracer/test/snapshots/Security.AspNetWebForms.Integrated.enableSecurity=True.__test=TestQueryParameterNameVulnerability.verified.txt b/tracer/test/snapshots/Security.AspNetWebForms.Integrated.enableSecurity=True.__test=TestQueryParameterNameVulnerability.verified.txt new file mode 100644 index 000000000000..792391baea01 --- /dev/null +++ b/tracer/test/snapshots/Security.AspNetWebForms.Integrated.enableSecurity=True.__test=TestQueryParameterNameVulnerability.verified.txt @@ -0,0 +1,61 @@ +[ + { + TraceId: Id_1, + SpanId: Id_2, + Name: aspnet.request, + Resource: GET /print, + Service: sample, + Type: web, + Tags: { + env: integration_tests, + http.method: GET, + http.request.headers.host: localhost:00000, + http.status_code: 200, + http.url: http://localhost:00000/print?Encrypt=True&ClientDatabase=774E4D65564946426A53694E48756B592B444A6C43673D3D&p=413&ID=2376&EntityType=114&Print=True&OutputType=WORDOPENXML&SSRSReportID=1, + http.useragent: Mistake Not... Hello/V, + language: dotnet, + runtime-id: Guid_1, + span.kind: server, + _dd.iast.enabled: 1, + _dd.iast.json: +{ + "vulnerabilities": [ + { + "type": "PATH_TRAVERSAL", + "hash": -1368908679, + "location": { + "spanId": XXX, + "path": "Iast_Print", + "method": "Page_Load", + "line": XXX + }, + "evidence": { + "valueParts": [ + { + "value": "ClientDatabase", + "source": 0 + } + ] + } + }, + { + "type": "XCONTENTTYPE_HEADER_MISSING", + "hash": -721858374 + } + ], + "sources": [ + { + "origin": "http.request.parameter.name", + "name": "ClientDatabase" + } + ] +} + }, + Metrics: { + process_id: 0, + _dd.top_level: 1.0, + _dd.tracer_kr: 1.0, + _sampling_priority_v1: 2.0 + } + } +] \ No newline at end of file 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 2f5ef09474c6..eda2dcbde6d8 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 @@ -290,7 +290,7 @@ private IActionResult ExecuteCommandInternal(string file, string argumentLine, b { result = Process.Start(file, argumentLine); } - + return Content($"Process launched: " + result.ProcessName); } else @@ -1056,7 +1056,7 @@ public IActionResult TestJsonTagSizeExceeded(string tainted) { ExecuteCommandInternal(i.ToString() + "-" + tainted, i.ToString() + "-" + tainted); } - + return Content("TestJsonTagSizeExceeded"); } @@ -1152,5 +1152,32 @@ public ActionResult DatabaseSourceInjection(string host, bool injectOnlyDatabase return Content(result, "text/html"); } + + [HttpGet("Print")] + public ActionResult PrintReport( + [FromQuery] bool Encrypt, + [FromQuery] string ClientDatabase, + [FromQuery] int p, + [FromQuery] int ID, + [FromQuery] int EntityType, + [FromQuery] bool Print, + [FromQuery] string OutputType, + [FromQuery] int SSRSReportID) + { + var key = Request.Query.ElementAt(1).Key; + var query1 = string.Format("\r\nDECLARE @{0}ID INT = (SELECT {0}ID FROM[Get{0}]('", key); + var query2 = string.Format("'))\r\n\r\nSELECT SSRSReports FROM [ClientCentral].[dbo].[ClientDatabases] WHERE {0}ID = @{0}ID)", key); + var query = (query1 + query2); + + try + { + var rname = ExecuteQuery(query); + return Content($"Result: " + rname); + } + catch + { + return Content("Nothing to display."); + } + } } } diff --git a/tracer/test/test-applications/security/aspnet/Samples.Security.AspNetMvc5/Controllers/IastController.cs b/tracer/test/test-applications/security/aspnet/Samples.Security.AspNetMvc5/Controllers/IastController.cs index d8ea1fbf6313..96113dc51a78 100644 --- a/tracer/test/test-applications/security/aspnet/Samples.Security.AspNetMvc5/Controllers/IastController.cs +++ b/tracer/test/test-applications/security/aspnet/Samples.Security.AspNetMvc5/Controllers/IastController.cs @@ -763,5 +763,32 @@ private ActionResult SendMailAux(string firstName, string lastName, string email return Content("Email sent"); } + + [Route("Print")] + public ActionResult Print( + bool Encrypt, + string ClientDatabase, + int p, + int ID, + int EntityType, + bool Print, + string OutputType, + int SSRSReportID) + { + var key = Request.QueryString.AllKeys.ElementAt(1); + var query1 = string.Format("\r\nDECLARE @{0}ID INT = (SELECT {0}ID FROM[Get{0}]('", key); + var query2 = string.Format("'))\r\n\r\nSELECT SSRSReports FROM [ClientCentral].[dbo].[ClientDatabases] WHERE {0}ID = @{0}ID)", key); + var query = query1 + query2; + + try + { + var rname = ExecuteQuery(query); + return Content($"Result: " + rname); + } + catch + { + return Content("Nothing to display."); + } + } } } diff --git a/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/Samples.Security.WebForms.csproj b/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/Samples.Security.WebForms.csproj index 9287687fac1e..8277bf67c57d 100644 --- a/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/Samples.Security.WebForms.csproj +++ b/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/Samples.Security.WebForms.csproj @@ -133,6 +133,7 @@ + @@ -142,6 +143,13 @@ + + print.aspx + ASPXCodeBehind + + + print.aspx + User.aspx ASPXCodeBehind diff --git a/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/print.aspx b/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/print.aspx new file mode 100644 index 000000000000..b224c4a44978 --- /dev/null +++ b/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/print.aspx @@ -0,0 +1,16 @@ + <%@ Page Language="C#" AutoEventWireup="true" CodeFile="print.aspx.cs" Inherits="Iast_Print" %> + + + + + Print + + +
+
+ + +
+
+ + \ No newline at end of file diff --git a/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/print.aspx.cs b/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/print.aspx.cs new file mode 100644 index 000000000000..cd7840473ee9 --- /dev/null +++ b/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/print.aspx.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; +using System.Linq; +using System.Web.UI; + +public partial class Iast_Print : Page +{ + protected void Page_Load(object sender, EventArgs e) + { + var key = Request.QueryString.AllKeys.ElementAt(1); + + try + { + _ = File.ReadAllLines(key); + } + catch + { + + } + } +} diff --git a/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/print.aspx.designer.cs b/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/print.aspx.designer.cs new file mode 100644 index 000000000000..3170e4d2517b --- /dev/null +++ b/tracer/test/test-applications/security/aspnet/Samples.Security.WebForms/print.aspx.designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Samples.Security.WebForms +{ + + + public partial class print + { + + /// + /// form1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.HtmlControls.HtmlForm form1; + } +}