Skip to content
This repository was archived by the owner on Jul 5, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# Changelog

## Version 2.6.0-beta2
- [Added a max length restriction to values passed in through requests.](https://github.com/Microsoft/ApplicationInsights-dotnet-server/pull/810)


## Version 2.6.0-beta1
- [Fix: Dependency Telemetry is not collected with DiagnosticSource when response does not have content.](https://github.com/Microsoft/ApplicationInsights-dotnet-server/issues/739)
- [Improve DependencyCollectorEventSource.Log.CurrentActivityIsNull](https://github.com/Microsoft/ApplicationInsights-dotnet-server/issues/799)

**Project**
- A significant number of upgrades to our testing infrastructure.


## Version 2.5.0
- [Fix: System.InvalidCastException for SQL Dependency](https://github.com/Microsoft/ApplicationInsights-dotnet-server/issues/782)

Expand Down
2 changes: 2 additions & 0 deletions Src/Common/Common.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
<Compile Include="$(MSBuildThisFileDirectory)ExceptionUtilities.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HeadersUtilities.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ICorrelationIdLookupHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)InjectionGuardConstants.cs" />
<Compile Include="$(MSBuildThisFileDirectory)RequestResponseHeaders.cs" />
<Compile Include="$(MSBuildThisFileDirectory)StringUtilities.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'netstandard1.6' ">
<Compile Include="$(MSBuildThisFileDirectory)WebHeaderCollectionExtensions.cs" />
Expand Down
4 changes: 4 additions & 0 deletions Src/Common/CorrelationIdLookupHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,12 @@ public bool IsFetchAppInProgress(string ikey)
/// </summary>
/// <param name="ikey">Instrumentation Key is expected to be a Guid string.</param>
/// <param name="appId">Application Id is expected to be a Guid string. App Id needs to be Http Header safe, and all non-ASCII characters will be removed.</param>
/// <remarks>To protect against injection attacks, AppId will be truncated to a maximum length.</remarks>
private void GenerateCorrelationIdAndAddToDictionary(string ikey, string appId)
{
// Arbitrary maximum length to guard against injections.
appId = StringUtilities.EnforceMaxLength(appId, InjectionGuardConstants.AppIdMaxLengeth);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we just drop the AppId if it does not satisfy the expected length and/or format? However, let's see if it's actually consuming less resources than to pass it around after it's been cut to MaxLength and write once into the item.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I don't understand your "less resources" comment.

If we drop the AppId, the sdk will keep making appId requests (after brief timeout). I think either approach could be valid, but lets discuss what's best for this use case.

Copy link
Copy Markdown
Member

@Dmitry-Matveev Dmitry-Matveev Feb 10, 2018

Choose a reason for hiding this comment

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

There are two ways we can go about appId if we think it's wrong (e.g. does not satisfy length or format):

  • Drop it and let it query for it again later;
  • Accept it (e.g. cut to length or ignore the wrong format) and let SDK process the wrong appID entry everywhere down the line.

Both cases have their own pros, so I was interested in which one is "cheaper" from CPU/Memory perspective - trying out for new appId or keep re-using the wrong one on related code paths.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Discussed offline with Dmitry.
We are deciding to truncate the value and accept it as is.
Assuming that if an attacker injected a fraudulent value, that attacker would continue to inject fraudulent values on retry attempts. Best to quit and wait until app pool recycles to try again.


if (string.IsNullOrWhiteSpace(appId))
{
return;
Expand Down
7 changes: 5 additions & 2 deletions Src/Common/HeadersUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public static string GetHeaderKeyValue(IEnumerable<string> headerValues, string
string[] keyNameValueParts = keyNameValue.Trim().Split('=');
if (keyNameValueParts.Length == 2 && keyNameValueParts[0].Trim() == keyName)
{
return keyNameValueParts[1].Trim();
string value = keyNameValueParts[1].Trim();
return StringUtilities.EnforceMaxLength(value, InjectionGuardConstants.RequestHeaderMaxLength);
}
}
}
Expand All @@ -47,7 +48,9 @@ public static IDictionary<string, string> GetHeaderDictionary(IEnumerable<string
string keyName = keyNameValueParts[0].Trim();
if (!result.ContainsKey(keyName))
{
result.Add(keyName, keyNameValueParts[1].Trim());
string value = keyNameValueParts[1].Trim();
value = StringUtilities.EnforceMaxLength(value, InjectionGuardConstants.RequestHeaderMaxLength);
result.Add(keyName, value);
}
}
}
Expand Down
20 changes: 20 additions & 0 deletions Src/Common/InjectionGuardConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Microsoft.ApplicationInsights.Common
{
/// <summary>
/// These values are listed to guard against malicious injections by limiting the max size allowed in an HTTP Response.
/// These max limits are intentionally exaggerated to allow for unexpected responses, while still guarding against unreasonably large responses.
/// Example: While a 32 character response may be expected, 50 characters may be permitted while a 10,000 character response would be unreasonable and malicious.
/// </summary>
public static class InjectionGuardConstants
{
/// <summary>
/// Max length of AppId allowed in response from Breeze.
/// </summary>
public const int AppIdMaxLengeth = 50;

/// <summary>
/// Max length of incoming Request Header value allowed.
/// </summary>
public const int RequestHeaderMaxLength = 1024;
}
}
26 changes: 26 additions & 0 deletions Src/Common/StringUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Microsoft.ApplicationInsights.Common
{
using System.Diagnostics;

/// <summary>
/// Generic functions to perform common operations on a string.
/// </summary>
public static class StringUtilities
{
/// <summary>
/// Check a strings length and trim to a max length if needed.
/// </summary>
public static string EnforceMaxLength(string input, int maxLength)
{
Debug.Assert(input != null, $"{nameof(input)} must not be null");
Debug.Assert(maxLength > 0, $"{nameof(maxLength)} must be greater than 0");

if (input != null && input.Length > maxLength)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We seem to have assert on maxLength > 0 but still execute code with that value - is it intentional?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I saw this design pattern used in other places.
Not sure about best practices in this case.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Discussed with Dmitry offline.
We're going to leave this for now, both conditions are already addressed in the if statement so no risk to the application.
At a later date, we'll add a Test Project around our Common library and remove these asserts.

{
input = input.Substring(0, maxLength);
}

return input;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ public void CorrelationIdLookupHelperReturnsAppIdOnSecondCall()
var correlationIdLookupHelper = new CorrelationIdLookupHelper((ikey) =>
{
// Pretend App Id is the same as Ikey
var tcs = new TaskCompletionSource<string>();
tcs.SetResult(ikey);
return tcs.Task;
return Task.FromResult(ikey);
});

string instrumenationKey = Guid.NewGuid().ToString();
Expand All @@ -44,5 +42,40 @@ public void CorrelationIdLookupHelperReturnsAppIdOnSecondCall()
// Once fetch is complete, subsequent calls should return correlation id.
Assert.IsTrue(correlationIdLookupHelper.TryGetXComponentCorrelationId(instrumenationKey, out cid));
}

/// <summary>
/// Test that if an malicious value is returned, that value will be truncated.
/// </summary>
[TestMethod]
public void CorrelationIdLookupHelperTruncatesMaliciousValue()
{
// 50 character string.
var value = "a123456789b123546789c123456789d123456798e123456789";

// An arbitrary string that is expected to be truncated.
var malicious = "00000000000000000000000000000000000000000000000000000000000";

var cidPrefix = "cid-v1:";

var correlationIdLookupHelper = new CorrelationIdLookupHelper((ikey) =>
{
return Task.FromResult(value + malicious);
});

string instrumenationKey = Guid.NewGuid().ToString();

// first request fails because this will create the fetch task.
Assert.IsFalse(correlationIdLookupHelper.TryGetXComponentCorrelationId(instrumenationKey, out string ignore));

// Let's wait for the task to complete. It should be really quick (based on the test setup) but not immediate.
while (correlationIdLookupHelper.IsFetchAppInProgress(instrumenationKey))
{
Thread.Sleep(10); // wait 10 ms.
}

// Once fetch is complete, subsequent calls should return correlation id.
Assert.IsTrue(correlationIdLookupHelper.TryGetXComponentCorrelationId(instrumenationKey, out string cid));
Assert.AreEqual(cidPrefix + value, cid);
}
}
}
4 changes: 3 additions & 1 deletion Src/Web/Web.Net45/Implementation/HttpRequestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Specialized;
using System.Web;
using Microsoft.ApplicationInsights.Common;

/// <summary>
/// HttpRequest Extensions.
Expand All @@ -16,7 +17,8 @@ public static HttpCookie UnvalidatedGetCookie(this HttpRequest httpRequest, stri

public static string UnvalidatedGetHeader(this HttpRequest httpRequest, string headerName)
{
return httpRequest.Unvalidated.Headers[headerName];
string value = httpRequest.Unvalidated.Headers[headerName];
return StringUtilities.EnforceMaxLength(value, InjectionGuardConstants.RequestHeaderMaxLength);
}

public static Uri UnvalidatedGetUrl(this HttpRequest httpRequest)
Expand Down
1 change: 1 addition & 0 deletions Src/Web/Web.Shared.Net/RequestTrackingTelemetryModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ internal bool OnEndRequest_ShouldLog(HttpContext context)
try
{
var rootRequestId = headers[HeaderRootRequestId];
rootRequestId = StringUtilities.EnforceMaxLength(rootRequestId, InjectionGuardConstants.RequestHeaderMaxLength);
if (rootRequestId != null)
{
if (!this.IsRequestKnown(rootRequestId))
Expand Down