Skip to content

Commit

Permalink
Merge pull request #29 from AikidoSec/installation-frameworks-drivers…
Browse files Browse the repository at this point in the history
…-and-benchmark

Update README.md to enhance installation instructions and supported f…
  • Loading branch information
Yannis-S-Standaert authored Jan 21, 2025
2 parents 926a58c + 89f1ce7 commit a8762db
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 52 deletions.
52 changes: 29 additions & 23 deletions Aikido.Zen.Core/Helpers/AgentInfoHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Aikido.Zen.Core.Models;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("Aikido.Zen.Tests")]
namespace Aikido.Zen.Core.Helpers
{
Expand All @@ -9,34 +10,39 @@ namespace Aikido.Zen.Core.Helpers
/// </summary>
internal class AgentInfoHelper
{
// Cache the AgentInfo object to avoid repeated creation
private static readonly AgentInfo _cachedAgentInfo = new AgentInfo
{
Hostname = Environment.MachineName,
Os = new Os
{
Version = Environment.OSVersion.VersionString,
Name = Environment.OSVersion.Platform.ToString()
},
Platform = new Platform
{
Version = Environment.Version.ToString(),
Arch = RuntimeInformation.ProcessArchitecture.ToString()
},
IpAddress = IPHelper.Server,
Library = "firewall-dotnet",
Version = typeof(AgentInfoHelper).Assembly.GetName().Version.ToString(),
// Determine if running in a serverless environment
Serverless = Environment.GetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME") != null || Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID") != null
};

/// <summary>
/// Gets information about the current runtime environment including OS, platform, and configuration details.
/// </summary>
/// <returns>An AgentInfo object containing the environment information.</returns>
public static AgentInfo GetInfo()
{
return new AgentInfo
{
Hostname = Environment.MachineName,
Os = new Os
{
Version = Environment.OSVersion.VersionString,
Name = Environment.OSVersion.Platform.ToString()
},
Platform = new Platform
{
// version
Version = Environment.Version.ToString(),
// core or framework
Arch = Environment.Version.Major >= 5 ? "core" : "framework"
},
IpAddress = IPHelper.Server,
DryMode = EnvironmentHelper.DryMode,
// check if lambda or azure function
Serverless = Environment.GetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME") != null || Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID") != null,
Library = "firewall-dotnet",
Version = "1.0.0"
};
// Update only the fields that can change
_cachedAgentInfo.DryMode = EnvironmentHelper.DryMode;
_cachedAgentInfo.Serverless = Environment.GetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME") != null || Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID") != null;


return _cachedAgentInfo;
}
}
}
}
29 changes: 14 additions & 15 deletions Aikido.Zen.DotNetCore/Middleware/ContextMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,48 +18,47 @@ public ContextMiddleware(IEnumerable<EndpointDataSource> endpointSources)

