Esempio n. 1
0
        public void Inspect(RequestAnalysisContext context, CancellationToken cancellationToken)
        {
            var method = context.Request.Method;

            if (!HttpMethods.IsGet(method) &&
                !HttpMethods.IsHead(method) &&
                !HttpMethods.IsOptions(method) &&
                !HttpMethods.IsPost(method) &&
                !HttpMethods.IsDelete(method) &&
                !HttpMethods.IsPut(method) &&
                !HttpMethods.IsPatch(method) &&
                !HttpMethods.IsConnect(method)
                )
            {
                // TRACE must be disabled
                if (HttpMethods.IsTrace(method))
                {
                    context.ReportDiagnostic(new Diagnostic(RuleTrace, Location.Method));
                    return;
                }

                // unknown method
                context.ReportDiagnostic(new Diagnostic(Rule, Location.Method));

                // Apache directory listing exploit
                if (method.Equals("GETS", StringComparison.OrdinalIgnoreCase))
                {
                    context.ReportDiagnostic(new Diagnostic(RuleGets, Location.Method));
                }
            }
        }
        /// <summary>
        /// Return a corresponding VerbsHttpMethod with the verb used in the request.
        /// </summary>
        /// <param name="context">ActionExecutingContext</param>
        /// <returns>Return VerbsHttpMethod corresponding to the request.</returns>
        public static VerbsHttpMethod GetVerbs(this ActionExecutingContext context)
        {
            var method = context.HttpContext.Request.Method;

            if (HttpMethods.IsGet(method))
            {
                return(VerbsHttpMethod.Get);
            }

            if (HttpMethods.IsPost(method))
            {
                return(VerbsHttpMethod.Post);
            }

            if (HttpMethods.IsDelete(method))
            {
                return(VerbsHttpMethod.Delete);
            }

            if (HttpMethods.IsPut(method))
            {
                return(VerbsHttpMethod.Put);
            }

            if (HttpMethods.IsHead(method))
            {
                return(VerbsHttpMethod.Head);
            }

            if (HttpMethods.IsOptions(method))
            {
                return(VerbsHttpMethod.Options);
            }

            if (HttpMethods.IsPatch(method))
            {
                return(VerbsHttpMethod.Patch);
            }

            if (HttpMethods.IsTrace(method))
            {
                return(VerbsHttpMethod.Trace);
            }

            if (HttpMethods.IsConnect(method))
            {
                return(VerbsHttpMethod.Connect);
            }

            throw new HttpMethodNotFoundException($"Could not find the HttpMethod '{method}'");
        }
Esempio n. 3
0
    public async Task ExecuteAsync(HttpContext context, HttpConnectionDispatcherOptions options, ConnectionDelegate connectionDelegate)
    {
        // Create the log scope and attempt to pass the Connection ID to it so as many logs as possible contain
        // the Connection ID metadata. If this is the negotiate request then the Connection ID for the scope will
        // be set a little later.

        HttpConnectionContext?connectionContext = null;
        var connectionToken = GetConnectionToken(context);

        if (!StringValues.IsNullOrEmpty(connectionToken))
        {
            // Use ToString; IsNullOrEmpty doesn't tell the compiler anything about implicit conversion to string.
            _manager.TryGetConnection(connectionToken.ToString(), out connectionContext);
        }

        var logScope = new ConnectionLogScope(connectionContext?.ConnectionId);

        using (_logger.BeginScope(logScope))
        {
            if (HttpMethods.IsPost(context.Request.Method))
            {
                // POST /{path}
                await ProcessSend(context);
            }
            else if (HttpMethods.IsGet(context.Request.Method) || HttpMethods.IsConnect(context.Request.Method))
            {
                // GET /{path}
                await ExecuteAsync(context, connectionDelegate, options, logScope);
            }
            else if (HttpMethods.IsDelete(context.Request.Method))
            {
                // DELETE /{path}
                await ProcessDeleteAsync(context);
            }
            else
            {
                context.Response.ContentType = "text/plain";
                context.Response.StatusCode  = StatusCodes.Status405MethodNotAllowed;
            }
        }
    }
