Skip to content

Commit

Permalink
Merge pull request #31 from AikidoSec/dependency-cleanups-and-improve…
Browse files Browse the repository at this point in the history
…ments

Dependency cleanups and improvements
  • Loading branch information
Yannis-S-Standaert authored Jan 21, 2025
2 parents b7288f9 + b7bb414 commit 926a58c
Show file tree
Hide file tree
Showing 16 changed files with 301 additions and 264 deletions.
1 change: 0 additions & 1 deletion Aikido.Zen.Core/Agent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
using Aikido.Zen.Core.Models.Events;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NetTools;

[assembly: InternalsVisibleTo("Aikido.Zen.Tests")]
namespace Aikido.Zen.Core
Expand Down
3 changes: 1 addition & 2 deletions Aikido.Zen.Core/Aikido.Zen.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@
</Target>-->

<ItemGroup>
<PackageReference Include="IPAddressRange" Version="6.1.0" />
<PackageReference Include="Lib.Harmony" Version="2.3.3" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.1" />
</ItemGroup>
</Project>
25 changes: 0 additions & 25 deletions Aikido.Zen.Core/Helpers/IPHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Net;
using System.Linq;
using NetTools;
using System.Collections.Generic;
using System;

Expand Down Expand Up @@ -28,30 +27,6 @@ public static bool IsValidIp(string ip)
return IPAddress.TryParse(ip, out _);
}

/// <summary>
/// Check if an IP address is in a subnet
/// Example:
/// Mask: 192.168.1.0/24
/// Address: 192.168.1.1
/// </summary>
/// <param name="address"></param>
/// <param name="subnet"></param>
/// <returns></returns>
public static bool IsInSubnet(IPAddress address, IPAddressRange subnet)
{
return subnet.Contains(address);
}

/// <summary>
/// Checks if a given ip string is a subnet
/// </summary>
/// <param name="ip"></param>
/// <returns></returns>
public static bool IsSubnet(string ip)
{
return ip.Contains("/") && IPAddressRange.TryParse(ip, out _);
}

/// <summary>
/// Converts an IP address range to a list of CIDR strings.
/// </summary>
Expand Down
68 changes: 68 additions & 0 deletions Aikido.Zen.Core/Helpers/ReflectionHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Aikido.Zen.Core.Helpers
{
/// <summary>
/// Helper class for reflection-related operations.
/// </summary>
public static class ReflectionHelper
{
private static IDictionary<string, Type> _types;
private static IDictionary<string, Assembly> _assemblies;

static ReflectionHelper()
{
_types = new Dictionary<string, Type>();
_assemblies = new Dictionary<string, Assembly>();
}

/// <summary>
/// Attempts to load an assembly, get a type from the loaded assembly, and then get a method from the type.
/// </summary>
/// <param name="assemblyName">The name of the assembly to load.</param>
/// <param name="typeName">The name of the type to get from the assembly.</param>
/// <param name="methodName">The name of the method to get from the type.</param>
/// <param name="parameterTypeNames">The names of the parameter types for the method.</param>
/// <returns>The MethodInfo of the specified method, or null if not found.</returns>
public static MethodInfo GetMethodFromAssembly(string assemblyName, string typeName, string methodName, params string[] parameterTypeNames)
{
// Attempt to load the assembly
// Attempt to get the assembly from the cache, if not found, load it
if (!_assemblies.TryGetValue(assemblyName, out var assembly))
{
assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == assemblyName);
// If the assembly is not loaded, and the assembly path exists, load it
if (File.Exists($"{assemblyName}.dll") && assembly == null)
{
assembly = Assembly.LoadFrom($"{assemblyName}.dll");
}
if (assembly == null) return null;
_assemblies[assemblyName] = assembly;
}

// Attempt to get the type from the cache, if not found, get it from the loaded assembly
var typeKey = $"{assemblyName}.{typeName}";
if (!_types.TryGetValue(typeKey, out var type))
{
type = assembly.ExportedTypes.FirstOrDefault(t => t.Name == typeName || t.FullName == typeName);
if (type == null) return null;
_types[typeKey] = type;
}

// Use reflection to get the method
var method = type.GetMethods().FirstOrDefault(m => m.Name == methodName && m.GetParameters().All(p => parameterTypeNames.Any(ptn => ptn == p.ParameterType.FullName)));
return method;
}

public static void ClearCache()
{
_types.Clear();
_assemblies.Clear();
}
}
}
1 change: 0 additions & 1 deletion Aikido.Zen.Core/Models/AgentContext.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Aikido.Zen.Core.Helpers;
using Aikido.Zen.Core.Models.Ip;
using Microsoft.AspNetCore.Http;
using NetTools;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down
1 change: 0 additions & 1 deletion Aikido.Zen.Core/Models/Ip/Blocklist.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Net;
using System.Threading;
using Aikido.Zen.Core.Helpers;
using NetTools;

