From 3061a48a38d73afa40d1c776709ed6c26d537e6f Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 10 May 2016 10:59:42 -0700 Subject: [PATCH] Do not allow control characters in response headers. --- .../RequestProcessing/HeaderCollection.cs | 32 ++++++++++ .../ResponseHeaderTests.cs | 61 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/HeaderCollection.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/HeaderCollection.cs index 23778ddec5..2decf287e0 100644 --- a/src/Microsoft.Net.Http.Server/RequestProcessing/HeaderCollection.cs +++ b/src/Microsoft.Net.Http.Server/RequestProcessing/HeaderCollection.cs @@ -41,6 +41,8 @@ namespace Microsoft.Net.Http.Server } else { + ValidateHeaderCharacters(key); + ValidateHeaderCharacters(value); Store[key] = value; } } @@ -52,6 +54,8 @@ namespace Microsoft.Net.Http.Server set { ThrowIfReadOnly(); + ValidateHeaderCharacters(key); + ValidateHeaderCharacters(value); Store[key] = value; } } @@ -74,18 +78,24 @@ namespace Microsoft.Net.Http.Server public void Add(KeyValuePair item) { ThrowIfReadOnly(); + ValidateHeaderCharacters(item.Key); + ValidateHeaderCharacters(item.Value); Store.Add(item); } public void Add(string key, StringValues value) { ThrowIfReadOnly(); + ValidateHeaderCharacters(key); + ValidateHeaderCharacters(value); Store.Add(key, value); } public void Append(string key, string value) { ThrowIfReadOnly(); + ValidateHeaderCharacters(key); + ValidateHeaderCharacters(value); StringValues values; Store.TryGetValue(key, out values); Store[key] = StringValues.Concat(values, value); @@ -156,5 +166,27 @@ namespace Microsoft.Net.Http.Server throw new InvalidOperationException("The response headers cannot be modified because the response has already started."); } } + + public static void ValidateHeaderCharacters(StringValues headerValues) + { + foreach (var value in headerValues) + { + ValidateHeaderCharacters(value); + } + } + + public static void ValidateHeaderCharacters(string headerCharacters) + { + if (headerCharacters != null) + { + foreach (var ch in headerCharacters) + { + if (ch < 0x20) + { + throw new InvalidOperationException(string.Format("Invalid control character in header: 0x{0:X2}", (byte)ch)); + } + } + } + } } } \ No newline at end of file diff --git a/test/Microsoft.Net.Http.Server.FunctionalTests/ResponseHeaderTests.cs b/test/Microsoft.Net.Http.Server.FunctionalTests/ResponseHeaderTests.cs index 3a498ecbda..a788e2ffb4 100644 --- a/test/Microsoft.Net.Http.Server.FunctionalTests/ResponseHeaderTests.cs +++ b/test/Microsoft.Net.Http.Server.FunctionalTests/ResponseHeaderTests.cs @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; +using Microsoft.Extensions.Primitives; using Xunit; namespace Microsoft.Net.Http.Server @@ -415,6 +417,65 @@ namespace Microsoft.Net.Http.Server } } + [Theory] + [InlineData("Server", "\r\nData")] + [InlineData("Server", "\0Data")] + [InlineData("Server", "Data\r")] + [InlineData("Server", "Da\0ta")] + [InlineData("Server", "Da\u001Fta")] + [InlineData("Unknown-Header", "\r\nData")] + [InlineData("Unknown-Header", "\0Data")] + [InlineData("Unknown-Header", "Data\0")] + [InlineData("Unknown-Header", "Da\nta")] + [InlineData("\r\nServer", "Data")] + [InlineData("Server\r", "Data")] + [InlineData("Ser\0ver", "Data")] + [InlineData("Server\r\n", "Data")] + [InlineData("\u001FServer", "Data")] + [InlineData("Unknown-Header\r\n", "Data")] + [InlineData("\0Unknown-Header", "Data")] + [InlineData("Unknown\r-Header", "Data")] + [InlineData("Unk\nown-Header", "Data")] + public async Task AddingControlCharactersToHeadersThrows(string key, string value) + { + string address; + using (var server = Utilities.CreateHttpServer(out address)) + { + Task responseTask = SendRequestAsync(address); + + var context = await server.GetContextAsync(); + + var responseHeaders = context.Response.Headers; + + Assert.Throws(() => { + responseHeaders[key] = value; + }); + + Assert.Throws(() => { + responseHeaders[key] = new StringValues(new[] { "valid", value }); + }); + + Assert.Throws(() => { + ((IDictionary)responseHeaders)[key] = value; + }); + + Assert.Throws(() => { + var kvp = new KeyValuePair(key, value); + ((ICollection>)responseHeaders).Add(kvp); + }); + + Assert.Throws(() => { + var kvp = new KeyValuePair(key, value); + ((IDictionary)responseHeaders).Add(key, value); + }); + + context.Dispose(); + + HttpResponseMessage response = await responseTask; + response.EnsureSuccessStatusCode(); + } + } + private async Task SendRequestAsync(string uri, bool usehttp11 = true, bool sendKeepAlive = false) { using (HttpClient client = new HttpClient())