public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next)
{
// this will be used to check for attacks
// Convert headers and query parameters to dictionaries once
var queryDictionary = httpContext.Request.Query.ToDictionary(q => q.Key, q => q.Value.ToArray());
var headersDictionary = httpContext.Request.Headers.ToDictionary(h => h.Key, h => h.Value.ToArray());

var context = new Context
{
Url = httpContext.Request.Path.ToString(),
Method = httpContext.Request.Method,
Query = httpContext.Request.Query.ToDictionary(q => q.Key, q => q.Value.ToArray()),
Headers = httpContext.Request.Headers
.ToDictionary(h => h.Key, h => h.Value.ToArray()),
RemoteAddress = httpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty,
Query = queryDictionary,
Headers = headersDictionary,
RemoteAddress = httpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty, // no need to use X-FORWARDED-FOR, .NET Core already handles this
Cookies = httpContext.Request.Cookies.ToDictionary(c => c.Key, c => c.Value),
UserAgent = httpContext.Request.Headers["User-Agent"].ToString(),
UserAgent = headersDictionary.TryGetValue("User-Agent", out var userAgent) ? userAgent.FirstOrDefault() : string.Empty,
Source = Environment.Version.Major >= 5 ? "DotNetCore" : "DotNetFramework",
Route = GetRoute(httpContext),
};

// no need to use X-FORWARDED-FOR, .NET Core already handles this
var clientIp = httpContext.Connection.RemoteIpAddress?.ToString();
// Add request information to the agent, which will collect routes, users and stats
// every x minutes, this information will be sent to the Zen server as a heartbeat event, and the collected info will be cleared
Agent.Instance.CaptureInboundRequest(context.User, httpContext.Request.Path, context.Method, clientIp);
Agent.Instance.CaptureInboundRequest(context.User, context.Url, context.Method, context.RemoteAddress);

try
{
var request = httpContext.Request;
// allow the body to be read multiple times
request.EnableBuffering();

var parsedUserInput = await HttpHelper.ReadAndFlattenHttpDataAsync(
queryParams: context.Query.ToDictionary(h => h.Key, h => string.Join(',', h.Value)),
headers: context.Headers.ToDictionary(h => h.Key, h => string.Join(',', h.Value)),
queryParams: queryDictionary.ToDictionary(h => h.Key, h => string.Join(',', h.Value)),
headers: headersDictionary.ToDictionary(h => h.Key, h => string.Join(',', h.Value)),
cookies: context.Cookies,
body: request.Body,
contentType: request.ContentType,
contentLength: request.ContentLength ?? 0
);

context.ParsedUserInput = parsedUserInput;
context.Body = request.Body;

}
catch (Exception e)
{
var message = e.Message;
var trace = e.StackTrace;
// Log the exception details if necessary
throw;
}

Expand Down
9 changes: 5 additions & 4 deletions Aikido.Zen.Test/AgentInfoHelperTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Runtime.InteropServices;
using Aikido.Zen.Core.Helpers;

namespace Aikido.Zen.Test
Expand Down Expand Up @@ -38,7 +39,7 @@ public void GetInfo_ShouldReturnValidAgentInfo()
// Basic properties
Assert.That(agentInfo.Hostname, Is.EqualTo(Environment.MachineName));
Assert.That(agentInfo.Library, Is.EqualTo("firewall-dotnet"));
Assert.That(agentInfo.Version, Is.EqualTo("1.0.0"));
Assert.That(agentInfo.Version, Is.EqualTo(typeof(AgentInfoHelper).Assembly.GetName().Version!.ToString()));
Assert.That(agentInfo.IpAddress, Is.Not.Null);

// OS Info
Expand All @@ -47,7 +48,7 @@ public void GetInfo_ShouldReturnValidAgentInfo()

// Platform Info
Assert.That(agentInfo.Platform.Version, Is.EqualTo(Environment.Version.ToString()));
Assert.That(agentInfo.Platform.Arch, Is.EqualTo(Environment.Version.Major >= 5 ? "core" : "framework"));
Assert.That(agentInfo.Platform.Arch, Is.EqualTo(RuntimeInformation.ProcessArchitecture.ToString()));
});
}

Expand Down Expand Up @@ -101,7 +102,7 @@ public void GetInfo_PlatformArchitecture_Core()
var agentInfo = AgentInfoHelper.GetInfo();

// Assert
Assert.That(agentInfo.Platform.Arch, Is.EqualTo("core"));
Assert.That(agentInfo.Platform.Arch, Is.EqualTo(RuntimeInformation.ProcessArchitecture.ToString()));
}
}

Expand All @@ -115,7 +116,7 @@ public void GetInfo_PlatformArchitecture_Framework()
var agentInfo = AgentInfoHelper.GetInfo();

// Assert
Assert.That(agentInfo.Platform.Arch, Is.EqualTo("framework"));
Assert.That(agentInfo.Platform.Arch, Is.EqualTo(RuntimeInformation.ProcessArchitecture.ToString()));
}
}

Expand Down
147 changes: 137 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,161 @@ Zen is an embedded Web Application Firewall that autonomously protects your .NET

Zen protects your .NET apps by preventing user input containing dangerous strings, which allow SQL injections. It runs on the same server as your .NET app for easy installation and zero maintenance.

Zen for .NET currently supports onwards of .NET xx. The latest tested version is .NET xx.
Zen for .NET currently supports onwards of .NET 4.6. The latest tested version is .NET 8.0.

## Features

Zen will autonomously protect your .NET applications from the inside against:

