// SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (c) StellaOps // Registry of known dangerous sinks per language. namespace StellaOps.Scanner.Contracts; using System.Collections.Frozen; using System.Collections.Immutable; /// /// Registry of known dangerous sinks per language. /// public static class SinkRegistry { private static readonly FrozenDictionary> SinksByLanguage = BuildRegistry(); private static FrozenDictionary> BuildRegistry() { var builder = new Dictionary>(StringComparer.Ordinal); // .NET sinks AddSink(builder, "dotnet", SinkCategory.CmdExec, "System.Diagnostics.Process.Start", cweId: "CWE-78"); AddSink(builder, "dotnet", SinkCategory.CmdExec, "System.Diagnostics.ProcessStartInfo", cweId: "CWE-78"); AddSink(builder, "dotnet", SinkCategory.UnsafeDeser, "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize", cweId: "CWE-502"); AddSink(builder, "dotnet", SinkCategory.UnsafeDeser, "Newtonsoft.Json.JsonConvert.DeserializeObject", cweId: "CWE-502", framework: "Newtonsoft.Json"); AddSink(builder, "dotnet", SinkCategory.SqlRaw, "System.Data.SqlClient.SqlCommand.ExecuteReader", cweId: "CWE-89"); AddSink(builder, "dotnet", SinkCategory.SqlRaw, "Microsoft.EntityFrameworkCore.RelationalQueryableExtensions.FromSqlRaw", cweId: "CWE-89", framework: "EFCore"); AddSink(builder, "dotnet", SinkCategory.Ssrf, "System.Net.Http.HttpClient.GetAsync", cweId: "CWE-918"); AddSink(builder, "dotnet", SinkCategory.FileWrite, "System.IO.File.WriteAllBytes", cweId: "CWE-73"); AddSink(builder, "dotnet", SinkCategory.PathTraversal, "System.IO.Path.Combine", cweId: "CWE-22"); AddSink(builder, "dotnet", SinkCategory.CryptoWeak, "System.Security.Cryptography.MD5.Create", cweId: "CWE-327"); AddSink(builder, "dotnet", SinkCategory.CryptoWeak, "System.Security.Cryptography.DES.Create", cweId: "CWE-327"); // Java sinks AddSink(builder, "java", SinkCategory.CmdExec, "java.lang.Runtime.exec", cweId: "CWE-78"); AddSink(builder, "java", SinkCategory.CmdExec, "java.lang.ProcessBuilder.start", cweId: "CWE-78"); AddSink(builder, "java", SinkCategory.UnsafeDeser, "java.io.ObjectInputStream.readObject", cweId: "CWE-502"); AddSink(builder, "java", SinkCategory.SqlRaw, "java.sql.Statement.executeQuery", cweId: "CWE-89"); AddSink(builder, "java", SinkCategory.Ssrf, "java.net.URL.openConnection", cweId: "CWE-918"); AddSink(builder, "java", SinkCategory.TemplateInjection, "org.springframework.expression.ExpressionParser.parseExpression", cweId: "CWE-917", framework: "Spring"); // Node.js sinks AddSink(builder, "node", SinkCategory.CmdExec, "child_process.exec", cweId: "CWE-78"); AddSink(builder, "node", SinkCategory.CmdExec, "child_process.spawn", cweId: "CWE-78"); AddSink(builder, "node", SinkCategory.UnsafeDeser, "node-serialize.unserialize", cweId: "CWE-502"); AddSink(builder, "node", SinkCategory.SqlRaw, "mysql.query", cweId: "CWE-89"); AddSink(builder, "node", SinkCategory.PathTraversal, "path.join", cweId: "CWE-22"); AddSink(builder, "node", SinkCategory.TemplateInjection, "eval", cweId: "CWE-94"); // Python sinks AddSink(builder, "python", SinkCategory.CmdExec, "os.system", cweId: "CWE-78"); AddSink(builder, "python", SinkCategory.CmdExec, "subprocess.call", cweId: "CWE-78"); AddSink(builder, "python", SinkCategory.UnsafeDeser, "pickle.loads", cweId: "CWE-502"); AddSink(builder, "python", SinkCategory.UnsafeDeser, "yaml.load", cweId: "CWE-502"); AddSink(builder, "python", SinkCategory.SqlRaw, "sqlite3.Cursor.execute", cweId: "CWE-89"); AddSink(builder, "python", SinkCategory.TemplateInjection, "jinja2.Template.render", cweId: "CWE-1336", framework: "Jinja2"); // Go sinks AddSink(builder, "go", SinkCategory.CmdExec, "os/exec.Command", cweId: "CWE-78"); AddSink(builder, "go", SinkCategory.CmdExec, "os/exec.CommandContext", cweId: "CWE-78"); AddSink(builder, "go", SinkCategory.SqlRaw, "database/sql.DB.Query", cweId: "CWE-89"); AddSink(builder, "go", SinkCategory.SqlRaw, "database/sql.DB.Exec", cweId: "CWE-89"); AddSink(builder, "go", SinkCategory.Ssrf, "net/http.Get", cweId: "CWE-918"); AddSink(builder, "go", SinkCategory.PathTraversal, "filepath.Join", cweId: "CWE-22"); // Ruby sinks AddSink(builder, "ruby", SinkCategory.CmdExec, "Kernel.system", cweId: "CWE-78"); AddSink(builder, "ruby", SinkCategory.CmdExec, "Kernel.exec", cweId: "CWE-78"); AddSink(builder, "ruby", SinkCategory.UnsafeDeser, "Marshal.load", cweId: "CWE-502"); AddSink(builder, "ruby", SinkCategory.UnsafeDeser, "YAML.load", cweId: "CWE-502"); AddSink(builder, "ruby", SinkCategory.SqlRaw, "ActiveRecord::Base.connection.execute", cweId: "CWE-89", framework: "Rails"); AddSink(builder, "ruby", SinkCategory.TemplateInjection, "ERB.new", cweId: "CWE-1336"); // PHP sinks AddSink(builder, "php", SinkCategory.CmdExec, "exec", cweId: "CWE-78"); AddSink(builder, "php", SinkCategory.CmdExec, "shell_exec", cweId: "CWE-78"); AddSink(builder, "php", SinkCategory.CmdExec, "system", cweId: "CWE-78"); AddSink(builder, "php", SinkCategory.UnsafeDeser, "unserialize", cweId: "CWE-502"); AddSink(builder, "php", SinkCategory.SqlRaw, "mysqli_query", cweId: "CWE-89"); AddSink(builder, "php", SinkCategory.SqlRaw, "PDO::query", cweId: "CWE-89"); AddSink(builder, "php", SinkCategory.FileWrite, "file_put_contents", cweId: "CWE-73"); AddSink(builder, "php", SinkCategory.CodeInjection, "eval", cweId: "CWE-94"); return builder.ToFrozenDictionary( kvp => kvp.Key, kvp => kvp.Value.ToImmutableArray(), StringComparer.Ordinal); } private static void AddSink( Dictionary> builder, string language, SinkCategory category, string symbolPattern, string? cweId = null, string? framework = null) { if (!builder.TryGetValue(language, out var list)) { list = []; builder[language] = list; } list.Add(new SinkDefinition( Category: category, SymbolPattern: symbolPattern, Language: language, Framework: framework, CweId: cweId)); } /// /// Gets all sink definitions for a language. /// public static ImmutableArray GetSinksForLanguage(string language) { if (string.IsNullOrWhiteSpace(language)) { return ImmutableArray.Empty; } return SinksByLanguage.GetValueOrDefault(language.Trim().ToLowerInvariant(), ImmutableArray.Empty); } /// /// Gets all registered languages. /// public static IEnumerable GetRegisteredLanguages() => SinksByLanguage.Keys; /// /// Checks if a symbol matches any known sink. /// public static SinkDefinition? MatchSink(string language, string symbol) { if (string.IsNullOrWhiteSpace(language) || string.IsNullOrWhiteSpace(symbol)) { return null; } var sinks = GetSinksForLanguage(language); return sinks.FirstOrDefault(sink => symbol.Contains(sink.SymbolPattern, StringComparison.OrdinalIgnoreCase)); } }