handle OData filer injection

This commit is contained in:
Oliver Kieselbach
2026-02-07 21:26:49 +01:00
parent 5f4519d635
commit 7fae27f0da
3 changed files with 76 additions and 0 deletions
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AutopilotMonitor.Functions.Services;
using AutopilotMonitor.Shared.Models;
@@ -15,6 +16,31 @@ namespace AutopilotMonitor.Functions.Security
/// </summary>
public class SecurityValidator
{
private static readonly Regex GuidRegex = new Regex(
@"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
RegexOptions.Compiled);
/// <summary>
/// Validates that a string is a valid GUID format.
/// Use this to prevent OData filter injection in Table Storage queries.
/// </summary>
public static bool IsValidGuid(string? value)
{
if (string.IsNullOrWhiteSpace(value))
return false;
return Guid.TryParse(value, out _) && GuidRegex.IsMatch(value);
}
/// <summary>
/// Validates that a value is a valid GUID and throws if not.
/// </summary>
public static void EnsureValidGuid(string? value, string parameterName)
{
if (!IsValidGuid(value))
throw new ArgumentException($"Invalid {parameterName} format. Expected a valid GUID.", parameterName);
}
private readonly TenantConfigurationService _configService;
private readonly RateLimitService _rateLimitService;
private readonly ILogger _logger;
@@ -1,5 +1,6 @@
using Azure;
using Azure.Data.Tables;
using AutopilotMonitor.Functions.Security;
using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using Microsoft.Extensions.Configuration;
@@ -82,6 +83,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<bool> StoreSessionAsync(SessionRegistration registration)
{
SecurityValidator.EnsureValidGuid(registration.TenantId, "TenantId");
SecurityValidator.EnsureValidGuid(registration.SessionId, "SessionId");
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.Sessions);
@@ -122,6 +126,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<bool> StoreEventAsync(EnrollmentEvent evt)
{
SecurityValidator.EnsureValidGuid(evt.TenantId, "TenantId");
SecurityValidator.EnsureValidGuid(evt.SessionId, "SessionId");
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.Events);
@@ -166,6 +173,8 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<List<SessionSummary>> GetSessionsAsync(string tenantId, int maxResults = 100)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.Sessions);
@@ -248,6 +257,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<bool> UpdateSessionStatusAsync(string tenantId, string sessionId, SessionStatus status, EnrollmentPhase? currentPhase = null, string? failureReason = null)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
SecurityValidator.EnsureValidGuid(sessionId, nameof(sessionId));
const int maxRetries = 3;
int retryCount = 0;
@@ -327,6 +339,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<List<EnrollmentEvent>> GetSessionEventsAsync(string tenantId, string sessionId, int maxResults = 1000)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
SecurityValidator.EnsureValidGuid(sessionId, nameof(sessionId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.Events);
@@ -439,6 +454,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<int> DeleteSessionEventsAsync(string tenantId, string sessionId)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
SecurityValidator.EnsureValidGuid(sessionId, nameof(sessionId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.Events);
@@ -501,6 +519,8 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<List<AuditLogEntry>> GetAuditLogsAsync(string tenantId, int maxResults = 100)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.AuditLogs);
@@ -581,6 +601,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<List<UsageMetricsSnapshot>> GetUsageMetricsSnapshotAsync(string? tenantId = null, string? startDate = null, string? endDate = null, int maxResults = 100)
{
if (!string.IsNullOrEmpty(tenantId))
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.UsageMetrics);
@@ -644,6 +667,8 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<List<SessionSummary>> GetSessionsOlderThanAsync(string tenantId, DateTime cutoffDate)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.Sessions);
@@ -740,6 +765,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<List<RuleResult>> GetRuleResultsAsync(string tenantId, string sessionId)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
SecurityValidator.EnsureValidGuid(sessionId, nameof(sessionId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.RuleResults);
@@ -828,6 +856,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<List<GatherRule>> GetGatherRulesAsync(string partitionKey)
{
if (partitionKey != "global")
SecurityValidator.EnsureValidGuid(partitionKey, nameof(partitionKey));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.GatherRules);
@@ -945,6 +976,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<List<AnalyzeRule>> GetAnalyzeRulesAsync(string partitionKey)
{
if (partitionKey != "global")
SecurityValidator.EnsureValidGuid(partitionKey, nameof(partitionKey));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.AnalyzeRules);
@@ -1053,6 +1087,8 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<List<AppInstallSummary>> GetAppInstallSummariesByTenantAsync(string tenantId)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.AppInstallSummaries);
@@ -1078,6 +1114,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<int> DeleteSessionAppInstallSummariesAsync(string tenantId, string sessionId)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
SecurityValidator.EnsureValidGuid(sessionId, nameof(sessionId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.AppInstallSummaries);
@@ -1105,6 +1144,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<int> DeleteSessionRuleResultsAsync(string tenantId, string sessionId)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
SecurityValidator.EnsureValidGuid(sessionId, nameof(sessionId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.RuleResults);
@@ -1297,6 +1339,8 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<UserActivityMetrics> GetUserActivityMetricsAsync(string tenantId)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.UserActivity);
@@ -1405,6 +1449,9 @@ namespace AutopilotMonitor.Functions.Services
/// </summary>
public async Task<(int uniqueUsers, int loginCount)> GetUserActivityForDateAsync(string? tenantId, DateTime date)
{
if (!string.IsNullOrEmpty(tenantId) && tenantId != "global")
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
try
{
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.UserActivity);
@@ -1,3 +1,4 @@
using AutopilotMonitor.Functions.Security;
using AutopilotMonitor.Shared;
using Azure.Data.Tables;
using Microsoft.Extensions.Caching.Memory;
@@ -207,6 +208,8 @@ public class TenantAdminsService
/// </summary>
public async Task<List<TenantAdminEntity>> GetTenantAdminsAsync(string tenantId)
{
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
tenantId = tenantId.ToLowerInvariant();
var tableClient = _tableServiceClient.GetTableClient(Constants.TableNames.TenantAdmins);