Using unit testing frameworks to detect vulnerabilities

Using unit testing frameworks to detect vulnerabilities

Formulation of the problem

Finding vulnerabilities is a difficult process, and exploiting a vulnerability on multiple targets in different configurations is even more difficult. It is for this reason that there are many frameworks such as Metasploit and various vulnerability scanners such as nuclei. There are so many frameworks and tools, but exploits are still done using simple scripts written in different languages ​​like Python and Go. Why so? It’s all about flexibility.

Writing your own scripts is the best bet, and these days security researchers are happy to write code in Python and Go. However, there are a few problems with simple scripts. For example, when you have hundreds of scripts and you need to find one specific script to exploit one part based on detection. To do this, I’ve seen security researchers write their own tools with simple features like detection and exploit methods, and then run the code in multiple threads. I think you already tried to do this. You may already have such a configuration, or you may be using Nuclei to achieve the same goal. It’s simple and really gets the job done, but is there a better way? Yes it is. Modular test.

What is unit testing and why use it for vulnerability scanning?

Unit testing is a methodology for testing small blocks or individual pieces of code. You can test a small function and see if the result is as expected or not. That’s what vulnerability testing does. Run a small piece of code, check whether the output is correct or not.

The reason you should use unit tests instead of a vulnerability scanner is simple. You get the flexibility to write code in the programming language of your choice, as well as the flexibility of system-level operations. You can also just import and test a small piece of code. You can perform web testing using headless browsers and HTTP client libraries, you can perform binary exploitation using system-level functions, and you can perform static and dynamic code analysis. If you write your own scripts, you’ll have to write a multi-threaded app that can load the scripts as an addon/plugin/extension, which is a time-consuming project in itself. The most important advantage of unit tests for detecting exploits is that you can easily share code with developers for regression testing (the idea of ​​testing code over and over at each iteration so that old code doesn’t fail). Such flexibility is difficult to implement in vulnerability scanners.

So how does unit testing work?

With unit testing, we essentially have code to test. We add a testing framework and write a class or function called “Test” that has methods to postulate some assumptions. You can then use test commands such as npm test, python -m nose2, go test, dotnet test, or any others you can use in your chosen testing framework. If you need to learn or brush up on your unit testing skills, find relevant resources on your chosen language and framework. Here are some good guides:

In the future, I will focus on the xUnit framework, since I have been using it for a while now as a vulnerability scanner.

xUnit as a tool for finding vulnerabilities
Building a vulnerability scanner based on unit testing requires only two things. A method of multi-threaded code execution and a method of logging events. In addition, a fairly simple project in which all the code will be stored. Create a new xunit project with,

dotnet new xunit -o VulnScanner

It should create a new directory with the proj file and the base unit test

UnitTest1.cs.

Now let’s make it multi-threaded. In our project, create a new file

xunit.runner.json

with the following content:

{
    "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
    "parallelizeAssembly": true,
    "maxParallelThreads": 40
}

In the parameter

maxParallelThreads

you can define how many threads you want to use to run the tests. Scheme

$schema

optional and only helps with code completion and highlighting in Visual Studio (Code). Now we need to tell the compiler to copy the configuration to the build directory. Open the proj file and add in the tag

the following:

    <Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />

Your proj file should look like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>

    <IsPackable>false</IsPackable>
    <IsTestProject>true</IsTestProject>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
    <PackageReference Include="xunit" Version="2.4.2" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="coverlet.collector" Version="6.0.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
  </ItemGroup>

</Project>

That’s all. Now you can start writing your code as tests in UnitTest1.cs or any other test file and it will run multithreaded.

dotnet test

Now there is one more thing left to do – log in. It doesn’t matter where you log. You just need to grab the xUnit log output and then use it however you want. The capture is pretty simple and you can still send the test. We intercept the conclusion by implementing

ITestOutputHelper

in the constructor of our test.

using Xunit;
using Xunit.Abstractions;

public class UnitTest1
{
    private readonly ITestOutputHelper output;

    public UnitTest1(ITestOutputHelper output)
    {
        this.output = output;
    }

    [Fact]
    public void Test1()
    {
        string target = Environment.GetEnvironmentVariable("TARGET");
        getallUrls(target);
        Assert.Contains("/haproxy-status", target);
        output.WriteLine("Some logging here");
    }
}

Record at

output

will send your log to the xUnit debug logs, which can be read with the following command.

dotnet test --logger "console;verbosity=detailed";

If you want to log in a different source, you can create

Logger

. However, the logger can introduce a dependency on a third-party source, so it’s better to make it in an abstract class and let your test extend it. When distributing your exploit, you can simply remove the inheritance to remove this logging dependency.

Another problem you may encounter is data entry. But this is quite easy to solve with an environment variable.

using Xunit.Abstractions;

namespace ScanV;

public class UnitTest2: Test
{
    public UnitTest2(ITestOutputHelper output) : base(output) { }

    /// <summary>
    /// Second test
    /// </summary>
    [Fact]
    public void Test2()
    {
        string? target = Environment.GetEnvironmentVariable("TARGET");
        if(target != null) {
            output.WriteLine($"Attacking {target}");
        }
    }
}

In bash shells, you can pass input to the above test using

TARGET="example.com" dotnet test --logger "console;verbosity=detailed";

In PowerShell,

 & { $env:TARGET="example.com"; dotnet test --logger "console;verbosity=detailed"}

and now you can use it as a vulnerability scanner. The only problem is that you’ll need to fail the test to prove the vulnerability exists, and passing the test means there’s no vulnerability. I’ll add some more to this article in the near future, but you get the gist. Happy hunting!

p/s “Old New Year” sale is going on at “Peter” publishing house

Related posts