Reject POST and PUT requests with no Content-Length or Transfer-Encoding (#1130).

This commit is contained in:
Cesar Blum Silveira 2016-11-03 13:57:55 -07:00
parent 5b65117b64
commit a1c598733b
6 changed files with 105 additions and 22 deletions

View File

@ -102,6 +102,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel
case RequestRejectionReason.FinalTransferCodingNotChunked: case RequestRejectionReason.FinalTransferCodingNotChunked:
ex = new BadHttpRequestException($"Final transfer coding is not \"chunked\": \"{value}\"", 400); ex = new BadHttpRequestException($"Final transfer coding is not \"chunked\": \"{value}\"", 400);
break; break;
case RequestRejectionReason.LengthRequired:
ex = new BadHttpRequestException($"{value} request contains no Content-Length or Transfer-Encoding header", 411);
break;
case RequestRejectionReason.LengthRequiredHttp10:
ex = new BadHttpRequestException($"{value} request contains no Content-Length header", 400);
break;
default: default:
ex = new BadHttpRequestException("Bad request.", 400); ex = new BadHttpRequestException("Bad request.", 400);
break; break;

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Numerics; using System.Numerics;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@ -266,17 +267,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
return new ForChunkedEncoding(keepAlive, headers, context); return new ForChunkedEncoding(keepAlive, headers, context);
} }
var unparsedContentLength = headers.HeaderContentLength.ToString(); var unparsedContentLength = headers.HeaderContentLength;
if (unparsedContentLength.Length > 0) if (unparsedContentLength.Count > 0)
{ {
long contentLength; try
if (!long.TryParse(unparsedContentLength, out contentLength) || contentLength < 0) {
var contentLength = FrameHeaders.ParseContentLength(unparsedContentLength);
return new ForContentLength(keepAlive, contentLength, context);
}
catch (InvalidOperationException)
{ {
context.RejectRequest(RequestRejectionReason.InvalidContentLength, unparsedContentLength); context.RejectRequest(RequestRejectionReason.InvalidContentLength, unparsedContentLength);
} }
else }
// Avoid slowing down most common case
if (!object.ReferenceEquals(context.Method, HttpMethods.Get))
{
// If we got here, request contains no Content-Length or Transfer-Encoding header.
// Reject with 411 Length Required.
if (HttpMethods.IsPost(context.Method) || HttpMethods.IsPut(context.Method))
{ {
return new ForContentLength(keepAlive, contentLength, context); var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10;
context.RejectRequest(requestRejectionReason, context.Method);
} }
} }

View File

@ -27,6 +27,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
MissingCRInHeaderLine, MissingCRInHeaderLine,
TooManyHeaders, TooManyHeaders,
RequestTimeout, RequestTimeout,
FinalTransferCodingNotChunked FinalTransferCodingNotChunked,
LengthRequired,
LengthRequiredHttp10,
} }
} }

View File

