Qualys 150300: HTTP Request Smuggling on Azure App Service

Qualys recently started reporting 150300: HTTP Request Smuggling against a .NET web application I work on in Azure App Service. Earlier scans had not flagged it. We tested two configuration changes independently, and either one cleared the finding in our tests: an IIS request-filtering rule in web.config, or enabling HTTP/2 in App Service. I ultimately standardized on HTTP/2.

The changes were small, but understanding what they did mattered. A newly reported finding does not establish when the underlying condition began. Qualys has had this QID since 2020. Without comparing the scan evidence and configuration history, I would not attribute its appearance to a particular scanner or platform update.

HTTP request smuggling happens when components handling a request disagree about where it ends. A reverse proxy might treat some bytes as part of one request while the server behind it treats those same bytes as the beginning of another. Depending on the system, that disagreement can let an attacker bypass request checks or interfere with another user's traffic.

In the CL:TE test Qualys documents for QID 150300, the front end follows Content-Length, while the back end follows Transfer-Encoding. The former specifies the body length in bytes; Transfer-Encoding: chunked describes a body sent in chunks. The problem is inconsistent handling of request boundaries across the connection.

Here are the two approaches we tested, and the tradeoffs I would consider before using either one elsewhere.

  1. The first approach used IIS request filtering in web.config. This is the relevant configuration, consolidated into one requestFiltering element:

    <configuration>
      <system.webServer>
        <security>
          <requestFiltering removeServerHeader="true">
            <requestLimits>
              <headerLimits>
                <add header="Transfer-Encoding" sizeLimit="0" />
              </headerLimits>
            </requestLimits>
            <verbs>
              <add verb="TRACE" allowed="false" />
            </verbs>
          </requestFiltering>
        </security>
      </system.webServer>
    </configuration>
    

    The setting relevant to request framing is sizeLimit="0" on Transfer-Encoding. Microsoft's header-limit documentation says that a zero limit effectively denies requests containing that header. It does not remove the header and let the request continue.

    The other two settings do different jobs. removeServerHeader="true" suppresses IIS's server response header, and the verbs entry denies TRACE requests. Those are separate IIS filtering settings; neither resolves disagreement over request lengths. Merge these entries into the existing configuration rather than replacing the whole file or adding a second requestFiltering element. Update any existing matching collection entries instead of duplicating them.

    The advantage is an explicit rule that can live alongside the application in source control. The cost is that it also rejects legitimate requests using chunked transfer encoding if they reach IIS with that header. Uploads and streaming clients need particular attention. This is an IIS-specific restriction, and it acts where IIS applies the filter; it does not establish that every upstream proxy handles requests correctly.

  2. The second approach was enabling HTTP/2 in Azure App Service. This independently cleared the finding in our tests and was the option I chose to standardize on. Microsoft's configuration documentation places the HTTP version setting under Configuration, General settings. Set it to 2.0 and apply the change.

    There are good reasons to enable it beyond this finding. HTTP/2 allows multiple requests to share a connection concurrently and compresses headers to reduce overhead. PSRule for Azure recommends considering HTTP/2 for App Service protocol efficiency. It is a supported platform capability that does not require rewriting the application's handlers.

    The advantage here was a simple platform setting, without adding a blanket ban on the Transfer-Encoding header. Enabling support also leaves HTTP/1.1 available for clients that use it. For ordinary HTTPS applications, that makes HTTP/2 a reasonable default to test and adopt. Keep HTTPS enabled and verify the application's actual clients and integrations; HTTP/2 support and HTTPS enforcement are separate settings.

    The limitation is important: enabling HTTP/2 does not require every connection to use it. Microsoft's App Service protocol documentation distinguishes enabling HTTP/2 at the front end from configuring HTTP/2 proxying to the application. The version setting alone is not proof of HTTP/2 from the client through to the back end.

That distinction matters for request smuggling. HTTP/2 uses explicit framing, but a proxy can translate an incoming HTTP/2 request into HTTP/1.1 before forwarding it. PortSwigger documents how that translation can introduce request-smuggling vulnerabilities. Qualys's recommendation to use HTTP/2 for back-end connections is therefore more specific than turning on HTTP/2 support for clients.

Our A/B tests established that both changes independently resolved the reported finding in our environment. That is a useful result. To carry the same conclusion into another environment, I would check:

  • Retest QID 150300 against the same endpoint with comparable scan settings, and inspect the evidence rather than relying only on its absence from a summary.
  • Verify the negotiated protocol and test any remaining HTTP/1.1 path. A browser showing HTTP/2 does not tell you which protocol a proxy uses behind it.
  • Exercise normal application traffic, especially uploads, streaming requests, authentication, and integrations. Include the CDN, WAF, or other proxies that are actually in the request path.

I chose HTTP/2 because it resolved this finding in our tests and gave us a useful platform standard without the additional IIS restriction. I would make that setting part of deployment configuration so it is reproduced consistently, and keep the scan evidence with the change. That records both what we changed and what we verified.