namespace Aikido.Zen.Core.Models.Ip
{
Expand Down
58 changes: 30 additions & 28 deletions Aikido.Zen.Core/Patches/HttpClientPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,49 @@ namespace Aikido.Zen.Core.Patches
{
internal static class HttpClientPatches
{
/// <summary>
/// Applies patches to HttpClient methods using Harmony and reflection.
/// </summary>
/// <param name="harmony">The Harmony instance used for patching.</param>
public static void ApplyPatches(Harmony harmony)
{
var asyncMethod = AccessTools.Method(typeof(HttpClient), "SendAsync", new[] {
typeof(HttpRequestMessage),
typeof(HttpCompletionOption),
typeof(CancellationToken)
});
var syncMethod = AccessTools.Method(typeof(HttpClient), "Send", new[] {
typeof(HttpRequestMessage),
typeof(CancellationToken)
});
try
{
if (asyncMethod != null && !asyncMethod.IsAbstract)
{
var patchMethod = new HarmonyMethod(typeof(HttpClientPatches).GetMethod(nameof(CaptureRequest), BindingFlags.Static | BindingFlags.NonPublic));
harmony.Patch(asyncMethod, patchMethod);
}
// Use reflection to get the methods dynamically
PatchMethod(harmony, "System.Net.Http", "HttpClient", "SendAsync", "System.Net.Http.HttpRequestMessage", "System.Net.Http.HttpCompletionOption", "System.Threading.CancellationToken");
PatchMethod(harmony, "System.Net.Http", "HttpClient", "Send", "System.Net.Http.HttpRequestMessage", "System.Threading.CancellationToken");
}

if (syncMethod != null && !syncMethod.IsAbstract)
{
harmony.Patch(syncMethod, new HarmonyMethod(typeof(HttpClientPatches).GetMethod(nameof(CaptureRequest), BindingFlags.Static | BindingFlags.NonPublic)));
}
}
catch (Exception)
/// <summary>
/// Patches a method using Harmony by dynamically retrieving it via reflection.
/// </summary>
/// <param name="harmony">The Harmony instance used for patching.</param>
/// <param name="assemblyName">The name of the assembly containing the type.</param>
/// <param name="typeName">The name of the type containing the method.</param>
/// <param name="methodName">The name of the method to patch.</param>
/// <param name="parameterTypeNames">The names of the parameter types for the method.</param>
private static void PatchMethod(Harmony harmony, string assemblyName, string typeName, string methodName, params string[] parameterTypeNames)
{
var method = ReflectionHelper.GetMethodFromAssembly(assemblyName, typeName, methodName, parameterTypeNames);
if (method != null && !method.IsAbstract)
{
// continue
var patchMethod = new HarmonyMethod(typeof(HttpClientPatches).GetMethod(nameof(CaptureRequest), BindingFlags.Static | BindingFlags.NonPublic));
harmony.Patch(method, patchMethod);
}

}

internal static bool CaptureRequest(
HttpRequestMessage request,
HttpClient __instance)
/// <summary>
/// Callback method executed before the original HttpClient method is executed.
/// </summary>
/// <param name="request">The HttpRequestMessage being sent.</param>
/// <param name="__instance">The instance of HttpClient being used.</param>
/// <returns>True if the original method should continue execution; otherwise, false.</returns>
internal static bool CaptureRequest(HttpRequestMessage request, HttpClient __instance)
{
var uri = __instance.BaseAddress == null
? request.RequestUri
: request.RequestUri == null
? __instance.BaseAddress
: new Uri(__instance.BaseAddress, request.RequestUri);

var (hostname, port) = UriHelper.ExtractHost(uri);
if (hostname.EndsWith("aikido.dev"))
return true;
Expand Down
14 changes: 3 additions & 11 deletions Aikido.Zen.DotNetCore/Aikido.Zen.DotNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0;net8.0;</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<Nullable>disable</Nullable>
<PackageOutputPath>..\nupkgs</PackageOutputPath>
<AssemblyVersion></AssemblyVersion>
<FileVersion></FileVersion>
<AssemblyVersion>0.0.1</AssemblyVersion>
<FileVersion>0.0.1</FileVersion>
<UserSecretsId>97bd5157-97c6-4825-833a-933ce895cbc0</UserSecretsId>
</PropertyGroup>

Expand All @@ -16,20 +16,12 @@
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Routing.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.0" Condition="'$(TargetFramework)' == 'net6.0'" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.0" Condition="'$(TargetFramework)' == 'net7.0'" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.0" Condition="'$(TargetFramework)' == 'net8.0'" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" Condition="'$(TargetFramework)' == 'net6.0'" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" Condition="'$(TargetFramework)' == 'net7.0'" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" Condition="'$(TargetFramework)' == 'net8.0'" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" Condition="'$(TargetFramework)' == 'net6.0'" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0" Condition="'$(TargetFramework)' == 'net7.0'" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" Condition="'$(TargetFramework)' == 'net8.0'" />
<PackageReference Include="MySql.Data" Version="9.1.0" />
<PackageReference Include="MySqlConnector" Version="2.4.0" />
<PackageReference Include="Npgsql" Version="9.0.2" />
<PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
</ItemGroup>

<ItemGroup>
Expand Down
74 changes: 35 additions & 39 deletions Aikido.Zen.DotNetCore/Patches/SqlClientPatches.cs
Original file line number Diff line number Diff line change
@@ -1,72 +1,68 @@
using Aikido.Zen.Core.Helpers;
using System;
using System.Reflection;
using HarmonyLib;
using Microsoft.Data.Sqlite;
using MySql.Data.MySqlClient;
using System.Data.Common;
using Npgsql;
using Aikido.Zen.Core.Models;
using MySqlX.XDevAPI.Relational;
using System.Reflection;
using Aikido.Zen.Core.Helpers;

namespace Aikido.Zen.DotNetCore.Patches
{
internal static class SqlClientPatches
{
// we need to patch from inside the framework, because we have to pass the context, which is constructed in a framework specific manner
public static void ApplyPatches(Harmony harmony)
{

// Generic
PatchMethod(harmony, typeof(DbCommand), "ExecuteNonQueryAsync");
PatchMethod(harmony, typeof(DbCommand), "ExecuteReaderAsync", typeof(System.Data.CommandBehavior));
PatchMethod(harmony, typeof(DbCommand), "ExecuteScalarAsync");
// Use reflection to get the types dynamically
PatchMethod(harmony, "System.Data.Common", "DbCommand", "ExecuteNonQueryAsync");
PatchMethod(harmony, "System.Data.Common", "DbCommand", "ExecuteReaderAsync", "System.Data.CommandBehavior");
PatchMethod(harmony, "System.Data.Common", "DbCommand", "ExecuteScalarAsync");

// SQL Server
PatchMethod(harmony, typeof(Microsoft.Data.SqlClient.SqlCommand), "ExecuteNonQuery");
PatchMethod(harmony, typeof(Microsoft.Data.SqlClient.SqlCommand), "ExecuteScalar");
PatchMethod(harmony, typeof(Microsoft.Data.SqlClient.SqlCommand), "ExecuteReader", typeof(System.Data.CommandBehavior));
PatchMethod(harmony, typeof(System.Data.SqlClient.SqlCommand), "ExecuteNonQuery");
PatchMethod(harmony, typeof(System.Data.SqlClient.SqlCommand), "ExecuteScalar");
PatchMethod(harmony, typeof(System.Data.SqlClient.SqlCommand), "ExecuteReader", typeof(System.Data.CommandBehavior));
PatchMethod(harmony, "Microsoft.Data.SqlClient", "SqlCommand", "ExecuteNonQuery");
PatchMethod(harmony, "Microsoft.Data.SqlClient", "SqlCommand", "ExecuteScalar");
PatchMethod(harmony, "Microsoft.Data.SqlClient", "SqlCommand", "ExecuteReader", "System.Data.CommandBehavior");
PatchMethod(harmony, "System.Data.SqlClient", "SqlCommand", "ExecuteNonQuery");
PatchMethod(harmony, "System.Data.SqlClient", "SqlCommand", "ExecuteScalar");
PatchMethod(harmony, "System.Data.SqlClient", "SqlCommand", "ExecuteReader", "System.Data.CommandBehavior");

// SQLite
PatchMethod(harmony, typeof(SqliteCommand), "ExecuteNonQuery");
PatchMethod(harmony, typeof(SqliteCommand), "ExecuteScalar");
PatchMethod(harmony, typeof(SqliteCommand), "ExecuteReader", typeof(System.Data.CommandBehavior));
PatchMethod(harmony, "Microsoft.Data.Sqlite", "SqliteCommand", "ExecuteNonQuery");
PatchMethod(harmony, "Microsoft.Data.Sqlite", "SqliteCommand", "ExecuteScalar");
PatchMethod(harmony, "Microsoft.Data.Sqlite", "SqliteCommand", "ExecuteReader", "System.Data.CommandBehavior");

// MySql, MariaDB
PatchMethod(harmony, typeof(MySqlCommand), "ExecuteNonQuery");
PatchMethod(harmony, typeof(MySqlCommand), "ExecuteScalar");
PatchMethod(harmony, typeof(MySqlCommand), "ExecuteReader", typeof(System.Data.CommandBehavior));
PatchMethod(harmony, typeof(MySqlConnector.MySqlCommand), "ExecuteNonQuery");
PatchMethod(harmony, typeof(MySqlConnector.MySqlCommand), "ExecuteScalar");
PatchMethod(harmony, typeof(MySqlConnector.MySqlCommand), "ExecuteReader", typeof(System.Data.CommandBehavior));
PatchMethod(harmony, "MySql.Data", "MySqlClient.MySqlCommand", "ExecuteNonQuery");
PatchMethod(harmony, "MySql.Data", "MySqlClient.MySqlCommand", "ExecuteScalar");
PatchMethod(harmony, "MySql.Data", "MySqlClient.MySqlCommand", "ExecuteReader", "System.Data.CommandBehavior");
PatchMethod(harmony, "MySqlConnector", "MySqlCommand", "ExecuteNonQuery");
PatchMethod(harmony, "MySqlConnector", "MySqlCommand", "ExecuteScalar");
PatchMethod(harmony, "MySqlConnector", "MySqlCommand", "ExecuteReader", "System.Data.CommandBehavior");

// PostgreSQL
PatchMethod(harmony, typeof(NpgsqlCommand), "ExecuteNonQuery");
PatchMethod(harmony, typeof(NpgsqlCommand), "ExecuteScalar");
PatchMethod(harmony, typeof(NpgsqlCommand), "ExecuteReader", typeof(System.Data.CommandBehavior));
PatchMethod(harmony, "Npgsql", "NpgsqlCommand", "ExecuteNonQuery");
PatchMethod(harmony, "Npgsql", "NpgsqlCommand", "ExecuteScalar");
PatchMethod(harmony, "Npgsql", "NpgsqlCommand", "ExecuteReader", "System.Data.CommandBehavior");

// MySqlX
PatchMethod(harmony, typeof(Table), "Select");
PatchMethod(harmony, typeof(Table), "Insert");
PatchMethod(harmony, typeof(Table), "Update");
PatchMethod(harmony, typeof(Table), "Delete");
PatchMethod(harmony, "MySqlX", "XDevAPI.Relational.Table", "Select");
PatchMethod(harmony, "MySqlX", "XDevAPI.Relational.Table", "Insert");
PatchMethod(harmony, "MySqlX", "XDevAPI.Relational.Table", "Update");
PatchMethod(harmony, "MySqlX", "XDevAPI.Relational.Table", "Delete");
}

private static void PatchMethod(Harmony harmony, Type type, string methodName, params Type[] parameters)
private static void PatchMethod(Harmony harmony, string assemblyName, string typeName, string methodName, params string[] parameterTypeNames)
{
var method = AccessTools.Method(type, methodName, parameters);
var method = ReflectionHelper.GetMethodFromAssembly(assemblyName, typeName, methodName, parameterTypeNames);
if (method != null)
{
harmony.Patch(method, new HarmonyMethod(typeof(SqlClientPatches).GetMethod(nameof(OnCommandExecuting), BindingFlags.Static | BindingFlags.NonPublic)));
}
}

private static bool OnCommandExecuting(object[] __args, MethodBase __originalMethod, DbCommand __instance)
private static bool OnCommandExecuting(object[] __args, MethodBase __originalMethod, object __instance)
{
var dbCommand = __instance as System.Data.Common.DbCommand;
if (dbCommand == null) return true;
var assembly = __instance.GetType().Assembly.FullName?.Split(", Culture=")[0];
return Aikido.Zen.Core.Patches.SqlClientPatcher.OnCommandExecuting(__args, __originalMethod, __instance, assembly, Zen.GetContext());
return Aikido.Zen.Core.Patches.SqlClientPatcher.OnCommandExecuting(__args, __originalMethod, dbCommand, assembly, Zen.GetContext());
}
}
}
Loading

0 comments on commit 926a58c

Please sign in to comment.