hardened agent (added systempaths class to pin paths, no more env_var support for backend api)

This commit is contained in:
Oliver Kieselbach
2026-04-08 00:42:41 +02:00
parent 6b099329cf
commit 91cb3442bf
10 changed files with 68 additions and 13 deletions
@@ -7,6 +7,7 @@ using System.Net.NetworkInformation;
using System.Text.Json;
using System.Threading.Tasks;
using AutopilotMonitor.Agent.Core.Logging;
using AutopilotMonitor.Agent.Core.Security;
using AutopilotMonitor.Shared.Models;
using Microsoft.Win32;
@@ -635,7 +636,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
{
var psi = new ProcessStartInfo
{
FileName = "netsh.exe",
FileName = SystemPaths.Netsh,
Arguments = "wlan show interfaces",
UseShellExecute = false,
RedirectStandardOutput = true,
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using AutopilotMonitor.Agent.Core.Security;
using AutopilotMonitor.Shared.Models;
namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors.GatherCollectors
@@ -56,7 +57,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors.GatherCollectors
var encodedCommand = Convert.ToBase64String(Encoding.Unicode.GetBytes(command));
psi = new ProcessStartInfo
{
FileName = "powershell.exe",
FileName = SystemPaths.PowerShell,
Arguments = $"-NoProfile -ExecutionPolicy RemoteSigned -EncodedCommand {encodedCommand}",
UseShellExecute = false,
RedirectStandardOutput = true,
@@ -68,7 +69,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors.GatherCollectors
{
psi = new ProcessStartInfo
{
FileName = "cmd.exe",
FileName = SystemPaths.Cmd,
Arguments = $"/c {command}",
UseShellExecute = false,
RedirectStandardOutput = true,
@@ -8,6 +8,7 @@ using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using AutopilotMonitor.Agent.Core.Logging;
using AutopilotMonitor.Agent.Core.Security;
using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
@@ -401,7 +402,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
{
var psi = new ProcessStartInfo
{
FileName = "netsh.exe",
FileName = SystemPaths.Netsh,
Arguments = "wlan show interfaces",
UseShellExecute = false,
RedirectStandardOutput = true,
@@ -3,6 +3,7 @@ using System.Diagnostics;
using System.IO;
using AutopilotMonitor.Agent.Core.Configuration;
using AutopilotMonitor.Agent.Core.Logging;
using AutopilotMonitor.Agent.Core.Security;
namespace AutopilotMonitor.Agent.Core.Monitoring.Core
{
@@ -101,8 +102,10 @@ Remove-Item -Path $scriptPath -Force -ErrorAction SilentlyContinue
// creates a new process group that breaks job inheritance, even under SYSTEM.
var psi = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c start \"\" /b powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File \"{tempScriptPath}\"",
FileName = SystemPaths.Cmd,
// Absolute path to powershell.exe is passed to `start` to prevent PATH hijacking
// (the `start` builtin does its own PATH resolution for the target command).
Arguments = $"/c start \"\" /b \"{SystemPaths.PowerShell}\" -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File \"{tempScriptPath}\"",
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = Path.GetTempPath()
@@ -12,6 +12,7 @@ using AutopilotMonitor.Agent.Core.Logging;
using AutopilotMonitor.Agent.Core.Monitoring.Collectors;
using AutopilotMonitor.Agent.Core.Monitoring.Network;
using AutopilotMonitor.Agent.Core.Monitoring.Tracking;
using AutopilotMonitor.Agent.Core.Security;
using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
@@ -635,7 +636,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
var psi = new ProcessStartInfo
{
FileName = "shutdown.exe",
FileName = SystemPaths.Shutdown,
Arguments = $"/r /t {_configuration.RebootDelaySeconds} /c \"Autopilot enrollment completed - rebooting\"",
UseShellExecute = false,
CreateNoWindow = true
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using AutopilotMonitor.Agent.Core.Logging;
using AutopilotMonitor.Agent.Core.Security;
namespace AutopilotMonitor.Agent.Core.Monitoring.Network
{
@@ -50,7 +51,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Network
var psi = new ProcessStartInfo
{
FileName = "tzutil",
FileName = SystemPaths.TzUtil,
Arguments = $"/s \"{windowsId}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
@@ -0,0 +1,48 @@
using System;
using System.IO;
namespace AutopilotMonitor.Agent.Core.Security
{
/// <summary>
/// Resolved absolute paths to Windows system binaries.
///
/// Rationale: launching processes by bare name (e.g. "cmd.exe", "tzutil") lets
/// Windows walk the PATH to find them. As SYSTEM this is a DLL/binary-hijacking
/// vector — an attacker able to plant a file in any PATH entry ahead of System32
/// gets code execution with SYSTEM privileges. Always use these resolved paths.
///
/// <see cref="Environment.SystemDirectory"/> respects WOW64 redirection
/// automatically:
/// - 64-bit process on any Windows → C:\Windows\System32
/// - 32-bit process on 64-bit Windows → C:\Windows\SysWOW64
/// The agent ships as AnyCPU/net48 and runs 64-bit on 64-bit Windows, so this
/// resolves to the real System32. It also honors non-default %SystemRoot%
/// (rare, but seen in custom OEM images on drives other than C:).
///
/// All listed binaries are in-box on every supported Windows version
/// (Windows 7 / 8.1 / 10 / 11 / Server 2012R2 – 2025).
/// </summary>
public static class SystemPaths
{
private static readonly string System32 = Environment.SystemDirectory;
/// <summary>C:\Windows\System32\cmd.exe</summary>
public static readonly string Cmd = Path.Combine(System32, "cmd.exe");
/// <summary>C:\Windows\System32\shutdown.exe</summary>
public static readonly string Shutdown = Path.Combine(System32, "shutdown.exe");
/// <summary>C:\Windows\System32\tzutil.exe</summary>
public static readonly string TzUtil = Path.Combine(System32, "tzutil.exe");
/// <summary>C:\Windows\System32\netsh.exe</summary>
public static readonly string Netsh = Path.Combine(System32, "netsh.exe");
/// <summary>
/// C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
/// Note: the "v1.0" folder is historical — it hosts every in-box PowerShell
/// version including 5.1 on Windows 10/11. Do not "fix" it to v5.1.
/// </summary>
public static readonly string PowerShell = Path.Combine(System32, "WindowsPowerShell", "v1.0", "powershell.exe");
}
}
@@ -105,9 +105,7 @@ namespace AutopilotMonitor.Agent
bool enableGeoLocationConfig = true;
bool useClientCertAuthConfig = true;
// Environment variable overrides built-in default
apiBaseUrl = Environment.GetEnvironmentVariable("AUTOPILOT_MONITOR_API") ?? apiBaseUrl;
// CLI override wins over environment variable/default
// CLI override wins over built-in default
if (!string.IsNullOrWhiteSpace(apiBaseUrlOverride))
apiBaseUrl = apiBaseUrlOverride;
@@ -6,6 +6,7 @@ using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using AutopilotMonitor.Agent.Core.Security;
using AutopilotMonitor.Shared;
using Newtonsoft.Json.Linq;
@@ -451,7 +452,7 @@ namespace AutopilotMonitor.Agent
var psi = new ProcessStartInfo
{
FileName = "powershell.exe",
FileName = SystemPaths.PowerShell,
Arguments = $"-NoProfile -NonInteractive -EncodedCommand {encoded}",
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
@@ -1 +1 @@
885
887