From 108c22182e1ada7052e969add7d2d128f786c37b Mon Sep 17 00:00:00 2001 From: Will Godbe <wigodbe@microsoft.com> Date: Fri, 8 Apr 2022 01:40:24 +0000 Subject: [PATCH] Merged PR 21240: [6.0] MSRC 70023 - ASP.Net FormFeature.cs - DenialOfService # ASP.Net FormFeature.cs - DenialOfService When parsing multi-part form data with FormFeature.cs, we do not honor ValueCountLimit when the content disposition is of an unknown type. Therefore an attacker could send multi-part form data where very part has invalid content disposition, and make us read indefinitely. ## Description When parsing multi-part form data with FormFeature.cs, we do not honor ValueCountLimit when the content disposition is of an unknown type. Therefore an attacker could send multi-part form data where very part has invalid content disposition, and make us read indefinitely. ## Customer Impact Prevents a potential Denial-of-service attack. ## Regression? - [ ] Yes - [x] No ## Risk - [ ] High - [x] Medium - [ ] Low We could have missed another potential version of this vulnerability ## Verification - [x] Manual (required) - [x] Automated Added a test, plus confirmed with a local repro that the pre-existing slowdown goes away after the change. ## Packaging changes reviewed? - [ ] Yes - [ ] No - [x] N/A ---- ## When servicing release/2.1 - [ ] Make necessary changes in eng/PatchConfig.props --- src/Http/Http/src/Features/FormFeature.cs | 7 ++++- .../Http/test/Features/FormFeatureTests.cs | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Http/Http/src/Features/FormFeature.cs b/src/Http/Http/src/Features/FormFeature.cs index e758b1b0354..173904e8c19 100644 --- a/src/Http/Http/src/Features/FormFeature.cs +++ b/src/Http/Http/src/Features/FormFeature.cs @@ -184,6 +184,7 @@ namespace Microsoft.AspNetCore.Http.Features else if (HasMultipartFormContentType(contentType)) { var formAccumulator = new KeyValueAccumulator(); + var nonFormOrFileContentDispositionCount = 0; var boundary = GetBoundary(contentType, _options.MultipartBoundaryLengthLimit); var multipartReader = new MultipartReader(boundary, _request.Body) @@ -259,7 +260,11 @@ namespace Microsoft.AspNetCore.Http.Features } else { - System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + section.ContentDisposition); + if (nonFormOrFileContentDispositionCount++ >= _options.ValueCountLimit) + { + throw new InvalidDataException($"Unrecognized Content-Disposition. Form value count limit {_options.ValueCountLimit} exceeded."); + + } } section = await multipartReader.ReadNextSectionAsync(cancellationToken); diff --git a/src/Http/Http/test/Features/FormFeatureTests.cs b/src/Http/Http/test/Features/FormFeatureTests.cs index 9426ce6dd1b..67873e223c4 100644 --- a/src/Http/Http/test/Features/FormFeatureTests.cs +++ b/src/Http/Http/test/Features/FormFeatureTests.cs @@ -165,6 +165,12 @@ namespace Microsoft.AspNetCore.Http.Features InvalidContentDispositionValue + "\r\n" + "\r\n" + +"Foo\r\n"; + + private const string MultipartFormFileNonFormOrFileContentDispositionValue = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" + +"Content-Disposition:x" + +"\r\n" + +"\r\n" + "Foo\r\n"; private const string MultipartFormWithField = @@ -468,6 +474,30 @@ InvalidContentDispositionValue + Assert.Equal("Form value count limit 2 exceeded.", exception.Message); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ReadFormAsync_NonFormOrFieldContentDisposition_ValueCountLimitExceeded_Throw(bool bufferRequest) + { + var formContent = new List<byte>(); + formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFileNonFormOrFileContentDispositionValue)); + formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFileNonFormOrFileContentDispositionValue)); + formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFileNonFormOrFileContentDispositionValue)); + formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormEnd)); + + var context = new DefaultHttpContext(); + var responseFeature = new FakeResponseFeature(); + context.Features.Set<IHttpResponseFeature>(responseFeature); + context.Request.ContentType = MultipartContentType; + context.Request.Body = new NonSeekableReadStream(formContent.ToArray()); + + IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 }); + context.Features.Set<IFormFeature>(formFeature); + + var exception = await Assert.ThrowsAsync<InvalidDataException>(() => context.Request.ReadFormAsync()); + Assert.Equal("Unrecognized Content-Disposition. Form value count limit 2 exceeded.", exception.Message); + } + [Theory] [InlineData(true)] [InlineData(false)] -- GitLab