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] Send header values as string to the WAF #6116

Closed
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Datadog.Trace.Util.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Primitives;

namespace Datadog.Trace.AppSec.Coordinator;

Expand All @@ -28,22 +29,23 @@ internal SecurityCoordinator(Security security, Span span, HttpTransport? transp

private static bool CanAccessHeaders => true;

public static Dictionary<string, string[]> ExtractHeadersFromRequest(IHeaderDictionary headers)
public static Dictionary<string, object> ExtractHeadersFromRequest(IHeaderDictionary headers)
{
var headersDic = new Dictionary<string, string[]>(headers.Keys.Count);
var headersDic = new Dictionary<string, object>(headers.Keys.Count);
foreach (var k in headers.Keys)
{
var currentKey = k ?? string.Empty;
if (!currentKey.Equals("cookie", System.StringComparison.OrdinalIgnoreCase))
{
currentKey = currentKey.ToLowerInvariant();
var value = GetHeaderValueForWaf(headers[currentKey]);
#if NETCOREAPP
if (!headersDic.TryAdd(currentKey, headers[currentKey]))
if (!headersDic.TryAdd(currentKey, value))
{
#else
if (!headersDic.ContainsKey(currentKey))
{
headersDic.Add(currentKey, headers[currentKey]);
headersDic.Add(currentKey, value);
}
else
{
Expand All @@ -56,6 +58,11 @@ public static Dictionary<string, string[]> ExtractHeadersFromRequest(IHeaderDict
return headersDic;
}

private static object GetHeaderValueForWaf(StringValues value)
{
return (value.Count == 1 ? value[0] : value);
}

internal void BlockAndReport(IResult? result)
{
if (result is not null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,20 +377,20 @@ public Dictionary<string, object> GetBasicRequestArgsForWaf()
{
var request = _httpTransport.Context.Request;
var headers = RequestDataHelper.GetHeaders(request);
Dictionary<string, string[]>? headersDic = null;
Dictionary<string, object>? headersDic = null;

if (headers is not null)
{
var headerKeys = headers.Keys;
headersDic = new Dictionary<string, string[]>(headerKeys.Count);
headersDic = new Dictionary<string, object>(headerKeys.Count);
foreach (string originalKey in headerKeys)
{
var keyForDictionary = originalKey?.ToLowerInvariant() ?? string.Empty;
if (keyForDictionary != "cookie")
{
if (!headersDic.ContainsKey(keyForDictionary))
{
headersDic.Add(keyForDictionary, headers.GetValues(originalKey));
headersDic.Add(keyForDictionary, GetHeaderValueForWaf(headers.GetValues(originalKey)));
}
else
{
Expand Down Expand Up @@ -488,10 +488,15 @@ public Dictionary<string, object> GetBasicRequestArgsForWaf()
return dict;
}

public Dictionary<string, string[]> GetResponseHeadersForWaf()
private static object GetHeaderValueForWaf(string[] value)
{
return (value.Count() == 1 ? value[0] : value);
}

public Dictionary<string, object> GetResponseHeadersForWaf()
{
var response = _httpTransport.Context.Response;
var headersDic = new Dictionary<string, string[]>(response.Headers.Keys.Count);
var headersDic = new Dictionary<string, object>(response.Headers.Keys.Count);
var headerKeys = response.Headers.Keys;
foreach (string originalKey in headerKeys)
{
Expand All @@ -501,7 +506,7 @@ public Dictionary<string, string[]> GetResponseHeadersForWaf()
keyForDictionary = keyForDictionary.ToLowerInvariant();
if (!headersDic.ContainsKey(keyForDictionary))
{
headersDic.Add(keyForDictionary, response.Headers.GetValues(originalKey));
headersDic.Add(keyForDictionary, GetHeaderValueForWaf(response.Headers.GetValues(originalKey)));
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public async Task TestApiSecurityScan(string url, string body, HttpStatusCode ex
// Simple scrubber for the response content type in .NET 8
// .NET 8 doesn't add the content-length header, whereas previous versions do
settings.AddSimpleScrubber(
"""_dd.appsec.s.res.headers: [{"content-length":[[[8]],{"len":1}]}],""",
"""_dd.appsec.s.res.headers: [{"content-length":[8]}],""",
"""_dd.appsec.s.res.headers: [{}],""");
#endif
await VerifySpans(spans, settings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class AspNetBase : TestHelper
private static readonly Regex AppSecRaspWafDuration = new(@"_dd.appsec.rasp.duration: \d+\.0", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex AppSecRaspWafDurationWithBindings = new(@"_dd.appsec.rasp.duration_ext: \d+\.0", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex AppSecFingerPrintHeaders = new(@"_dd.appsec.fp.http.header: hdr-\d+-\S*-\d+-\S*", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex AppSecFingerPrintNetwork = new(@"_dd.appsec.fp.http.network: net-\d+-\d+", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex AppSecSpanIdRegex = (new Regex("\"span_id\":\\d+"));
private static readonly Type MetaStructHelperType = Type.GetType("Datadog.Trace.AppSec.Rasp.MetaStructHelper, Datadog.Trace");
private static readonly MethodInfo MetaStructByteArrayToObject = MetaStructHelperType.GetMethod("ByteArrayToObject", BindingFlags.Public | BindingFlags.Static);
Expand Down Expand Up @@ -124,6 +125,7 @@ public async Task TestAppSecRequestWithVerifyAsync(MockTracerAgent agent, string
public void ScrubFingerprintHeaders(VerifySettings settings)
{
settings.AddRegexScrubber(AppSecFingerPrintHeaders, "_dd.appsec.fp.http.header: <HeaderPrint>");
settings.AddRegexScrubber(AppSecFingerPrintNetwork, "_dd.appsec.fp.http.network: <NetworkPrint>");
}

public async Task VerifySpans(IImmutableList<MockSpan> spans, VerifySettings settings, bool testInit = false, string methodNameOverride = null, string testName = null, bool forceMetaStruct = false, string fileNameOverride = null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[
[
{
TraceId: Id_1,
SpanId: Id_2,
Expand All @@ -25,8 +25,8 @@
network.client.ip: 127.0.0.1,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000000--1-4740ae63,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000000-3626b5f8-1-4740ae63,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"tst-037-011","name":"No fun","tags":{"category":"attack_attempt","type":"lfi"}},"rule_matches":[{"operator":"match_regex","operator_value":"fun","parameters":[{"address":"server.request.uri.raw","highlight":["fun"],"key_path":[],"value":"/health?q=fun"}]}]}]},
_dd.appsec.json.metastruct.test: true,
_dd.origin: appsec,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[
[
{
TraceId: Id_1,
SpanId: Id_2,
Expand Down Expand Up @@ -46,8 +46,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0100000001--3-bf93958a,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0100000001-3626b5f8-3-bf93958a,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-932-100","name":"Shell injection exploit","tags":{"category":"vulnerability_trigger","type":"command_injection"}},"rule_matches":[{"operator":"shi_detector","operator_value":"","parameters":[{"address":null,"highlight":[";evilCommand"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0100000001--3-bf93958a,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0100000001-3626b5f8-3-bf93958a,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-001-001","name":"Path traversal attack","tags":{"category":"vulnerability_trigger","type":"lfi"}},"rule_matches":[{"operator":"lfi_detector","operator_value":"","parameters":[{"address":null,"highlight":["/etc/password"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0100000001--3-bf93958a,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0100000001-3626b5f8-3-bf93958a,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-002-001","name":"Server-side request forgery","tags":{"category":"vulnerability_trigger","type":"ssrf"}},"rule_matches":[{"operator":"ssrf_detector","operator_value":"","parameters":[{"address":null,"highlight":["127.0.0.1"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[
[
{
TraceId: Id_1,
SpanId: Id_2,
Expand Down Expand Up @@ -47,8 +47,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0100000100--2-da57b738,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0100000100-3626b5f8-2-da57b738,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-942-100","name":"SQL injection exploit","tags":{"category":"vulnerability_trigger","type":"sql_injection"}},"rule_matches":[{"operator":"sqli_detector","operator_value":"","parameters":[{"address":null,"highlight":["' or '1'='1"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000001--3-bf93958a,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000001-3626b5f8-3-bf93958a,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-932-100","name":"Shell injection exploit","tags":{"category":"vulnerability_trigger","type":"command_injection"}},"rule_matches":[{"operator":"shi_detector","operator_value":"","parameters":[{"address":null,"highlight":[";evilCommand"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000001--3-bf93958a,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000001-3626b5f8-3-bf93958a,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-001-001","name":"Path traversal attack","tags":{"category":"vulnerability_trigger","type":"lfi"}},"rule_matches":[{"operator":"lfi_detector","operator_value":"","parameters":[{"address":null,"highlight":["/etc/password"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000001--3-bf93958a,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000001-3626b5f8-3-bf93958a,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-002-001","name":"Server-side request forgery","tags":{"category":"vulnerability_trigger","type":"ssrf"}},"rule_matches":[{"operator":"ssrf_detector","operator_value":"","parameters":[{"address":null,"highlight":["127.0.0.1"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000100--2-da57b738,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000100-3626b5f8-2-da57b738,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-942-100","name":"SQL injection exploit","tags":{"category":"vulnerability_trigger","type":"sql_injection"}},"rule_matches":[{"operator":"sqli_detector","operator_value":"","parameters":[{"address":null,"highlight":["' or '1'='1"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[
[
{
TraceId: Id_1,
SpanId: Id_2,
Expand All @@ -23,8 +23,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000001--3-bf93958a,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000001-3626b5f8-3-bf93958a,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-932-100","name":"Shell injection exploit","tags":{"category":"vulnerability_trigger","type":"command_injection"}},"rule_matches":[{"operator":"shi_detector","operator_value":"","parameters":[{"address":null,"highlight":[";evilCommand"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000001--3-bf93958a,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000001-3626b5f8-3-bf93958a,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-001-001","name":"Path traversal attack","tags":{"category":"vulnerability_trigger","type":"lfi"}},"rule_matches":[{"operator":"lfi_detector","operator_value":"","parameters":[{"address":null,"highlight":["/etc/password"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000001--3-bf93958a,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000001-3626b5f8-3-bf93958a,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-002-001","name":"Server-side request forgery","tags":{"category":"vulnerability_trigger","type":"ssrf"}},"rule_matches":[{"operator":"ssrf_detector","operator_value":"","parameters":[{"address":null,"highlight":["127.0.0.1"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000001--3-bf93958a,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000001-3626b5f8-3-bf93958a,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-002-001","name":"Server-side request forgery","tags":{"category":"vulnerability_trigger","type":"ssrf"}},"rule_matches":[{"operator":"ssrf_detector","operator_value":"","parameters":[{"address":null,"highlight":["127.0.0.1"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[
[
{
TraceId: Id_1,
SpanId: Id_2,
Expand All @@ -25,8 +25,8 @@
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.endpoint: http-post-a13f66cb--6f45fc03,
_dd.appsec.fp.http.header: hdr-0000000100--3-4d739311,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000100-3626b5f8-3-4d739311,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-942-100","name":"SQL injection exploit","tags":{"category":"vulnerability_trigger","type":"sql_injection"}},"rule_matches":[{"operator":"sqli_detector","operator_value":"","parameters":[{"address":null,"highlight":["' or '1'='1"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[
[
{
TraceId: Id_1,
SpanId: Id_2,
Expand All @@ -24,8 +24,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000001--5-6cdcf2fe,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000001-3626b5f8-5-6cdcf2fe,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-932-100","name":"Shell injection exploit","tags":{"category":"vulnerability_trigger","type":"command_injection"}},"rule_matches":[{"operator":"shi_detector","operator_value":"","parameters":[{"address":null,"highlight":[";evilCommand"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000001--5-6cdcf2fe,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000001-3626b5f8-5-6cdcf2fe,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-001-001","name":"Path traversal attack","tags":{"category":"vulnerability_trigger","type":"lfi"}},"rule_matches":[{"operator":"lfi_detector","operator_value":"","parameters":[{"address":null,"highlight":["/etc/password"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000001--5-6cdcf2fe,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000001-3626b5f8-5-6cdcf2fe,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-002-001","name":"Server-side request forgery","tags":{"category":"vulnerability_trigger","type":"ssrf"}},"rule_matches":[{"operator":"ssrf_detector","operator_value":"","parameters":[{"address":null,"highlight":["127.0.0.1"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
language: dotnet,
runtime-id: Guid_1,
span.kind: server,
_dd.appsec.fp.http.header: hdr-0000000001--5-6cdcf2fe,
_dd.appsec.fp.http.network: net-0-1000000000,
_dd.appsec.fp.http.header: hdr-0000000001-3626b5f8-5-6cdcf2fe,
_dd.appsec.fp.http.network: net-1-1000000000,
_dd.appsec.json: {"triggers":[{"rule":{"id":"rasp-002-001","name":"Server-side request forgery","tags":{"category":"vulnerability_trigger","type":"ssrf"}},"rule_matches":[{"operator":"ssrf_detector","operator_value":"","parameters":[{"address":null,"highlight":["127.0.0.1"],"key_path":null,"value":null}]}],"span_id": XXX}]},
_dd.origin: appsec,
_dd.runtime_family: dotnet
Expand Down
Loading
Loading