@ -11,19 +11,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public class MaxRequestLineSizeTests public class MaxRequestLineSizeTests
{ {
[Theory] [Theory]
[InlineData("GET / HTTP/1.1\r\n", 16)] [InlineData("GET / HTTP/1.1\r\n\r\n", 16)]
[InlineData("GET / HTTP/1.1\r\n", 17)] [InlineData("GET / HTTP/1.1\r\n\r\n", 17)]
[InlineData("GET / HTTP/1.1\r\n", 137)] [InlineData("GET / HTTP/1.1\r\n\r\n", 137)]
[InlineData("POST /abc/de HTTP/1.1\r\n", 23)] [InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 23)]
[InlineData("POST /abc/de HTTP/1.1\r\n", 24)] [InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 24)]
[InlineData("POST /abc/de HTTP/1.1\r\n", 287)] [InlineData("POST /abc/de HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 287)]
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 28)] [InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 28)]
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 29)] [InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 29)]
[InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\n", 589)] [InlineData("PUT /abc/de?f=ghi HTTP/1.1\r\nContent-Length: 0\r\n\r\n", 589)]
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 40)] [InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 40)]
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 41)] [InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 41)]
[InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n", 1027)] [InlineData("DELETE /a%20b%20c/d%20e?f=ghi HTTP/1.1\r\n\r\n", 1027)]
public async Task ServerAcceptsRequestLineWithinLimit(string requestLine, int limit) public async Task ServerAcceptsRequestLineWithinLimit(string request, int limit)
{ {
var maxRequestLineSize = limit; var maxRequestLineSize = limit;
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{ {
using (var connection = new TestConnection(server.Port)) using (var connection = new TestConnection(server.Port))
{ {
await connection.SendEnd($"{requestLine}\r\n"); await connection.SendEnd(request);
await connection.ReceiveEnd( await connection.ReceiveEnd(
"HTTP/1.1 200 OK", "HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}", $"Date: {server.Context.DateHeaderValue}",

View File

@ -174,6 +174,36 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
} }
} }
[Theory]
[InlineData("POST")]
[InlineData("PUT")]
public async Task BadRequestIfMethodRequiresLengthButNoContentLengthOrTransferEncodingInRequest(string method)
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = server.CreateConnection())
{
await connection.SendEnd($"{method} / HTTP/1.1\r\n\r\n");
await ReceiveBadRequestResponse(connection, "411 Length Required", server.Context.DateHeaderValue);
}
}
}
[Theory]
[InlineData("POST")]
[InlineData("PUT")]
public async Task BadRequestIfMethodRequiresLengthButNoContentLengthInHttp10Request(string method)
{
using (var server = new TestServer(context => { return Task.FromResult(0); }))
{
using (var connection = server.CreateConnection())
{
await connection.SendEnd($"{method} / HTTP/1.0\r\n\r\n");
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
}
}
}
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue) private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue)
{ {
await connection.ReceiveForcedEnd( await connection.ReceiveForcedEnd(

View File

@ -196,13 +196,45 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
using (var input = new TestInput()) using (var input = new TestInput())
{ {
var ex = Assert.Throws<BadHttpRequestException>(() => var ex = Assert.Throws<BadHttpRequestException>(() =>
MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders { HeaderTransferEncoding = "chunked, not-chunked" }, input.FrameContext)); MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderTransferEncoding = "chunked, not-chunked" }, input.FrameContext));
Assert.Equal(400, ex.StatusCode); Assert.Equal(400, ex.StatusCode);
Assert.Equal("Final transfer coding is not \"chunked\": \"chunked, not-chunked\"", ex.Message); Assert.Equal("Final transfer coding is not \"chunked\": \"chunked, not-chunked\"", ex.Message);
} }
} }
[Theory]
[InlineData("POST")]
[InlineData("PUT")]
public void ForThrowsWhenMethodRequiresLengthButNoContentLengthOrTransferEncodingIsSet(string method)
{
using (var input = new TestInput())
{
input.FrameContext.Method = method;
var ex = Assert.Throws<BadHttpRequestException>(() =>
MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders(), input.FrameContext));
Assert.Equal(411, ex.StatusCode);
Assert.Equal($"{method} request contains no Content-Length or Transfer-Encoding header", ex.Message);
}
}
[Theory]
[InlineData("POST")]
[InlineData("PUT")]
public void ForThrowsWhenMethodRequiresLengthButNoContentLengthSetHttp10(string method)
{
using (var input = new TestInput())
{
input.FrameContext.Method = method;
var ex = Assert.Throws<BadHttpRequestException>(() =>
MessageBody.For(HttpVersion.Http10, new FrameRequestHeaders(), input.FrameContext));
Assert.Equal(400, ex.StatusCode);
Assert.Equal($"{method} request contains no Content-Length header", ex.Message);
}
}
public static IEnumerable<object[]> StreamData => new[] public static IEnumerable<object[]> StreamData => new[]
{ {
new object[] { new ThrowOnWriteSynchronousStream() }, new object[] { new ThrowOnWriteSynchronousStream() },