Przeglądaj źródła

Issue #11559: Show meaningful error message for TLS over HTTP endpoint. (#12697)

bashdx 6 lat temu
rodzic
commit
1bff37bec1

+ 3 - 0
src/Servers/Kestrel/Core/src/BadHttpRequestException.cs

@@ -139,6 +139,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
             BadHttpRequestException ex;
             switch (reason)
             {
+                case RequestRejectionReason.TlsOverHttpError:
+                    ex = new BadHttpRequestException(CoreStrings.HttpParserTlsOverHttpError, StatusCodes.Status400BadRequest, reason);
+                    break;
                 case RequestRejectionReason.InvalidRequestLine:
                     ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(detail), StatusCodes.Status400BadRequest, reason);
                     break;

+ 31 - 28
src/Servers/Kestrel/Core/src/CoreStrings.resx

@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <root>
-  <!--
-    Microsoft ResX Schema
-
+  <!-- 
+    Microsoft ResX Schema 
+    
     Version 2.0
-
-    The primary goals of this format is to allow a simple XML format
-    that is mostly human readable. The generation and parsing of the
-    various data types are done through the TypeConverter classes
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
     associated with the data types.
-
+    
     Example:
-
+    
     ... ado.net/XML headers & schema ...
     <resheader name="resmimetype">text/microsoft-resx</resheader>
     <resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
         <comment>This is a comment</comment>
     </data>
-
-    There are any number of "resheader" rows that contain simple
+                
+    There are any number of "resheader" rows that contain simple 
     name/value pairs.
-
-    Each data row contains a name, and value. The row also contains a
-    type or mimetype. Type corresponds to a .NET class that support
-    text/value conversion through the TypeConverter architecture.
-    Classes that don't support this are serialized and stored with the
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
     mimetype set.
-
-    The mimetype is used for serialized objects, and tells the
-    ResXResourceReader how to depersist the object. This is currently not
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
     extensible. For a given mimetype the value must be set accordingly:
-
-    Note - application/x-microsoft.net.object.binary.base64 is the format
-    that the ResXResourceWriter will generate, however the reader can
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
     read any of the formats listed below.
-
+    
     mimetype: application/x-microsoft.net.object.binary.base64
-    value   : The object must be serialized with
+    value   : The object must be serialized with 
             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
             : and then encoded with base64 encoding.
-
+    
     mimetype: application/x-microsoft.net.object.soap.base64
-    value   : The object must be serialized with
+    value   : The object must be serialized with 
             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
             : and then encoded with base64 encoding.
 
     mimetype: application/x-microsoft.net.object.bytearray.base64
-    value   : The object must be serialized into a byte array
+    value   : The object must be serialized into a byte array 
             : using a System.ComponentModel.TypeConverter
             : and then encoded with base64 encoding.
     -->
@@ -614,4 +614,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
   <data name="Http2StreamResetByApplication" xml:space="preserve">
     <value>The HTTP/2 stream was reset by the application with error code {errorCode}.</value>
   </data>
-</root>
+  <data name="HttpParserTlsOverHttpError" xml:space="preserve">
+    <value>Detected a TLS handshake to an endpoint that does not have TLS enabled.</value>
+  </data>
+</root>

+ 22 - 1
src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs

@@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
         private const byte ByteTab = (byte)'\t';
         private const byte ByteQuestionMark = (byte)'?';
         private const byte BytePercentage = (byte)'%';
+        private const int MinTlsRequestSize = 1; // We need at least 1 byte to check for a proper TLS request line
 
         public unsafe bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
         {
@@ -415,9 +416,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
             return new Span<byte>(data, methodLength);
         }
 
+        private unsafe bool IsTlsHandshake(byte* data, int length)
+        {
+            const byte SslRecordTypeHandshake = (byte)0x16;
+
+            // Make sure we can check at least for the existence of a TLS handshake - we check the first byte
+            // See https://serializethoughts.com/2014/07/27/dissecting-tls-client-hello-message/
+
+            return (length >= MinTlsRequestSize && data[0] == SslRecordTypeHandshake);
+        }
+
         [StackTraceHidden]
         private unsafe void RejectRequestLine(byte* requestLine, int length)
-            => throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length);
+        {
+            // Check for incoming TLS handshake over HTTP
+            if (IsTlsHandshake(requestLine, length))
+            {
+                throw GetInvalidRequestException(RequestRejectionReason.TlsOverHttpError, requestLine, length);
+            }
+            else
+            {
+                throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length);
+            }
+        }
 
         [StackTraceHidden]
         private unsafe void RejectRequestHeader(byte* headerLine, int length)

+ 2 - 1
src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs

@@ -1,10 +1,11 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
 {
     internal enum RequestRejectionReason
     {
+        TlsOverHttpError,
         UnrecognizedHTTPVersion,
         InvalidRequestLine,
         InvalidRequestHeader,

+ 18 - 1
src/Servers/Kestrel/Core/test/HttpParserTests.cs

@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System;
@@ -394,6 +394,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
             Assert.Equal(buffer.End, examined);
         }
 
+        [Fact]
+        public void ParseRequestLineTlsOverHttp()
+        {
+            var parser = CreateParser(_nullTrace);
+            var buffer = ReadOnlySequenceFactory.CreateSegments(new byte[] { 0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0xfc, 0x03, 0x03, 0x03, 0xca, 0xe0, 0xfd, 0x0a });
+
+            var requestHandler = new RequestHandler();
+
+            var badHttpRequestException = Assert.Throws<BadHttpRequestException>(() =>
+            {
+                parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined);
+            });
+
+            Assert.Equal(badHttpRequestException.StatusCode, StatusCodes.Status400BadRequest);
+            Assert.Equal(RequestRejectionReason.TlsOverHttpError, badHttpRequestException.Reason);
+        }
+
         [Fact]
         public void ParseHeadersWithGratuitouslySplitBuffers()
         {