Esempio n. 4
0
        private StreamCopyHttpContent SetupRequestBodyCopy(HttpRequest request, bool isStreamingRequest, CancellationToken cancellation)
        {
            // If we generate an HttpContent without a Content-Length then for HTTP/1.1 HttpClient will add a Transfer-Encoding: chunked header
            // even if it's a GET request. Some servers reject requests containing a Transfer-Encoding header if they're not expecting a body.
            // Try to be as specific as possible about the client's intent to send a body. The one thing we don't want to do is to start
            // reading the body early because that has side-effects like 100-continue.
            var hasBody       = true;
            var contentLength = request.Headers.ContentLength;
            var method        = request.Method;

#if NET
            var canHaveBodyFeature = request.HttpContext.Features.Get <IHttpRequestBodyDetectionFeature>();
            if (canHaveBodyFeature != null)
            {
                // 5.0 servers provide a definitive answer for us.
                hasBody = canHaveBodyFeature.CanHaveBody;
            }
            else
#endif
            // https://tools.ietf.org/html/rfc7230#section-3.3.3
            // All HTTP/1.1 requests should have Transfer-Encoding or Content-Length.
            // Http.Sys/IIS will even add a Transfer-Encoding header to HTTP/2 requests with bodies for back-compat.
            // HTTP/1.0 Connection: close bodies are only allowed on responses, not requests.
            // https://tools.ietf.org/html/rfc1945#section-7.2.2
            //
            // Transfer-Encoding overrides Content-Length per spec
            if (request.Headers.TryGetValue(HeaderNames.TransferEncoding, out var transferEncoding) &&
                transferEncoding.Count == 1 &&
                string.Equals("chunked", transferEncoding.ToString(), StringComparison.OrdinalIgnoreCase))
            {
                hasBody = true;
            }
            else if (contentLength.HasValue)
            {
                hasBody = contentLength > 0;
            }
            // Kestrel HTTP/2: There are no required headers that indicate if there is a request body so we need to sniff other fields.
            else if (!ProtocolHelper.IsHttp2OrGreater(request.Protocol))
            {
                hasBody = false;
            }
            // https://tools.ietf.org/html/rfc7231#section-4.3.1
            // A payload within a GET/HEAD/DELETE/CONNECT request message has no defined semantics; sending a payload body on a
            // GET/HEAD/DELETE/CONNECT request might cause some existing implementations to reject the request.
            // https://tools.ietf.org/html/rfc7231#section-4.3.8
            // A client MUST NOT send a message body in a TRACE request.
            else if (HttpMethods.IsGet(method) ||
                     HttpMethods.IsHead(method) ||
                     HttpMethods.IsDelete(method) ||
                     HttpMethods.IsConnect(method) ||
                     HttpMethods.IsTrace(method))
            {
                hasBody = false;
            }
            // else hasBody defaults to true

            StreamCopyHttpContent requestContent = null;
            if (hasBody)
            {
                if (isStreamingRequest)
                {
                    DisableMinRequestBodyDataRateAndMaxRequestBodySize(request.HttpContext);
                }

                // Note on `autoFlushHttpClientOutgoingStream: isStreamingRequest`:
                // The.NET Core HttpClient stack keeps its own buffers on top of the underlying outgoing connection socket.
                // We flush those buffers down to the socket on every write when this is set,
                // but it does NOT result in calls to flush on the underlying socket.
                // This is necessary because we proxy http2 transparently,
                // and we are deliberately unaware of packet structure used e.g. in gRPC duplex channels.
                // Because the sockets aren't flushed, the perf impact of this choice is expected to be small.
                // Future: It may be wise to set this to true for *all* http2 incoming requests,
                // but for now, out of an abundance of caution, we only do it for requests that look like gRPC.
                requestContent = new StreamCopyHttpContent(
                    source: request.Body,
                    autoFlushHttpClientOutgoingStream: isStreamingRequest,
                    clock: _clock,
                    cancellation: cancellation);
            }

            return(requestContent);
        }
Esempio n. 5
0
 // https://datatracker.ietf.org/doc/html/rfc8441
 // :method = CONNECT
 // :protocol = websocket
 // :scheme = https
 // :path = /chat
 // :authority = server.example.com
 // sec-websocket-protocol = chat, superchat
 // sec-websocket-extensions = permessage-deflate
 // sec-websocket-version = 13
 // origin = http://www.example.com
 public static bool CheckSupportedWebSocketRequestH2(string method, string?protocol, IHeaderDictionary requestHeaders)
 {
     return(HttpMethods.IsConnect(method) &&
            string.Equals(protocol, Constants.Headers.UpgradeWebSocket, StringComparison.OrdinalIgnoreCase) &&
            CheckWebSocketVersion(requestHeaders));
 }