Skip to content

Commit

Permalink
Clientruntime/sanitize request header (#28169)
Browse files Browse the repository at this point in the history
* fix(ClientRuntime): sanitize request headers

1. add `HttpRequestSanitizer` to sanitize headers
2. update `HttpRequestMessageWrapper` to sanitive headers during headers copy
3. add test cases

* prepare release notes of 2.3.24
  • Loading branch information
archerzz authored Apr 12, 2022
1 parent 8724e58 commit 0a3afc9
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Rest.Utilities;
using System;
using System.Collections.Generic;
using System.Net.Http;
Expand All @@ -26,6 +27,7 @@ public HttpRequestMessageWrapper(HttpRequestMessage httpRequest, string content)

this.CopyHeaders(httpRequest.Headers);
this.CopyHeaders(httpRequest.GetContentHeaders());
HttpRequestSanitizer.SanitizerHeaders(Headers);

this.Content = content;
this.Method = httpRequest.Method;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
<Description>Infrastructure for error handling, tracing, and HttpClient pipeline configuration. Required by client libraries generated using AutoRest.</Description>
<AssemblyName>Microsoft.Rest.ClientRuntime</AssemblyName>
<AssemblyTitle>Client Runtime Library for Microsoft AutoRest Generated Clients</AssemblyTitle>
<Version>2.3.23</Version>
<Version>2.3.24</Version>
<PackageId>Microsoft.Rest.ClientRuntime</PackageId>
<PackageTags>Microsoft AutoRest ClientRuntime $(NugetCommonTags) $(NugetCommonProfileTags)</PackageTags>
<PackageReleaseNotes>
<![CDATA[
Added support for specifying MaxRetries in RetryAfterDelegatingHandler.
- Improving sanitization of exception messages. For details, see "CVE-2022-26907 - Security Update Guide - Microsoft - Azure SDK for .NET Information Disclosure Vulnerability": https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-26907
- Optimize performance of parsing JSON content
]]>
</PackageReleaseNotes>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Rest.Utilities
{
/// <summary>
/// Sanitizer used internall by <see cref="HttpRequestMessageWrapper"/>.
/// </summary>
internal class HttpRequestSanitizer
{
private readonly static string _redactedPlaceholder = "REDACTED";
private readonly static HashSet<string> _allowedHeaders = new HashSet<string>(new string[]
{
"x-ms-request-id",
"x-ms-client-request-id",
"x-ms-return-client-request-id",
"traceparent",
"MS-CV",

"Accept",
"Cache-Control",
"Connection",
"Content-Length",
"Content-Type",
"Date",
"ETag",
"Expires",
"If-Match",
"If-Modified-Since",
"If-None-Match",
"If-Unmodified-Since",
"Last-Modified",
"Pragma",
"Request-Id",
"Retry-After",
"Server",
"Transfer-Encoding",
"User-Agent",
"WWW-Authenticate" // OAuth Challenge header.
}, StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Sanitize value of sensitive headers in the given <paramref name="headers"/>.
/// </summary>
/// <param name="headers">A collection of headers to sanitize.</param>
public static void SanitizerHeaders(IDictionary<string, IEnumerable<string>> headers)
{
if (headers == null)
{
return;
}

var namesOfHeaderToSanitize = headers.Keys.Except(_allowedHeaders, StringComparer.OrdinalIgnoreCase).ToList();

foreach (string name in namesOfHeaderToSanitize)
{
headers[name] = new string[] { _redactedPlaceholder };
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace Microsoft.Rest.ClientRuntime.Tests
{
using System.Net;
using System.Net.Http;
using System.Collections.Generic;
using Xunit;
using System.Linq;

public class HttpRequestSanitizerTest
{
[Theory]
[InlineData("authorization")]
[InlineData("Authorization")]
[InlineData("AUTHORIZATION")]
public void SanitizeAuthorizationHeader(string headerName)
{
var request = CreateRequestWrapper(headerName, "test");

Assert.True(request.Headers.TryGetValue(HttpRequestHeader.Authorization.ToString(), out IEnumerable<string> sanitizedValues));
Assert.Single(sanitizedValues);
Assert.NotEqual("test", sanitizedValues.First());
Assert.Equal("REDACTED", sanitizedValues.First());
}

[Theory]
[InlineData("custom")]
[InlineData("foo")]
[InlineData("x-ms-secret")]
public void SanitizeCustomHeader(string headerName)
{
var request = CreateRequestWrapper(headerName, "test");

Assert.True(request.Headers.TryGetValue(headerName, out IEnumerable<string> sanitizedValues));
Assert.Single(sanitizedValues);
Assert.NotEqual("test", sanitizedValues.First());
Assert.Equal("REDACTED", sanitizedValues.First());
}

[Theory]
[InlineData("Accept", "application/json")]
[InlineData("User-Agent", "azure-sdk")]
[InlineData("Pragma", "foo")]
public void KeepAllowedHeaders(string headerName, string headerValue)
{
var request = CreateRequestWrapper(headerName, headerValue);

Assert.True(request.Headers.TryGetValue(headerName, out IEnumerable<string> sanitizedValues));
Assert.Single(sanitizedValues);
Assert.Equal(headerValue, sanitizedValues.First());
}

[Theory]
[InlineData("accept", "Accept", "application/json")]
[InlineData("user-agent", "User-Agent", "azure-sdk")]
[InlineData("pragma", "Pragma", "foo")]
public void AllowedHeaderNamesAreCaseInsensitive(string headerName, string standardName, string headerValue)
{
var request = CreateRequestWrapper(headerName, headerValue);

Assert.True(request.Headers.TryGetValue(standardName, out IEnumerable<string> sanitizedValues));
Assert.Single(sanitizedValues);
Assert.Equal(headerValue, sanitizedValues.First());
}

[Fact]
public void OnlySanitizeNotAllowedHeader()
{
var request = CreateRequestWrapper("Authorization", "test");
request.Headers.Add("Pragma", new string[] { "foo" });
request.Headers.Add("User-Agent", new string[] { "azure-sdk" });

Assert.True(request.Headers.TryGetValue(HttpRequestHeader.Authorization.ToString(), out IEnumerable<string> sanitizedValues));
Assert.Single(sanitizedValues);
Assert.NotEqual("test", sanitizedValues.First());
Assert.Equal("REDACTED", sanitizedValues.First());

Assert.True(request.Headers.TryGetValue("Pragma", out sanitizedValues));
Assert.Single(sanitizedValues);
Assert.Equal("foo", sanitizedValues.First());

Assert.True(request.Headers.TryGetValue("User-Agent", out sanitizedValues));
Assert.Single(sanitizedValues);
Assert.Equal("azure-sdk", sanitizedValues.First());
}

private HttpRequestMessageWrapper CreateRequestWrapper(string headerName, string headerValue)
{
var request = new HttpRequestMessage();
request.Headers.Add(headerName, headerValue);
return new HttpRequestMessageWrapper(request, "");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public void HeadersAndPayloadAreNotDisposed()
GC.Collect();
Assert.NotNull(ex.Request);
Assert.NotNull(ex.Response);
Assert.Equal("2013-11-01", ex.Request.Headers["x-ms-version"].First());
Assert.Equal("REDACTED", ex.Request.Headers["x-ms-version"].First());
Assert.Equal("Text", ex.Response.Content);
}
}
Expand Down

0 comments on commit 0a3afc9

Please sign in to comment.