No organization exists in an operational vacuum. It’s a natural law of software development that your world will collide with other projects. It is inevitable to share responsibility between customers, vendors, partners, and the open-source ecosystem. In a past life, I worked with insurance carriers that each had stringent security policies placed on their partners. One of those policies included scanning all production code for known vulnerabilities. In this post, I’ll show you how to scan your system for security vulnerabilities throughout your development lifecycle.

Getting Started

Roslyn, a .NET compiler, provides unprecedented insight into a codebase. The compiler gives developers the ability to understand the syntax and semantics of .NET applications to produce rich code analysis features. Security scanning is a great use case for code analysis tools.

Each codebase may be unique, but as human beings, we tend to make very similar mistakes. Common programmatic mistakes may include:

  • SQL Injection Attacks
  • Weak Randomization
  • Incorrect Request Validation

Understanding we make common mistakes allows security researchers to look for those mistakes in any codebase, and with Roslyn and .NET Core, it gets even more accessible. Let me introduce you to Security Code Scan (SCS) for .NET applications.

To get started scanning for vulnerabilities, add the nuget package to any .NET project.

dotnet add package SecurityCodeScan

You can also add it to your .csproj files.

<PackageReference Include="SecurityCodeScan" Version="3.4.0" PrivateAssets="all" />

What Does Security Code Scan (SCS) Do?

The package offers two styles of operation: During Development and Post-development. In both modes, the package can find security vulnerabilities around SQL injection, cross-site scripting (XSS), cross-site request forgery (CSRF), XML external entity injection (XXE), and much more.

During development, these security scans happen in real-time utilizing Roslyn analyzers to inform the developer of security mistakes. I’ll demonstrate with some screenshots later in the post. Full-solution analysis can be expensive, so you may also disable this feature and use the next mode.

Post-development, security scans happen at build time. With every compilation, an MSBuild task runs the same analyzers to determine issues with the codebase. This approach is excellent for teams running continuous integration builds as it can spot problems before the code finds its way to a production environment.

In addition to helping developers, SCS has an audit mode. The audit mode allows you to set expectations for your security scan. It may not be enough to find issues, but it may also be essential to mitigate changes in configuration and security practices. In audit mode, you can assert what your configuration should be. Audit mode is an advanced feature that doesn’t have a lot of documentation and is turned off by default.

Catching Common Security Problems

The documentation for SCS is excellent at explaining what each of the 36 rules does when scanning your project. In this post, I have picked three common ones you may likely find in your projects. The folks at SCS have also been kind enough to include a very vulnerable sample to test their analyzers (although it is a bit outdated targeting .NET 3.5).

Path Traversal (SCS0018)

When dealing with the filesystem, developers can make some severe mistakes in overexposing files and giving attackers access to restricted areas. Take a look at this example of an ASP.NET Core web method that allows users to pass in dangerous inputs.

[HttpGet]
public ActionResult Download(string fileName)
{
    byte[] fileBytes = System.IO.File.ReadAllBytes(Path.Combine(environment.ContentRootPath, fileName));
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

As you can see in JetBrains Rider, the analyzer immediately catches the issue of path traversal.

scs path traversal

To fix the issue, we need to stop the request immediately if we find any invalid path characters.

[HttpGet]
[SuppressMessage("Security", "SCS0018")]
public ActionResult Download(string fileName)
{
    if (fileName?.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
        return BadRequest();
    
    var path = Path.Combine(environment.ContentRootPath, fileName);
    byte[] fileBytes = System.IO.File.ReadAllBytes(path);
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

In this example, once we fix the issue, we need to suppress the warning, as the analyzer still thinks there is an issue.

SQL Injection With Entity Framework (SCS0035)

SQL Injection attacks still happen, even in 2020. Using an Object Relation Mapper doesn’t guarantee you’ll be protected either. In this example, we dropped to ADO.NET to execute a raw SQL query with Entity Framework 6.

[HttpGet]
public ActionResult GetResults(string input)
{
    var db = new Database();
    db.Database.ExecuteSqlCommand("SELECT * FROM Users WHERE username = '" + input + "' and role='user'");
    return Ok();
}

scs sql injection

As you can see, we are concatenating unsanitized user input with our SQL query. The fix is to use SQL Parameters.

[HttpGet]
public ActionResult GetResults(string input)
{
    var db = new Database();
    var cmd = "SELECT * FROM Users WHERE username = @username and role='user'";
    db.Database.ExecuteSqlCommand(
        cmd,
        new SqlParameter("@username", input));
    return Ok();
}

Weak Random Generator (SCS0005)

Depending on your use case, you may not want robust randomization. In most cases, it doesn’t hurt to go the extra mile an implement stronger randomization.

I’ve written a great post about random numbers if you’re interested in randomization in .NET Core.

SCS will find uses of the Random class and suggest you do not use it. Why?

The random numbers generated [by Random] could be predicted. –SCS

Use the RandomNumberGenerator class instead, which provides for cryptographically random numbers.

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
    var rng = new Random();
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
}

scs weak random

To fix my example, I create a local function next that utilizes the RandomNumberGenerator class.

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
    int next(int min, int max)
    {
        return RandomNumberGenerator.GetInt32(min, max);
    }
    
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = next(-20, 55),
            Summary = Summaries[next(0, Summaries.Length)]
        })
        .ToArray();
}

Conclusion

As developers, we produce a lot of value for ourselves, community, and businesses. In our rush to enable our users, we may also inadvertently allow bad-faith actors to access our systems in unintended ways. SCS is a low-effort method to protect yourself from common programming mistakes. I look at SCS as one of the first-line defenders against obvious security issues.

This tool is excellent, but it can only do so much utilizing the Roslyn analyzer. It is up to you to think through your scenarios and model potential attacks. This tool is supplemental to information security staff members, not a substitute for them. Working with experienced developers, security scanning tools, and information security staff within a DevOps culture can help deliver more secure software.

I hope you found this post helpful and give Security Code Scan a try and let me know what you think.