Content is user-generated and unverified.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; public static class CsvInjectionSecurityHelper { // Dangerous formula prefixes that can execute code in spreadsheet applications private static readonly char[] DangerousFormulaPrefixes = { '=', '+', '-', '@', '\t', '\r' }; // Additional dangerous characters that could be used in formulas private static readonly string[] DangerousPatterns = { "=cmd|", // Command execution "=system(", // System command "=exec(", // Execute command "=shell(", // Shell command "=powershell", // PowerShell execution "=bash", // Bash command "=sh", // Shell command "=python", // Python execution "=perl", // Perl execution "=ruby", // Ruby execution "=javascript:", // JavaScript execution "=hyperlink(", // Hyperlink injection "=importxml(", // XML import (Google Sheets) "=importhtml(", // HTML import (Google Sheets) "=importdata(", // Data import (Google Sheets) "=importfeed(", // Feed import (Google Sheets) "=importrange(", // Range import (Google Sheets) "=googletranslate(", // Google Translate function "=webservice(", // Web service call "=filterxml(", // XML filtering "=dde|", // Dynamic Data Exchange "=document.", // Document object access "=window.", // Window object access "=eval(", // Eval function "=setTimeout(", // Timeout function "=setInterval(", // Interval function }; /// <summary> /// Validates input for CSV injection patterns /// </summary> /// <param name="input">The input string to validate</param> /// <returns>True if input appears safe, false if potentially dangerous</returns> public static bool IsValidForCsv(string input) { if (string.IsNullOrEmpty(input)) return true; // Check for dangerous formula prefixes if (input.Length > 0 && DangerousFormulaPrefixes.Contains(input[0])) return false; // Check for dangerous patterns (case-insensitive) foreach (string pattern in DangerousPatterns) { if (input.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) >= 0) return false; } // Check for suspicious patterns that might indicate formula injection if (ContainsSuspiciousFormulas(input)) return false; return true; } /// <summary> /// Sanitizes input to prevent CSV injection /// </summary> /// <param name="input">The input string to sanitize</param> /// <param name="strategy">Sanitization strategy to use</param> /// <returns>Sanitized string safe for CSV output</returns> public static string SanitizeForCsv(string input, CsvSanitizationStrategy strategy = CsvSanitizationStrategy.PrefixWithApostrophe) { if (string.IsNullOrEmpty(input)) return string.Empty; switch (strategy) { case CsvSanitizationStrategy.PrefixWithApostrophe: return SanitizeWithApostrophe(input); case CsvSanitizationStrategy.PrefixWithSpace: return SanitizeWithSpace(input); case CsvSanitizationStrategy.RemoveDangerousCharacters: return RemoveDangerousCharacters(input); case CsvSanitizationStrategy.EncodeSpecialCharacters: return EncodeSpecialCharacters(input); case CsvSanitizationStrategy.RejectDangerous: return IsValidForCsv(input) ? input : "[REJECTED_CONTENT]"; default: return SanitizeWithApostrophe(input); } } /// <summary> /// Sanitization strategies for CSV content /// </summary> public enum CsvSanitizationStrategy { /// <summary>Prefix dangerous content with apostrophe to treat as text</summary> PrefixWithApostrophe, /// <summary>Prefix dangerous content with space</summary> PrefixWithSpace, /// <summary>Remove dangerous characters entirely</summary> RemoveDangerousCharacters, /// <summary>Encode special characters</summary> EncodeSpecialCharacters, /// <summary>Reject dangerous content with placeholder</summary> RejectDangerous } /// <summary> /// Sanitizes by prefixing with apostrophe (most common approach) /// </summary> /// <param name="input">Input to sanitize</param> /// <returns>Sanitized string</returns> private static string SanitizeWithApostrophe(string input) { if (string.IsNullOrEmpty(input)) return string.Empty; // If input starts with dangerous character, prefix with apostrophe if (input.Length > 0 && DangerousFormulaPrefixes.Contains(input[0])) return "'" + input; // Check for dangerous patterns and prefix if found foreach (string pattern in DangerousPatterns) { if (input.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) >= 0) return "'" + input; } return input; } /// <summary> /// Sanitizes by prefixing with space /// </summary> /// <param name="input">Input to sanitize</param> /// <returns>Sanitized string</returns> private static string SanitizeWithSpace(string input) { if (string.IsNullOrEmpty(input)) return string.Empty; if (input.Length > 0 && DangerousFormulaPrefixes.Contains(input[0])) return " " + input; return input; } /// <summary> /// Removes dangerous characters entirely /// </summary> /// <param name="input">Input to sanitize</param> /// <returns>Sanitized string</returns> private static string RemoveDangerousCharacters(string input) { if (string.IsNullOrEmpty(input)) return string.Empty; var result = new StringBuilder(); foreach (char c in input) { if (!DangerousFormulaPrefixes.Contains(c)) result.Append(c); } return result.ToString(); } /// <summary> /// Encodes special characters /// </summary> /// <param name="input">Input to sanitize</param> /// <returns>Sanitized string</returns> private static string EncodeSpecialCharacters(string input) { if (string.IsNullOrEmpty(input)) return string.Empty; return input.Replace("=", "&#61;") .Replace("+", "&#43;") .Replace("-", "&#45;") .Replace("@", "&#64;") .Replace("\t", "&#9;") .Replace("\r", "&#13;"); } /// <summary> /// Checks for suspicious formula patterns /// </summary> /// <param name="input">Input to check</param> /// <returns>True if suspicious patterns found</returns> private static bool ContainsSuspiciousFormulas(string input) { // Check for patterns that might indicate formula injection var suspiciousPatterns = new[] { @"=\s*\w+\s*\(", // Function calls @"=.*\|.*\|", // Pipe characters (DDE) @"=.*cmd.*", // Command references @"=.*powershell.*", // PowerShell references @"=.*javascript:.*", // JavaScript protocol @"=.*file:\/\/.*", // File protocol @"=.*http[s]?:\/\/.*", // HTTP protocols in formulas }; foreach (string pattern in suspiciousPatterns) { if (Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase)) return true; } return false; } /// <summary> /// Validates an entire CSV row for injection attacks /// </summary> /// <param name="csvRow">Array of CSV field values</param> /// <returns>True if all fields are safe, false otherwise</returns> public static bool IsValidCsvRow(string[] csvRow) { if (csvRow == null) return true; return csvRow.All(field => IsValidForCsv(field)); } /// <summary> /// Sanitizes an entire CSV row /// </summary> /// <param name="csvRow">Array of CSV field values</param> /// <param name="strategy">Sanitization strategy</param> /// <returns>Sanitized CSV row</returns> public static string[] SanitizeCsvRow(string[] csvRow, CsvSanitizationStrategy strategy = CsvSanitizationStrategy.PrefixWithApostrophe) { if (csvRow == null) return null; return csvRow.Select(field => SanitizeForCsv(field, strategy)).ToArray(); } /// <summary> /// Validates CSV content for bulk operations /// </summary> /// <param name="csvData">List of CSV rows</param> /// <param name="dangerousRows">Output list of row indices that contain dangerous content</param> /// <returns>True if all rows are safe, false otherwise</returns> public static bool ValidateCsvData(List<string[]> csvData, out List<int> dangerousRows) { dangerousRows = new List<int>(); if (csvData == null || csvData.Count == 0) return true; for (int i = 0; i < csvData.Count; i++) { if (!IsValidCsvRow(csvData[i])) { dangerousRows.Add(i); } } return dangerousRows.Count == 0; } /// <summary> /// Sanitizes CSV data for bulk operations /// </summary> /// <param name="csvData">List of CSV rows</param> /// <param name="strategy">Sanitization strategy</param> /// <returns>Sanitized CSV data</returns> public static List<string[]> SanitizeCsvData(List<string[]> csvData, CsvSanitizationStrategy strategy = CsvSanitizationStrategy.PrefixWithApostrophe) { if (csvData == null) return null; return csvData.Select(row => SanitizeCsvRow(row, strategy)).ToList(); } } // CSV Export Helper with built-in security public static class SecureCsvExporter { /// <summary> /// Exports data to CSV with automatic sanitization /// </summary> /// <param name="data">Data to export</param> /// <param name="headers">CSV headers</param> /// <param name="strategy">Sanitization strategy</param> /// <returns>Safe CSV content</returns> public static string ExportToCsv(List<object[]> data, string[] headers, CsvInjectionSecurityHelper.CsvSanitizationStrategy strategy = CsvInjectionSecurityHelper.CsvSanitizationStrategy.PrefixWithApostrophe) { var csvBuilder = new StringBuilder(); // Sanitize and add headers if (headers != null) { var safeHeaders = headers.Select(h => CsvInjectionSecurityHelper.SanitizeForCsv(h?.ToString() ?? "", strategy)).ToArray(); csvBuilder.AppendLine(string.Join(",", safeHeaders.Select(EscapeCsvField))); } // Sanitize and add data rows foreach (var row in data) { var stringRow = row.Select(field => field?.ToString() ?? "").ToArray(); var safeRow = CsvInjectionSecurityHelper.SanitizeCsvRow(stringRow, strategy); csvBuilder.AppendLine(string.Join(",", safeRow.Select(EscapeCsvField))); } return csvBuilder.ToString(); } /// <summary> /// Properly escapes CSV field values /// </summary> /// <param name="field">Field value to escape</param> /// <returns>Escaped field value</returns> private static string EscapeCsvField(string field) { if (string.IsNullOrEmpty(field)) return string.Empty; // If field contains comma, quote, or newline, wrap in quotes and escape internal quotes if (field.Contains(",") || field.Contains("\"") || field.Contains("\n") || field.Contains("\r")) { return "\"" + field.Replace("\"", "\"\"") + "\""; } return field; } } // Example usage public class CsvSecurityExample { public void DemonstrateUsage() { // Example of dangerous CSV content var dangerousInputs = new[] { "=cmd|' /C calc'!A0", // Command execution "=SUM(1+1)*cmd|' /C calc'!A0", // Hidden command in formula "=2+5+cmd|' /C calc'!A0", // Command in calculation "+2+5+cmd|' /C calc'!A0", // Plus prefix "-2+5+cmd|' /C calc'!A0", // Minus prefix "@SUM(1+1)*cmd|' /C calc'!A0", // @ prefix "=HYPERLINK(\"http://evil.com\",\"click me\")", // Hyperlink injection "=IMPORTXML(\"http://evil.com/\",\"//\")" // XML import }; Console.WriteLine("=== CSV Injection Validation Examples ==="); foreach (var input in dangerousInputs) { bool isValid = CsvInjectionSecurityHelper.IsValidForCsv(input); string sanitized = CsvInjectionSecurityHelper.SanitizeForCsv(input); Console.WriteLine($"Input: {input}"); Console.WriteLine($"Valid: {isValid}"); Console.WriteLine($"Sanitized: {sanitized}"); Console.WriteLine(); } // Example of CSV export with security var userData = new List<object[]> { new object[] { "John Doe", "john@example.com", "=SUM(1+1)" }, new object[] { "Jane Smith", "jane@example.com", "+1234567890" }, new object[] { "Bob Johnson", "bob@example.com", "Normal data" } }; var headers = new[] { "Name", "Email", "Notes" }; string safeCsv = SecureCsvExporter.ExportToCsv(userData, headers); Console.WriteLine("=== Secure CSV Export ==="); Console.WriteLine(safeCsv); } /// <summary> /// Example of validating uploaded CSV file /// </summary> /// <param name="csvContent">Raw CSV content</param> public void ValidateUploadedCsv(string csvContent) { // Parse CSV (using a CSV parsing library like CsvHelper in real scenarios) var rows = ParseCsvContent(csvContent); // Validate for CSV injection if (CsvInjectionSecurityHelper.ValidateCsvData(rows, out List<int> dangerousRows)) { Console.WriteLine("CSV file is safe to process."); } else { Console.WriteLine($"CSV file contains dangerous content in rows: {string.Join(", ", dangerousRows)}"); // Option 1: Reject the file throw new InvalidOperationException("CSV file contains potentially dangerous content."); // Option 2: Sanitize and continue var sanitizedData = CsvInjectionSecurityHelper.SanitizeCsvData(rows); Console.WriteLine("CSV data has been sanitized."); } } /// <summary> /// Simple CSV parsing for demonstration (use proper CSV library in production) /// </summary> /// <param name="csvContent">CSV content to parse</param> /// <returns>Parsed CSV rows</returns> private List<string[]> ParseCsvContent(string csvContent) { var rows = new List<string[]>(); var lines = csvContent.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { // Simple parsing - use proper CSV library for production var fields = line.Split(','); rows.Add(fields); } return rows; } }
Content is user-generated and unverified.
    CSV Injection Prevention Functions | Claude