Fixing OWASP ZAP Baseline Scan Alerts for ASP.NET Core Web Application

Share on facebook
Share on twitter
Share on linkedin

In the last post, Using the OWASP ZAP Baseline Scan GitHub Action, I showed how to add the OWASP ZAP baseline scan to a ASP.NET Core MVC Web Application to Azure pipeline. The baseline scan identified 8 security alerts that are causing the pipeline to fail. In this post I want to show how to resolve these alerts to get the pipeline passing and provide some insight to why OWASP ZAP is identifying alerts on the vanilla ASP.NET Core Web Application template. The complete solution can be found at https://github.com/mikedouglasdev/secure-aspnetcoremvc

One question you might be asking is why wouldn’t Microsoft fix these alerts in the template or in the Azure App Service. While there are a couple items I think the default behavior could be changed, often with anything security related, there is a trade off by implementing all of these alerts, and usually the trade off is performance. So before you blindly implement all of these items, be sure to test the impact. Most of these are good practices regardless but one in particular, cache item I think should be validated before implementing it.

OWASP ZAP Baseline scan action creates an issue with all of the alerts identified. The issue not only identifies what needs to be fixed but also provides traceable work to show that these alerts were resolved.
https://github.com/mikedouglasdev/secure-aspnetcoremvc/issues/1

Below are the alerts that were identified. I will go through how to resolve these issues for the ASP.NET Core Web Application hosted in a Windows based Azure App Service. I tried to fix these in the most ASP.NET Core native way. However, this is hosted in a Windows based PAAS resource, therefore it is using IIS and required a web.config to fix two items.

Incomplete or No Cache-control and Pragma HTTP Header Set

ZAP Report Description:
The cache-control and pragma HTTP header have not been set properly or are missing allowing the browser and proxies to cache content.

ZAP Recommended Solution:
Whenever possible ensure the cache-control HTTP header is set with no-cache, no-store, must-revalidate; and that the pragma HTTP header is set with no-cache.

There are several ways to set no-cache and no-store in the Cache-Control, but I only found two ways to add the must validate setting. One was brute force and adding to the response header, second is setting the CacheControl using the CacheControlHeaderValue class. This will be the default security setting for all requests that go through the ASP.NET Core.

app.Use((context, next) =>
{
    context.Response.GetTypedHeaders().CacheControl =
        new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
        {
            MustRevalidate = true,
            NoCache = true,
            NoStore = true,
                        
        };

The response header will be look like this.

This is probably not the behavior you want for all responses. You can override the default cache settings by adding multiple cache policies and using them in your code.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.CacheProfiles.Add("Default30",
            new CacheProfile()
            {
                Duration = 30
            });
        options.CacheProfiles.Add("NoCache",
            new CacheProfile()
            {
                Location = ResponseCacheLocation.None,
                Duration = 0
            });
                
    }).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
}

Setting the profile can be done in the specific controllers or actions.

[ResponseCache(CacheProfileName = "Default30")]
public IActionResult Index()
{
    return View();
}

Additional information:
https://docs.microsoft.com/en-us/aspnet/core/performance/caching/middleware?view=aspnetcore-3.1
https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-3.1

Content Security Policy (CSP) Header Not Set

ZAP Report Description:
Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft to site defacement or distribution of malware. CSP provides a set of standard HTTP headers that allow website owners to declare approved sources of content that browsers should be allowed to load on that page — covered types are JavaScript, CSS, HTML frames, fonts, images and embeddable objects such as Java applets, ActiveX, audio and video files.

ZAP Recommend Solution:
Ensure that your web server, application server, load balancer, etc. is configured to set the Content-Security-Policy header, to achieve optimal browser support: “Content-Security-Policy” for Chrome 25+, Firefox 23+ and Safari 7+, “X-Content-Security-Policy” for Firefox 4.0+ and Internet Explorer 10+, and “X-WebKit-CSP” for Chrome 14+ and Safari 6+.

Content Security Policy (CSP) allows you to define what resources are allowed to load on a website’s page. The default ASP.NET Core Web Application template is very simple, so it only requires a simple CSP policy. https://content-security-policy.com is a great resource for getting started on your policy. I used the starter policy. However, ZAP alerted me that this didn’t define a policy for frame-ancestors. Since the template doesn’t use frames, I added this with a setting of none.

context.Response.Headers.Add("Content-Security-Policy", "default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';base-uri 'self';form-action 'self';frame-ancestors 'none';");

Building a more advanced CSP policy especially if you are using additional resources like CDNs can be more complicated to create. There is a Nuget package that can help build this policy. See this post for more details.

X-Content-Type-Options Header Missing

ZAP Report Description:
The Anti-MIME-Sniffing header X-Content-Type-Options was not set to ‘nosniff’. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing

Browsers used to try to figure out the MIME types of the request. The header tells the browser to use the specific content type and not try to figure it out. This can mitigate several attacks that you can read more here.

To remove the alert, add the following response header

context.Response.Headers.Add("X-Content-Type-Options", "nosniff");

Server Leaks Version Information via “Server” HTTP Response Header Field

and

Server Leaks Information via “X-Powered-By” HTTP Response Header Field(s)

ZAP Report Description (combined):
The web/application server is leaking version information via the “Server” and “X-Powered-By” HTTP response headers. Access to such information may facilitate attackers identifying other vulnerabilities your web/application server is subject to.

In the Response Headers, you can see we know that this is hosted in IIS and is using ASP.NET. If there are known vulnerabilities or specific attack techniques for either of these, the attacker knows to specific use these. In other words, we don’t need to give the attackers any hints on how to attack our website.

If this was just being hosted using Kestrel, we can do this in code

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(serverOptions =>
            {
                // Set properties and call methods on options
                serverOptions.AddServerHeader = false;
            })
            .UseStartup<Startup>();
        });

Since the App Service is hosted in IIS, we need to include a Web.config.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <remove name="X-Powered-By" />
      </customHeaders>
    </httpProtocol>
    <security>
      <requestFiltering removeServerHeader="true" />
    </security>
  </system.webServer>
</configuration>

Cookie Without Secure Flag

ZAP Report Description:
A cookie has been set without the SameSite attribute, which means that the cookie can be sent as a result of a ‘cross-site’ request. The SameSite attribute is an effective counter measure to cross-site request forgery, cross-site script inclusion, and timing attacks.

and

Cookie Without SameSite Attribute

ZAP Report Description:
A cookie has been set without the SameSite attribute, which means that the cookie can be sent as a result of a ‘cross-site’ request. The SameSite attribute is an effective counter measure to cross-site request forgery, cross-site script inclusion, and timing attacks.

ZAP Report Evidence:
Set-Cookie: ARRAffinity

This cookie is used to manage state and provide a “stick session” like feature so requests always return to the original response if there is session state being managed. Server side session state is not a good practice this creates overhead to manage these sessions. This is something I want to disable. This will remove both of these alerts.

To disable, navigate to the to the Configuration Settings for AARAffinity and turn off the setting and save.

All of the alerts have been resolved and now the build is passing

More to explore