* 🛡️ [NoSQL injection attacks](https://www.aikido.dev/blog/web-application-security-vulnerabilities)
* 🛡️ [SQL injection attacks](https://www.aikido.dev/blog/the-state-of-sql-injections)
* 🛡️ [Command injection attacks](https://www.aikido.dev/blog/command-injection-in-2024-unpacked)
* 🛡️ [Path traversal attacks](https://owasp.org/www-community/attacks/Path_Traversal)
* 🛡️ [Server-side request forgery (SSRF)](./docs/ssrf.md)
* 🛡️ [Path traversal attacks](https://www.aikido.dev/blog/path-traversal-in-2024-the-year-unpacked)
* 🚧 [Command injection attacks](https://www.aikido.dev/blog/command-injection-in-2024-unpacked)
* 🚧 [Server-side request forgery (SSRF)](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery)

Zen operates autonomously on the same server as your .NET app to:

* ✅ Secure your app like a classic web application firewall (WAF), but with none of the infrastructure or cost.
* ✅ Rate limit specific API endpoints by IP or by user
* ✅ Allow you to block specific users manually
* ✅ Auto-generate API specifications
* ✅ Allow you to block traffic by country
* ✅ Allow you to block bots and AI scrapers
* ✅ Allow you to allow traffic by ip per endpoint


## Supported libraries and frameworks

### Web frameworks
* ✅ TODO
* ✅ ASP.NET Core 6.0
* ✅ ASP.NET Core 7.0
* ✅ ASP.NET Core 8.0
* ✅ ASP.NET Framework 4.6.x
* ✅ ASP.NET Framework 4.7.x
* ✅ ASP.NET Framework 4.8.x

### Database drivers
* ✅ TODO
* ✅ Microsoft.Data.SqlClient
* ✅ System.Data.SqlClient
* ✅ Microsoft.Data.Sqlite
* ✅ MySql.Data.MySqlClient
* ✅ MySqlConnector
* ✅ Npgsql
* ✅ MySqlX

## Installation

TODO
### .NET Core

- Install the package from NuGet:

``` shell
dotnet add package Zen.Aikido
```

- Add the following to your `appsettings.json` file: (use secrets manager to store the API key)

``` json
{
"Aikido": {
"AikidoToken": "your-api-key"
}
}
```

- or add it as an environment variable

``` shell
AIKIDO_TOKEN=<YOUR-TOKEN-HERE>
```

If you are using a startup class, you can add the following to your `Startup.cs` file:

``` csharp
public void ConfigureServices(IServiceCollection services)
{
// other services
services.AddZenFirewall(Configuration);
// other services
}

public void Configure(IApplicationBuilder app)
{
// other middleware
app.UseZenFirewall(); // place this after userouting, or after authorization, but high enough in the pipeline to catch all requests
// other middleware
}
```

You can also set the user in your custom middleware, if you would like to block users by their identity.

``` csharp
// add routing
.UseRouting()
// authorize users
.Use((context, next) =>
{
var id = context.User?.Identity?.Name ?? "test";
var name = context.User?.Identity?.Name ?? "Anonymous";
if (!string.IsNullOrEmpty(id))
Zen.SetUser(id, name, context);
return next();
})
// add Zen middleware
.UseZenFireWall()
```

### .NET Framework

To add the Aikido token in the Web.config file, follow these steps:

1. Open your `Web.config` file.
2. Locate the `<appSettings>` section.
3. Add the following key-value pair within the `<appSettings>` section:

``` xml
<add key="Aikido:AikidoToken" value="your-api-key" />
```

in your global.asax.cs file, add the following:

``` csharp
protected void Application_Start()
{
// other code
Zen.Start();
}
```

if you are using OWIN, you can add the following to your `Startup.cs` file:

``` csharp
public void Configuration(IAppBuilder app)
{
// other code
Zen.Start();
}
```

If you would like to block users by their identity, you can pass in a function to set the user, in your global.asax.cs file.

``` csharp
public void Application_Start()
{
// other code
Zen.SetUser(context => new User(context.User.Identity.Name, context.User.Identity.Name));
Zen.Start();
}
```

Or if you are using OWIN, you can add the following to your `Startup.cs` file:

``` csharp
public void Configuration(IAppBuilder app)
{
// other code
Zen.SetUser(context => new User(context.User.Identity.Name, context.User.Identity.Name));
Zen.Start();
}
```

## Reporting to your Aikido Security dashboard

Expand Down Expand Up @@ -89,7 +216,7 @@ address: [email protected] or create an account at https://app.aikido.dev.

## Performance

TODO
![Under construction](https://img.icons8.com/emoji/20/000000/construction-emoji.png) Under construction ![Under construction](https://img.icons8.com/emoji/20/000000/construction-emoji.png)

## Code of Conduct

Expand Down

0 comments on commit a8762db

Please sign in to comment.