Using Webhint and NWebSec to improve security a Asp.Net Core 2.1+ Web Application

August 27, 2018    Asp.Net Core Asp.Net MVC Web Development Azure DevOps Security .Net

Using Webhint and NWebSec to improve security a Asp.Net Core 2.1+ Web Application

I recently wrote about Security and performance improvements in your web.config for Asp.Net MVC “Classic” . Since then, our team has gone on a journey to convert to Asp.Net Core 2.1. It took longer than we expected and I’m planning on having a reference blog to show you what we ran into and how we got around it. After the conversion, I ran the Webhint scanner from JS Foundation and Security Headers and found that my security header and caching changes in the web.config aren’t getting applied.

Note: As of August 7th, 2018, Sonarwhal is now “WebHint.io .

TLDR; I’m creating a new project with dotnet new mvc, running Webhint.io against it, then showing how to remediate the issues with NWebSec and other .Net Core middleware. See my source code on Github .

Creating the project

Run dotnet new mvc in a command line (I have version 2.1.4 at the time of writting, but the version doesn’t matter for this context.

Running Webhint against local host

There’s a CLI for Webhint and the latest version is 3.2.1 at this moment. Here are the instructions from the Webhint page.

npm install -g hint

Then I fired up my application and ran hint https://localhost:5000 Here’s my initial results:

Webhint init

Visit to get info on the config. Run npm create hintrc and then hint ... again and you’ll be rolling along.

Note: Webhint creates an html report in the hint_report folder it creates from the folder you ran the cli from. This is the same report as if you ran it from their website. This is a quick way to get links to their documentation. It’s created when you use the default setup.

{
    "extends": [
        "web-recommended"
    ]
}

Time to Move to Middleware

Use NWebSec Middleware to improve security . DamienBod has provided this article and a linked GitHub project that I’m going to use as a reference.

dotnet add package NWebsec.AspNetCore.Middleware does the trick.

Just follow Damien’s article, it lays it out really well with good references.

Here’s what my Chrome Dev Tools Network tabs shows for the headers on the main page before I added in the security changes:

Security Headers before

There are now a lot more security related headers in the response

Security Headers after

Check the results with Webhint CLI

I ran hint https://localhost:44738 again after following Damien’s advice ( see my commit ). There’s good improvements here, but only by 1 error (no-html-only-headers is one less) according to Webhint. Note: Remember that I’m running locally and in development mode, so I’m not worried about the ssl errors at this moment.

  • axe 1 warning
  • no-friendly-error-pages 1 error
  • no-vulnerable-javascript-libraries 1 warning
  • ssllabs 1 error
  • no-bom 2 errors
  • html-checker 2 errors
  • sri 5 errors
  • x-content-type-options 5 errors
  • no-html-only-headers 9 errors
  • http-cache 10 errors
  • content-type 11 errors
  • http-compression 20 errors
  • × Found a total of 66 errors and 3 warnings

Note: you also should compare the Lighthouse before and after. I wrote about this in my last security article .

More details from Webhint

Let’s get more details using the @hint/formatter-codeframe. We could also look at the details from the html report (which gets overwritten every time you run the command), but let’s see it in the console output.

There is a global config file C:\Users\{user}\AppData\Roaming\npm\node_modules\hint\.hintrc, but you should change the .hintrc file that is in the same directory as your CLI command. I looked at their docs and changed my .hintrc to use the recommended from their docs. Note: you can also specify the formatter in-line hint -f codeframe https://wwww.localhost:5001.

npm install @hint/formatter-codeframe -g

{
    "extends": ["web-recommended"],
    "formatters": ["codeframe"]
}

Webhint post codeframe formatter

Note: Then I tried ‘stylish’, which hung in the finishing... state for awhile and had a blank output. So I’ll stick with the codeframe style. That gives a lot of details we’d see in the website run scan. I put in a quick suggestion on improving their docs while I was at it.

HINT: --debug is helpful if you get a blank output hint https://localhost:44738 --debug showed a lot of errors with ‘ECONNREFUSED’ after I came back to this a few days later. I had to switch to the non-http address to get around that error (I’m not sure why and moved on).

The excel formatter creates a sheet for each resource, there’s a lot of good detail in there individually.

npm install -g @hint/formatter-json looks to be another nice option

Let’s make more improvements in the areas flagged by Webhint

Security

X-Content-Type-Options

“x-content-type-options requires that all scripts and stylesheets are served with the X-Content-Type-Options: nosniff HTTP response header.” NWebSec reference

Learn how to configure with NWebSec from their docs.

These are fixed by the default. X-Content-Type-Options errors from Webhint

However, there are a few more flagged that we have to add some customization to fix.

Example 1 - main page

Response should not include unneeded 'x-content-type-options' header. https://localhost:5001/

Example 2 - svg files

Response should not include unneeded 'x-content-type-options' header. https://localhost:5001/images/banner1.svg:78:13

Example 3 - ico file

Response should not include unneeded 'x-content-type-options' header. https://localhost:5001/favicon.ico

I’ve created an issue on the NWebSec page to see if there is a proper way to do this or they can provide configuration options. I’ll update this when I learn the right way.

Thanks to espenrl , I have an answer. “UseWhen is a builtin API of ASP.NET Core that allows a middleware to be run conditionally. IsCssOrJsFile is my own method. NOTE: MapWhen and UseWhen have very different behavior.”

builder.UseWhen(
                ctx => IsCssOrJsFile(ctx.Request.Path),
                app => app.UseXContentTypeOptions());

SRI

nWebSec reference documentation on SRI.

I’m hoping to add a webpack SRI article in the future. I’ll add a link at that time. Sneak preview: I’m going to use the Waysact library .

Another approach and more info about subresource integrity .

Rule-sri: Script src and integrity hashes
External Example - adsbygoogle.js

Webhint reports The hash algorithm "sha256" doesn't meet the baseline "sha384" https://code.jquery.com/jquery-3.3.1.min.js:441:5

Let’s fix that hash: <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT=" crossorigin="anonymous"></script>

We can use SRI Hash Generator to get a hash.

pagead2.googlesyndication.com/pagead/js/adsbygoogle.js issues, StackOverflow suggests using cloudflare instead .

I put up a question on StackOverflow .

Webhint report on Google ads

As far as I see it, the only way to fix SRI for files from a CDN is if the CDN provides the hash that meets the requirements.

Dealing with CDN’s

Let’s use https://code.jquery.com/jquery-3.3.1.min.js instead of having it packaged locally.

"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js is another one we have to deal with a lot.

Strict-Transport-Security

Strict-Transport-Security “(HSTS) is an opt-in security enhancement that is specified by a web application through the use of a special response header.” Here’s the NWebSec specific docs . “This creates an opportunity for a man-in-the-middle attack. The redirect could be exploited to direct visitors to a malicious site instead of the secure version of the original site.”

     app.UseHsts(options => options.MaxAge(days: 30).IncludeSubdomains());

I’m getting warnings, so I’ll assume that it’s my local non-HSTS setup (even after moving `app.UseHsts() inside of development in my Startup.cs) and check it after I deploy. “Strict-Transport-Security: The connection to the site is untrustworthy, so the specified header was ignored.”

Webhint reported “Webhint ‘strict-transport-security’ header ‘max-age’ value should be more than 10886400”, so I changed the MaxAge(days: 30) from Damien to MaxAge(days: 180).

Content Security Policy

NWebSec docs say “(CSP) provides a safety net for injection attacks by specifying a whitelist from where various content in a webpage can be loaded from.” This is more involved and requires more reading to understand and setup correctly.

    app.UseCsp(opts => opts
...

Here&rsquo;s the official documentation for NWebSec

We’ll whitelist our CDN files and allow local connections at a global level (“define your baseline policy in web.config, CSP middleware or through global filters”)

 services.AddMvc(opts =>
    {
        opts.Filters.Add(typeof(CspAttribute));
        opts.Filters.Add(new CspDefaultSrcAttribute { Self = true });  

Damien has more info about NWebSec and CDNs . First, add integrity hashes to your <script src in your html.

Web.config still needs a few things

It is recommended not to broadcast what server technology that is in use. We don’t want to give hackers any free information. IIS exposes the version of MVC and IIS version (or Kestrel) by default.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <httpRuntime enableVersionHeader="false"/>
  </system.web>
  <system.webServer>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" disableStartUpErrorPage="true" />
    <security>
      <requestFiltering removeServerHeader="true" />
    </security>
    <httpProtocol>
      <customHeaders>
        <remove name="X-Powered-By"/>
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

Troy Hunt talks about removing server response headers .

rel=noopener

Mathias Bynens has good information on rel=noopener .

To fix this, manually add the rel="noopener" to your links in html. for example: <a href="somewhere.html" target="_blank" rel="noopener"><b>Click me!!1 (now with <code>rel=noopener</code>)</b></a>

More options for testing your sites

Automate and Integrate into a VSTS Release

We could integrate this into our builds using the CLI, but I’ve put in a feature request for a VSTS build task in the marketplace that would standardize the approach.

Conclusions

We can use Webhint to “lint” our sites for missing industry standard recommendations. I believe that as web developers we should use these tools to ensure we are as secure as we can be (of course more security analysis is needed, but this is a great starting baseline). The tools also teach us about security and other issues.

The cli can give us quick feedback locally and in a build against a more production like environment.

Go out and test your websites with Webhint and see where you can improve today!

I plan a follow up article on how to improve the Interoperability and Performance flags from Webhint soon.

Notes

I wrote about Lighthouse in my last security article . That’s another great tool to run often.

You can create custom hints to ensure custom rules for your organization as well!



Watch the Story for Good News
I gladly accept BTC Lightning Network tips at [email protected]

Please consider using Brave and adding me to your BAT payment ledger. Then you won't have to see ads! (when I get to $100 in Google Ads for a payout, I pledge to turn off ads)

Use Brave

Also check out my Resources Page for referrals that would help me.


Swan logo
Use Swan Bitcoin to onramp with low fees and automatic daily cost averaging and get $10 in BTC when you sign up.