feat: Add native binary analyzer test utilities and implement SM2 signing tests
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
- Introduced `NativeTestBase` class for ELF, PE, and Mach-O binary parsing helpers and assertions. - Created `TestCryptoFactory` for SM2 cryptographic provider setup and key generation. - Implemented `Sm2SigningTests` to validate signing functionality with environment gate checks. - Developed console export service and store with comprehensive unit tests for export status management.
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Globalization;
|
||||
global using System.IO;
|
||||
global using System.IO.Compression;
|
||||
global using System.Linq;
|
||||
global using System.Security.Cryptography;
|
||||
global using System.Text;
|
||||
global using System.Text.RegularExpressions;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents evidence of a capability usage detected in Java source code.
|
||||
/// </summary>
|
||||
internal sealed record JavaCapabilityEvidence
|
||||
{
|
||||
public JavaCapabilityEvidence(
|
||||
CapabilityKind kind,
|
||||
string sourceFile,
|
||||
int sourceLine,
|
||||
string pattern,
|
||||
string? snippet = null,
|
||||
float confidence = 1.0f,
|
||||
CapabilityRisk risk = CapabilityRisk.Low)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(sourceFile, nameof(sourceFile));
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(pattern, nameof(pattern));
|
||||
|
||||
Kind = kind;
|
||||
SourceFile = NormalizePath(sourceFile);
|
||||
SourceLine = sourceLine;
|
||||
Pattern = pattern;
|
||||
Snippet = snippet;
|
||||
Confidence = Math.Clamp(confidence, 0f, 1f);
|
||||
Risk = risk;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The capability category.
|
||||
/// </summary>
|
||||
public CapabilityKind Kind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The source file where the capability is used.
|
||||
/// </summary>
|
||||
public string SourceFile { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The line number of the capability usage.
|
||||
/// </summary>
|
||||
public int SourceLine { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The API, method, or pattern matched.
|
||||
/// </summary>
|
||||
public string Pattern { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A snippet of the code (for context).
|
||||
/// </summary>
|
||||
public string? Snippet { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Confidence level (0.0 to 1.0).
|
||||
/// </summary>
|
||||
public float Confidence { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Risk level associated with this capability usage.
|
||||
/// </summary>
|
||||
public CapabilityRisk Risk { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique key for deduplication.
|
||||
/// </summary>
|
||||
public string DeduplicationKey => $"{Kind}|{SourceFile}|{SourceLine}|{Pattern}";
|
||||
|
||||
/// <summary>
|
||||
/// Creates metadata entries for this evidence.
|
||||
/// </summary>
|
||||
public IEnumerable<KeyValuePair<string, string?>> CreateMetadata()
|
||||
{
|
||||
yield return new KeyValuePair<string, string?>("capability.kind", Kind.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.source", $"{SourceFile}:{SourceLine}");
|
||||
yield return new KeyValuePair<string, string?>("capability.pattern", Pattern);
|
||||
yield return new KeyValuePair<string, string?>("capability.risk", Risk.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.confidence", Confidence.ToString("F2", CultureInfo.InvariantCulture));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Snippet))
|
||||
{
|
||||
var truncated = Snippet.Length > 200 ? Snippet[..197] + "..." : Snippet;
|
||||
yield return new KeyValuePair<string, string?>("capability.snippet", truncated);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts to base LanguageComponentEvidence.
|
||||
/// </summary>
|
||||
public LanguageComponentEvidence ToLanguageEvidence()
|
||||
{
|
||||
return new LanguageComponentEvidence(
|
||||
Kind: LanguageEvidenceKind.Metadata,
|
||||
Source: SourceFile,
|
||||
Locator: $"line:{SourceLine}",
|
||||
Value: $"{Kind}:{Pattern}",
|
||||
Sha256: null);
|
||||
}
|
||||
|
||||
private static string NormalizePath(string path)
|
||||
=> path.Replace('\\', '/');
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Orchestrates capability scanning across Java source files.
|
||||
/// </summary>
|
||||
internal static class JavaCapabilityScanBuilder
|
||||
{
|
||||
private static readonly string[] SourceExtensions = [".java"];
|
||||
|
||||
/// <summary>
|
||||
/// Scans a Java project directory for capabilities.
|
||||
/// </summary>
|
||||
public static JavaCapabilityScanResult ScanProject(string projectPath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(projectPath);
|
||||
|
||||
if (!Directory.Exists(projectPath))
|
||||
{
|
||||
return JavaCapabilityScanResult.Empty;
|
||||
}
|
||||
|
||||
var allEvidences = new List<JavaCapabilityEvidence>();
|
||||
|
||||
foreach (var sourceFile in EnumerateSourceFiles(projectPath))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
var content = File.ReadAllText(sourceFile);
|
||||
var relativePath = Path.GetRelativePath(projectPath, sourceFile);
|
||||
var evidences = JavaCapabilityScanner.ScanFile(content, relativePath);
|
||||
allEvidences.AddRange(evidences);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Skip inaccessible files
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
// Skip inaccessible files
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate and sort for determinism
|
||||
var finalEvidences = allEvidences
|
||||
.DistinctBy(e => e.DeduplicationKey)
|
||||
.OrderBy(e => e.SourceFile, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.SourceLine)
|
||||
.ThenBy(e => e.Kind)
|
||||
.ToList();
|
||||
|
||||
return new JavaCapabilityScanResult(finalEvidences);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scans a Maven/Gradle project for capabilities.
|
||||
/// </summary>
|
||||
public static JavaCapabilityScanResult ScanMavenProject(string pomPath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(pomPath);
|
||||
|
||||
var projectDir = File.Exists(pomPath)
|
||||
? Path.GetDirectoryName(pomPath) ?? pomPath
|
||||
: pomPath;
|
||||
|
||||
if (!Directory.Exists(projectDir))
|
||||
{
|
||||
return JavaCapabilityScanResult.Empty;
|
||||
}
|
||||
|
||||
// Scan src/main/java and src/test/java
|
||||
var allEvidences = new List<JavaCapabilityEvidence>();
|
||||
|
||||
var srcMainJava = Path.Combine(projectDir, "src", "main", "java");
|
||||
if (Directory.Exists(srcMainJava))
|
||||
{
|
||||
var result = ScanProject(srcMainJava, cancellationToken);
|
||||
allEvidences.AddRange(result.Evidences);
|
||||
}
|
||||
|
||||
var srcTestJava = Path.Combine(projectDir, "src", "test", "java");
|
||||
if (Directory.Exists(srcTestJava))
|
||||
{
|
||||
var result = ScanProject(srcTestJava, cancellationToken);
|
||||
allEvidences.AddRange(result.Evidences);
|
||||
}
|
||||
|
||||
// Also scan root if no Maven structure
|
||||
if (allEvidences.Count == 0)
|
||||
{
|
||||
return ScanProject(projectDir, cancellationToken);
|
||||
}
|
||||
|
||||
var finalEvidences = allEvidences
|
||||
.DistinctBy(e => e.DeduplicationKey)
|
||||
.OrderBy(e => e.SourceFile, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.SourceLine)
|
||||
.ThenBy(e => e.Kind)
|
||||
.ToList();
|
||||
|
||||
return new JavaCapabilityScanResult(finalEvidences);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scans specific Java source content.
|
||||
/// </summary>
|
||||
public static JavaCapabilityScanResult ScanContent(string content, string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
return JavaCapabilityScanResult.Empty;
|
||||
}
|
||||
|
||||
var evidences = JavaCapabilityScanner.ScanFile(content, filePath);
|
||||
return new JavaCapabilityScanResult(evidences.ToList());
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateSourceFiles(string rootPath)
|
||||
{
|
||||
var options = new EnumerationOptions
|
||||
{
|
||||
RecurseSubdirectories = true,
|
||||
IgnoreInaccessible = true,
|
||||
MaxRecursionDepth = 30
|
||||
};
|
||||
|
||||
foreach (var ext in SourceExtensions)
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(rootPath, $"*{ext}", options))
|
||||
{
|
||||
// Skip build output directories
|
||||
if (file.Contains($"{Path.DirectorySeparatorChar}target{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.DirectorySeparatorChar}build{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}target{Path.AltDirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}build{Path.AltDirectorySeparatorChar}"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip generated sources
|
||||
if (file.Contains($"{Path.DirectorySeparatorChar}generated-sources{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.DirectorySeparatorChar}generated-test-sources{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}generated-sources{Path.AltDirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}generated-test-sources{Path.AltDirectorySeparatorChar}"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip annotation processor output
|
||||
if (file.Contains($"{Path.DirectorySeparatorChar}apt{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}apt{Path.AltDirectorySeparatorChar}"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip IDE output
|
||||
if (file.Contains($"{Path.DirectorySeparatorChar}.idea{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.DirectorySeparatorChar}.gradle{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}.idea{Path.AltDirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}.gradle{Path.AltDirectorySeparatorChar}"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Aggregates capability scan results from Java source code analysis.
|
||||
/// </summary>
|
||||
internal sealed class JavaCapabilityScanResult
|
||||
{
|
||||
private readonly IReadOnlyList<JavaCapabilityEvidence> _evidences;
|
||||
private ILookup<CapabilityKind, JavaCapabilityEvidence>? _byKind;
|
||||
private ILookup<CapabilityRisk, JavaCapabilityEvidence>? _byRisk;
|
||||
private ILookup<string, JavaCapabilityEvidence>? _byFile;
|
||||
|
||||
public JavaCapabilityScanResult(IReadOnlyList<JavaCapabilityEvidence> evidences)
|
||||
{
|
||||
_evidences = evidences ?? Array.Empty<JavaCapabilityEvidence>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All capability evidences found.
|
||||
/// </summary>
|
||||
public IReadOnlyList<JavaCapabilityEvidence> Evidences => _evidences;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether any capabilities were detected.
|
||||
/// </summary>
|
||||
public bool HasCapabilities => _evidences.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets evidences grouped by capability kind.
|
||||
/// </summary>
|
||||
public ILookup<CapabilityKind, JavaCapabilityEvidence> EvidencesByKind
|
||||
=> _byKind ??= _evidences.ToLookup(e => e.Kind);
|
||||
|
||||
/// <summary>
|
||||
/// Gets evidences grouped by risk level.
|
||||
/// </summary>
|
||||
public ILookup<CapabilityRisk, JavaCapabilityEvidence> EvidencesByRisk
|
||||
=> _byRisk ??= _evidences.ToLookup(e => e.Risk);
|
||||
|
||||
/// <summary>
|
||||
/// Gets evidences grouped by source file.
|
||||
/// </summary>
|
||||
public ILookup<string, JavaCapabilityEvidence> EvidencesByFile
|
||||
=> _byFile ??= _evidences.ToLookup(e => e.SourceFile, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all critical risk evidences.
|
||||
/// </summary>
|
||||
public IEnumerable<JavaCapabilityEvidence> CriticalRiskEvidences
|
||||
=> _evidences.Where(e => e.Risk == CapabilityRisk.Critical);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all high risk evidences.
|
||||
/// </summary>
|
||||
public IEnumerable<JavaCapabilityEvidence> HighRiskEvidences
|
||||
=> _evidences.Where(e => e.Risk == CapabilityRisk.High);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of detected capability kinds.
|
||||
/// </summary>
|
||||
public IReadOnlySet<CapabilityKind> DetectedKinds
|
||||
=> _evidences.Select(e => e.Kind).ToHashSet();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the highest risk level found.
|
||||
/// </summary>
|
||||
public CapabilityRisk HighestRisk
|
||||
=> _evidences.Count > 0
|
||||
? _evidences.Max(e => e.Risk)
|
||||
: CapabilityRisk.Low;
|
||||
|
||||
/// <summary>
|
||||
/// Gets evidences for a specific capability kind.
|
||||
/// </summary>
|
||||
public IEnumerable<JavaCapabilityEvidence> GetByKind(CapabilityKind kind)
|
||||
=> EvidencesByKind[kind];
|
||||
|
||||
/// <summary>
|
||||
/// Gets evidences at or above a specific risk level.
|
||||
/// </summary>
|
||||
public IEnumerable<JavaCapabilityEvidence> GetByMinimumRisk(CapabilityRisk minRisk)
|
||||
=> _evidences.Where(e => e.Risk >= minRisk);
|
||||
|
||||
/// <summary>
|
||||
/// Creates metadata entries for the scan result.
|
||||
/// </summary>
|
||||
public IEnumerable<KeyValuePair<string, string?>> CreateMetadata()
|
||||
{
|
||||
yield return new KeyValuePair<string, string?>(
|
||||
"capability.total_count",
|
||||
_evidences.Count.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
foreach (var kindGroup in EvidencesByKind.OrderBy(g => g.Key.ToString(), StringComparer.Ordinal))
|
||||
{
|
||||
yield return new KeyValuePair<string, string?>(
|
||||
$"capability.{kindGroup.Key.ToString().ToLowerInvariant()}_count",
|
||||
kindGroup.Count().ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
var criticalCount = CriticalRiskEvidences.Count();
|
||||
var highCount = HighRiskEvidences.Count();
|
||||
var mediumCount = _evidences.Count(e => e.Risk == CapabilityRisk.Medium);
|
||||
var lowCount = _evidences.Count(e => e.Risk == CapabilityRisk.Low);
|
||||
|
||||
yield return new KeyValuePair<string, string?>("capability.critical_risk_count", criticalCount.ToString(CultureInfo.InvariantCulture));
|
||||
yield return new KeyValuePair<string, string?>("capability.high_risk_count", highCount.ToString(CultureInfo.InvariantCulture));
|
||||
yield return new KeyValuePair<string, string?>("capability.medium_risk_count", mediumCount.ToString(CultureInfo.InvariantCulture));
|
||||
yield return new KeyValuePair<string, string?>("capability.low_risk_count", lowCount.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
if (_evidences.Count > 0)
|
||||
{
|
||||
yield return new KeyValuePair<string, string?>(
|
||||
"capability.highest_risk",
|
||||
HighestRisk.ToString().ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (DetectedKinds.Count > 0)
|
||||
{
|
||||
yield return new KeyValuePair<string, string?>(
|
||||
"capability.detected_kinds",
|
||||
string.Join(';', DetectedKinds.OrderBy(k => k.ToString(), StringComparer.Ordinal).Select(k => k.ToString().ToLowerInvariant())));
|
||||
}
|
||||
|
||||
var criticalFiles = CriticalRiskEvidences
|
||||
.Select(e => e.SourceFile)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(f => f, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
if (criticalFiles.Count > 0)
|
||||
{
|
||||
yield return new KeyValuePair<string, string?>(
|
||||
"capability.critical_files",
|
||||
string.Join(';', criticalFiles.Take(10)));
|
||||
|
||||
if (criticalFiles.Count > 10)
|
||||
{
|
||||
yield return new KeyValuePair<string, string?>(
|
||||
"capability.critical_files_truncated",
|
||||
"true");
|
||||
}
|
||||
}
|
||||
|
||||
var uniquePatterns = _evidences
|
||||
.Select(e => e.Pattern)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Count();
|
||||
|
||||
yield return new KeyValuePair<string, string?>(
|
||||
"capability.unique_pattern_count",
|
||||
uniquePatterns.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a summary of detected capabilities.
|
||||
/// </summary>
|
||||
public JavaCapabilitySummary CreateSummary()
|
||||
{
|
||||
return new JavaCapabilitySummary(
|
||||
HasExec: EvidencesByKind[CapabilityKind.Exec].Any(),
|
||||
HasFilesystem: EvidencesByKind[CapabilityKind.Filesystem].Any(),
|
||||
HasNetwork: EvidencesByKind[CapabilityKind.Network].Any(),
|
||||
HasEnvironment: EvidencesByKind[CapabilityKind.Environment].Any(),
|
||||
HasSerialization: EvidencesByKind[CapabilityKind.Serialization].Any(),
|
||||
HasCrypto: EvidencesByKind[CapabilityKind.Crypto].Any(),
|
||||
HasDatabase: EvidencesByKind[CapabilityKind.Database].Any(),
|
||||
HasDynamicCode: EvidencesByKind[CapabilityKind.DynamicCode].Any(),
|
||||
HasReflection: EvidencesByKind[CapabilityKind.Reflection].Any(),
|
||||
HasNativeCode: EvidencesByKind[CapabilityKind.NativeCode].Any(),
|
||||
HasJndi: EvidencesByKind[CapabilityKind.Other].Any(e => e.Pattern.Contains("JNDI", StringComparison.OrdinalIgnoreCase)),
|
||||
CriticalCount: CriticalRiskEvidences.Count(),
|
||||
HighRiskCount: HighRiskEvidences.Count(),
|
||||
TotalCount: _evidences.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Empty scan result with no capabilities detected.
|
||||
/// </summary>
|
||||
public static JavaCapabilityScanResult Empty { get; } = new(Array.Empty<JavaCapabilityEvidence>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Summary of detected Java capabilities.
|
||||
/// </summary>
|
||||
internal sealed record JavaCapabilitySummary(
|
||||
bool HasExec,
|
||||
bool HasFilesystem,
|
||||
bool HasNetwork,
|
||||
bool HasEnvironment,
|
||||
bool HasSerialization,
|
||||
bool HasCrypto,
|
||||
bool HasDatabase,
|
||||
bool HasDynamicCode,
|
||||
bool HasReflection,
|
||||
bool HasNativeCode,
|
||||
bool HasJndi,
|
||||
int CriticalCount,
|
||||
int HighRiskCount,
|
||||
int TotalCount)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates metadata entries for the summary.
|
||||
/// </summary>
|
||||
public IEnumerable<KeyValuePair<string, string?>> CreateMetadata()
|
||||
{
|
||||
yield return new KeyValuePair<string, string?>("capability.has_exec", HasExec.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.has_filesystem", HasFilesystem.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.has_network", HasNetwork.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.has_environment", HasEnvironment.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.has_serialization", HasSerialization.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.has_crypto", HasCrypto.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.has_database", HasDatabase.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.has_dynamic_code", HasDynamicCode.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.has_reflection", HasReflection.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.has_native_code", HasNativeCode.ToString().ToLowerInvariant());
|
||||
yield return new KeyValuePair<string, string?>("capability.has_jndi", HasJndi.ToString().ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Scans Java source files for security-relevant capabilities.
|
||||
/// Detects patterns for command execution, file I/O, network access,
|
||||
/// serialization, reflection, JNI, JNDI, and more.
|
||||
/// </summary>
|
||||
internal static class JavaCapabilityScanner
|
||||
{
|
||||
// ========================================
|
||||
// EXEC - Command/Process Execution (Critical)
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] ExecPatterns =
|
||||
[
|
||||
// Runtime.exec - most common command execution
|
||||
(new Regex(@"Runtime\s*\.\s*getRuntime\s*\(\s*\)\s*\.\s*exec\s*\(", RegexOptions.Compiled), "Runtime.exec", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"\.exec\s*\(\s*(?:new\s+String\s*\[\]|"")", RegexOptions.Compiled), "Runtime.exec(String[])", CapabilityRisk.Critical, 0.95f),
|
||||
|
||||
// ProcessBuilder
|
||||
(new Regex(@"new\s+ProcessBuilder\s*\(", RegexOptions.Compiled), "ProcessBuilder", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"ProcessBuilder\s*\.\s*command\s*\(", RegexOptions.Compiled), "ProcessBuilder.command", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"ProcessBuilder\s*\.\s*start\s*\(", RegexOptions.Compiled), "ProcessBuilder.start", CapabilityRisk.Critical, 0.95f),
|
||||
|
||||
// Direct Process
|
||||
(new Regex(@"Process\s+\w+\s*=", RegexOptions.Compiled), "Process variable", CapabilityRisk.High, 0.7f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// FILESYSTEM - File/Directory Operations
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] FilesystemPatterns =
|
||||
[
|
||||
// File streams
|
||||
(new Regex(@"new\s+FileInputStream\s*\(", RegexOptions.Compiled), "FileInputStream", CapabilityRisk.Medium, 0.95f),
|
||||
(new Regex(@"new\s+FileOutputStream\s*\(", RegexOptions.Compiled), "FileOutputStream", CapabilityRisk.High, 0.95f),
|
||||
(new Regex(@"new\s+FileReader\s*\(", RegexOptions.Compiled), "FileReader", CapabilityRisk.Medium, 0.95f),
|
||||
(new Regex(@"new\s+FileWriter\s*\(", RegexOptions.Compiled), "FileWriter", CapabilityRisk.High, 0.95f),
|
||||
(new Regex(@"new\s+RandomAccessFile\s*\(", RegexOptions.Compiled), "RandomAccessFile", CapabilityRisk.High, 0.95f),
|
||||
|
||||
// NIO Files API
|
||||
(new Regex(@"Files\s*\.\s*(?:read|write|copy|move|delete|createFile|createDirectory|createTempFile|createTempDirectory)\w*\s*\(", RegexOptions.Compiled), "Files.*", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"Files\s*\.\s*(?:newInputStream|newOutputStream|newBufferedReader|newBufferedWriter)\s*\(", RegexOptions.Compiled), "Files.new*Stream", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"Files\s*\.\s*walkFileTree\s*\(", RegexOptions.Compiled), "Files.walkFileTree", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// File object operations
|
||||
(new Regex(@"\.delete\s*\(\s*\)", RegexOptions.Compiled), "File.delete", CapabilityRisk.High, 0.8f),
|
||||
(new Regex(@"\.deleteOnExit\s*\(\s*\)", RegexOptions.Compiled), "File.deleteOnExit", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"\.setReadable\s*\(", RegexOptions.Compiled), "File.setReadable", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"\.setWritable\s*\(", RegexOptions.Compiled), "File.setWritable", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"\.setExecutable\s*\(", RegexOptions.Compiled), "File.setExecutable", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"\.createNewFile\s*\(\s*\)", RegexOptions.Compiled), "File.createNewFile", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"\.mkdirs?\s*\(\s*\)", RegexOptions.Compiled), "File.mkdir(s)", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"\.renameTo\s*\(", RegexOptions.Compiled), "File.renameTo", CapabilityRisk.Medium, 0.8f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// NETWORK - Network I/O
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] NetworkPatterns =
|
||||
[
|
||||
// Sockets
|
||||
(new Regex(@"new\s+Socket\s*\(", RegexOptions.Compiled), "Socket", CapabilityRisk.Medium, 0.95f),
|
||||
(new Regex(@"new\s+ServerSocket\s*\(", RegexOptions.Compiled), "ServerSocket", CapabilityRisk.Medium, 0.95f),
|
||||
(new Regex(@"new\s+DatagramSocket\s*\(", RegexOptions.Compiled), "DatagramSocket", CapabilityRisk.Medium, 0.95f),
|
||||
(new Regex(@"SocketChannel\s*\.\s*open\s*\(", RegexOptions.Compiled), "SocketChannel.open", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"ServerSocketChannel\s*\.\s*open\s*\(", RegexOptions.Compiled), "ServerSocketChannel.open", CapabilityRisk.Medium, 0.9f),
|
||||
|
||||
// URL connections
|
||||
(new Regex(@"\.openConnection\s*\(\s*\)", RegexOptions.Compiled), "URL.openConnection", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"\.openStream\s*\(\s*\)", RegexOptions.Compiled), "URL.openStream", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"new\s+URL\s*\(", RegexOptions.Compiled), "URL constructor", CapabilityRisk.Low, 0.7f),
|
||||
|
||||
// HTTP clients
|
||||
(new Regex(@"HttpURLConnection", RegexOptions.Compiled), "HttpURLConnection", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"HttpsURLConnection", RegexOptions.Compiled), "HttpsURLConnection", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"HttpClient\s*\.\s*newBuilder\s*\(", RegexOptions.Compiled), "HttpClient.newBuilder", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"HttpClient\s*\.\s*newHttpClient\s*\(", RegexOptions.Compiled), "HttpClient.newHttpClient", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"HttpRequest\s*\.\s*newBuilder\s*\(", RegexOptions.Compiled), "HttpRequest.newBuilder", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// Apache/OkHttp clients
|
||||
(new Regex(@"new\s+CloseableHttpClient", RegexOptions.Compiled), "CloseableHttpClient", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"HttpClients\s*\.\s*create", RegexOptions.Compiled), "HttpClients.create", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"new\s+OkHttpClient", RegexOptions.Compiled), "OkHttpClient", CapabilityRisk.Medium, 0.85f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// ENVIRONMENT - Environment Variables
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] EnvironmentPatterns =
|
||||
[
|
||||
(new Regex(@"System\s*\.\s*getenv\s*\(", RegexOptions.Compiled), "System.getenv", CapabilityRisk.Medium, 0.95f),
|
||||
(new Regex(@"System\s*\.\s*getProperty\s*\(", RegexOptions.Compiled), "System.getProperty", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"System\s*\.\s*setProperty\s*\(", RegexOptions.Compiled), "System.setProperty", CapabilityRisk.High, 0.95f),
|
||||
(new Regex(@"System\s*\.\s*clearProperty\s*\(", RegexOptions.Compiled), "System.clearProperty", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"System\s*\.\s*getProperties\s*\(\s*\)", RegexOptions.Compiled), "System.getProperties", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"System\s*\.\s*setProperties\s*\(", RegexOptions.Compiled), "System.setProperties", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"ProcessBuilder\s*\.\s*environment\s*\(", RegexOptions.Compiled), "ProcessBuilder.environment", CapabilityRisk.High, 0.9f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// SERIALIZATION - Object Serialization (Critical for deserialization attacks)
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] SerializationPatterns =
|
||||
[
|
||||
// Java native serialization - HIGH RISK for deserialization attacks
|
||||
(new Regex(@"new\s+ObjectInputStream\s*\(", RegexOptions.Compiled), "ObjectInputStream", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"\.readObject\s*\(\s*\)", RegexOptions.Compiled), "readObject", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"\.readUnshared\s*\(\s*\)", RegexOptions.Compiled), "readUnshared", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"new\s+ObjectOutputStream\s*\(", RegexOptions.Compiled), "ObjectOutputStream", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"\.writeObject\s*\(", RegexOptions.Compiled), "writeObject", CapabilityRisk.Medium, 0.75f),
|
||||
|
||||
// XMLDecoder - known vulnerability vector
|
||||
(new Regex(@"new\s+XMLDecoder\s*\(", RegexOptions.Compiled), "XMLDecoder", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"XMLDecoder\s*\.\s*readObject\s*\(", RegexOptions.Compiled), "XMLDecoder.readObject", CapabilityRisk.Critical, 1.0f),
|
||||
|
||||
// XStream - historically vulnerable
|
||||
(new Regex(@"new\s+XStream\s*\(", RegexOptions.Compiled), "XStream", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"xstream\s*\.\s*fromXML\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase), "XStream.fromXML", CapabilityRisk.Critical, 0.95f),
|
||||
|
||||
// Jackson/JSON - generally safer but check for polymorphic deserialization
|
||||
(new Regex(@"new\s+ObjectMapper\s*\(", RegexOptions.Compiled), "ObjectMapper", CapabilityRisk.Low, 0.7f),
|
||||
(new Regex(@"\.readValue\s*\(", RegexOptions.Compiled), "ObjectMapper.readValue", CapabilityRisk.Medium, 0.75f),
|
||||
(new Regex(@"@JsonTypeInfo", RegexOptions.Compiled), "Jackson polymorphic", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"enableDefaultTyping\s*\(", RegexOptions.Compiled), "Jackson defaultTyping", CapabilityRisk.Critical, 0.95f),
|
||||
|
||||
// Kryo
|
||||
(new Regex(@"new\s+Kryo\s*\(", RegexOptions.Compiled), "Kryo", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"\.readObject\s*\(.*Kryo", RegexOptions.Compiled | RegexOptions.IgnoreCase), "Kryo.readObject", CapabilityRisk.High, 0.85f),
|
||||
|
||||
// SnakeYAML - known for unsafe defaults
|
||||
(new Regex(@"new\s+Yaml\s*\(", RegexOptions.Compiled), "SnakeYAML", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"yaml\s*\.\s*load\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase), "Yaml.load", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"yaml\s*\.\s*loadAs\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase), "Yaml.loadAs", CapabilityRisk.High, 0.9f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// CRYPTO - Cryptographic Operations
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] CryptoPatterns =
|
||||
[
|
||||
(new Regex(@"MessageDigest\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "MessageDigest", CapabilityRisk.Low, 0.9f),
|
||||
(new Regex(@"Cipher\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "Cipher", CapabilityRisk.Low, 0.95f),
|
||||
(new Regex(@"Mac\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "Mac", CapabilityRisk.Low, 0.9f),
|
||||
(new Regex(@"Signature\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "Signature", CapabilityRisk.Low, 0.9f),
|
||||
(new Regex(@"KeyGenerator\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "KeyGenerator", CapabilityRisk.Low, 0.9f),
|
||||
(new Regex(@"KeyPairGenerator\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "KeyPairGenerator", CapabilityRisk.Low, 0.9f),
|
||||
(new Regex(@"SecretKeyFactory\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "SecretKeyFactory", CapabilityRisk.Low, 0.9f),
|
||||
(new Regex(@"KeyFactory\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "KeyFactory", CapabilityRisk.Low, 0.9f),
|
||||
(new Regex(@"SecureRandom", RegexOptions.Compiled), "SecureRandom", CapabilityRisk.Low, 0.85f),
|
||||
(new Regex(@"KeyStore\s*\.\s*getInstance\s*\(", RegexOptions.Compiled), "KeyStore", CapabilityRisk.Low, 0.9f),
|
||||
|
||||
// Weak crypto patterns
|
||||
(new Regex(@"""(?:MD5|SHA-?1|DES|RC4|RC2)""", RegexOptions.Compiled | RegexOptions.IgnoreCase), "Weak crypto algorithm", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"DESede|TripleDES", RegexOptions.Compiled | RegexOptions.IgnoreCase), "3DES (deprecated)", CapabilityRisk.Medium, 0.8f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// DATABASE - Database Access
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] DatabasePatterns =
|
||||
[
|
||||
(new Regex(@"DriverManager\s*\.\s*getConnection\s*\(", RegexOptions.Compiled), "DriverManager.getConnection", CapabilityRisk.Medium, 0.95f),
|
||||
(new Regex(@"DataSource\s*\.\s*getConnection\s*\(", RegexOptions.Compiled), "DataSource.getConnection", CapabilityRisk.Medium, 0.9f),
|
||||
|
||||
// Statement execution
|
||||
(new Regex(@"\.executeQuery\s*\(", RegexOptions.Compiled), "Statement.executeQuery", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"\.executeUpdate\s*\(", RegexOptions.Compiled), "Statement.executeUpdate", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"\.execute\s*\([^)]*\)", RegexOptions.Compiled), "Statement.execute", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"\.executeBatch\s*\(\s*\)", RegexOptions.Compiled), "Statement.executeBatch", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// Prepared statements (safer)
|
||||
(new Regex(@"\.prepareStatement\s*\(", RegexOptions.Compiled), "PreparedStatement", CapabilityRisk.Low, 0.85f),
|
||||
(new Regex(@"\.prepareCall\s*\(", RegexOptions.Compiled), "CallableStatement", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// SQL injection patterns - string concatenation with SQL
|
||||
(new Regex(@"""(?:SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE)\s+.*""\s*\+", RegexOptions.Compiled | RegexOptions.IgnoreCase), "SQL concatenation", CapabilityRisk.Critical, 0.9f),
|
||||
(new Regex(@"String\s+.*=\s*"".*(?:SELECT|INSERT|UPDATE|DELETE).*""\s*\+", RegexOptions.Compiled | RegexOptions.IgnoreCase), "SQL string concat", CapabilityRisk.Critical, 0.85f),
|
||||
|
||||
// JPA/Hibernate
|
||||
(new Regex(@"\.createQuery\s*\(", RegexOptions.Compiled), "EntityManager.createQuery", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"\.createNativeQuery\s*\(", RegexOptions.Compiled), "Native SQL query", CapabilityRisk.High, 0.85f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// DYNAMIC CODE - Dynamic Code Execution
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] DynamicCodePatterns =
|
||||
[
|
||||
// ScriptEngine (JavaScript, Groovy, etc.)
|
||||
(new Regex(@"ScriptEngineManager\s*\.\s*getEngineByName\s*\(", RegexOptions.Compiled), "ScriptEngineManager", CapabilityRisk.High, 0.95f),
|
||||
(new Regex(@"new\s+ScriptEngineManager\s*\(", RegexOptions.Compiled), "ScriptEngineManager", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"\.eval\s*\([^)]*\)", RegexOptions.Compiled), "ScriptEngine.eval", CapabilityRisk.Critical, 0.9f),
|
||||
|
||||
// MethodHandles
|
||||
(new Regex(@"MethodHandles\s*\.\s*lookup\s*\(", RegexOptions.Compiled), "MethodHandles.lookup", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"MethodHandle\s*\.\s*invoke\w*\s*\(", RegexOptions.Compiled), "MethodHandle.invoke", CapabilityRisk.High, 0.9f),
|
||||
|
||||
// Java Compiler API
|
||||
(new Regex(@"ToolProvider\s*\.\s*getSystemJavaCompiler\s*\(", RegexOptions.Compiled), "JavaCompiler", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"JavaCompiler\s*\.\s*getTask\s*\(", RegexOptions.Compiled), "JavaCompiler.getTask", CapabilityRisk.Critical, 0.95f),
|
||||
|
||||
// Expression Language (EL) injection
|
||||
(new Regex(@"ValueExpression\s*\.\s*getValue\s*\(", RegexOptions.Compiled), "EL ValueExpression", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"MethodExpression\s*\.\s*invoke\s*\(", RegexOptions.Compiled), "EL MethodExpression", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"ExpressionFactory\s*\.\s*createValueExpression\s*\(", RegexOptions.Compiled), "EL ExpressionFactory", CapabilityRisk.High, 0.8f),
|
||||
|
||||
// SpEL (Spring Expression Language)
|
||||
(new Regex(@"SpelExpressionParser", RegexOptions.Compiled), "SpEL Parser", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"new\s+SpelExpressionParser\s*\(", RegexOptions.Compiled), "SpEL Parser", CapabilityRisk.High, 0.95f),
|
||||
(new Regex(@"\.parseExpression\s*\(", RegexOptions.Compiled), "SpEL parseExpression", CapabilityRisk.High, 0.85f),
|
||||
|
||||
// OGNL (Object-Graph Navigation Language)
|
||||
(new Regex(@"Ognl\s*\.\s*getValue\s*\(", RegexOptions.Compiled), "OGNL.getValue", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"Ognl\s*\.\s*setValue\s*\(", RegexOptions.Compiled), "OGNL.setValue", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"OgnlUtil", RegexOptions.Compiled), "OgnlUtil", CapabilityRisk.High, 0.85f),
|
||||
|
||||
// Velocity/Freemarker templates
|
||||
(new Regex(@"VelocityEngine", RegexOptions.Compiled), "Velocity", CapabilityRisk.High, 0.8f),
|
||||
(new Regex(@"Velocity\s*\.\s*evaluate\s*\(", RegexOptions.Compiled), "Velocity.evaluate", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"Configuration\s*\.\s*setTemplateLoader", RegexOptions.Compiled), "Freemarker", CapabilityRisk.Medium, 0.75f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// REFLECTION - Code Introspection
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] ReflectionPatterns =
|
||||
[
|
||||
// Class loading
|
||||
(new Regex(@"Class\s*\.\s*forName\s*\(", RegexOptions.Compiled), "Class.forName", CapabilityRisk.High, 0.95f),
|
||||
(new Regex(@"ClassLoader\s*\.\s*loadClass\s*\(", RegexOptions.Compiled), "ClassLoader.loadClass", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"\.loadClass\s*\(", RegexOptions.Compiled), "loadClass", CapabilityRisk.High, 0.8f),
|
||||
(new Regex(@"\.defineClass\s*\(", RegexOptions.Compiled), "defineClass", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"new\s+URLClassLoader\s*\(", RegexOptions.Compiled), "URLClassLoader", CapabilityRisk.High, 0.9f),
|
||||
|
||||
// Method/Field invocation
|
||||
(new Regex(@"Method\s*\.\s*invoke\s*\(", RegexOptions.Compiled), "Method.invoke", CapabilityRisk.High, 0.95f),
|
||||
(new Regex(@"\.invoke\s*\([^)]*\)", RegexOptions.Compiled), "invoke", CapabilityRisk.Medium, 0.7f),
|
||||
(new Regex(@"\.getMethod\s*\(", RegexOptions.Compiled), "getMethod", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"\.getDeclaredMethod\s*\(", RegexOptions.Compiled), "getDeclaredMethod", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"\.getDeclaredField\s*\(", RegexOptions.Compiled), "getDeclaredField", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"Field\s*\.\s*set\s*\(", RegexOptions.Compiled), "Field.set", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"\.setAccessible\s*\(\s*true\s*\)", RegexOptions.Compiled), "setAccessible(true)", CapabilityRisk.High, 0.95f),
|
||||
|
||||
// Constructor invocation
|
||||
(new Regex(@"Constructor\s*\.\s*newInstance\s*\(", RegexOptions.Compiled), "Constructor.newInstance", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"\.getDeclaredConstructor\s*\(", RegexOptions.Compiled), "getDeclaredConstructor", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"\.newInstance\s*\(", RegexOptions.Compiled), "newInstance", CapabilityRisk.High, 0.75f),
|
||||
|
||||
// Proxy creation
|
||||
(new Regex(@"Proxy\s*\.\s*newProxyInstance\s*\(", RegexOptions.Compiled), "Proxy.newProxyInstance", CapabilityRisk.High, 0.9f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// NATIVE CODE - JNI/JNA
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] NativeCodePatterns =
|
||||
[
|
||||
// JNI library loading
|
||||
(new Regex(@"System\s*\.\s*loadLibrary\s*\(", RegexOptions.Compiled), "System.loadLibrary", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"System\s*\.\s*load\s*\(", RegexOptions.Compiled), "System.load", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"Runtime\s*\.\s*load\w*\s*\(", RegexOptions.Compiled), "Runtime.load", CapabilityRisk.Critical, 0.95f),
|
||||
|
||||
// JNA (Java Native Access)
|
||||
(new Regex(@"Native\s*\.\s*load\w*\s*\(", RegexOptions.Compiled), "JNA Native.load", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"Native\s*\.\s*getLibrary\s*\(", RegexOptions.Compiled), "JNA Native.getLibrary", CapabilityRisk.Critical, 0.9f),
|
||||
(new Regex(@"extends\s+(?:Structure|StdCallLibrary|Library)", RegexOptions.Compiled), "JNA Structure/Library", CapabilityRisk.High, 0.85f),
|
||||
|
||||
// JNR (Java Native Runtime)
|
||||
(new Regex(@"LibraryLoader\s*\.\s*create\s*\(", RegexOptions.Compiled), "JNR LibraryLoader", CapabilityRisk.High, 0.85f),
|
||||
|
||||
// native method declaration
|
||||
(new Regex(@"\bnative\s+\w+\s+\w+\s*\(", RegexOptions.Compiled), "native method", CapabilityRisk.High, 0.9f),
|
||||
|
||||
// Unsafe
|
||||
(new Regex(@"Unsafe\s*\.\s*getUnsafe\s*\(", RegexOptions.Compiled), "Unsafe.getUnsafe", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"theUnsafe", RegexOptions.Compiled), "Unsafe field access", CapabilityRisk.Critical, 0.9f),
|
||||
(new Regex(@"\.allocateInstance\s*\(", RegexOptions.Compiled), "Unsafe.allocateInstance", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"\.putObject\s*\(", RegexOptions.Compiled), "Unsafe.putObject", CapabilityRisk.Critical, 0.9f),
|
||||
(new Regex(@"\.getObject\s*\(", RegexOptions.Compiled), "Unsafe.getObject", CapabilityRisk.High, 0.85f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// JNDI - Java Naming and Directory Interface
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] JndiPatterns =
|
||||
[
|
||||
// JNDI lookups - Log4Shell attack vector
|
||||
(new Regex(@"new\s+InitialContext\s*\(", RegexOptions.Compiled), "InitialContext", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"InitialContext\s*\.\s*lookup\s*\(", RegexOptions.Compiled), "InitialContext.lookup", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"\.lookup\s*\(\s*[""'][^""']*(?:ldap|rmi|dns|corba):", RegexOptions.Compiled | RegexOptions.IgnoreCase), "JNDI remote lookup", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"Context\s*\.\s*lookup\s*\(", RegexOptions.Compiled), "Context.lookup", CapabilityRisk.High, 0.85f),
|
||||
|
||||
// LDAP
|
||||
(new Regex(@"new\s+InitialLdapContext\s*\(", RegexOptions.Compiled), "InitialLdapContext", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"new\s+InitialDirContext\s*\(", RegexOptions.Compiled), "InitialDirContext", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"LdapContext\s*\.\s*search\s*\(", RegexOptions.Compiled), "LdapContext.search", CapabilityRisk.Medium, 0.8f),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Scans a Java source file for capability usages.
|
||||
/// </summary>
|
||||
public static IEnumerable<JavaCapabilityEvidence> ScanFile(string content, string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Strip comments for more accurate detection
|
||||
var cleanedContent = StripComments(content);
|
||||
var lines = cleanedContent.Split('\n');
|
||||
|
||||
for (var lineNumber = 0; lineNumber < lines.Length; lineNumber++)
|
||||
{
|
||||
var line = lines[lineNumber];
|
||||
var lineNum = lineNumber + 1;
|
||||
|
||||
// Exec patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, ExecPatterns, CapabilityKind.Exec))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Filesystem patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, FilesystemPatterns, CapabilityKind.Filesystem))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Network patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, NetworkPatterns, CapabilityKind.Network))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Environment patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, EnvironmentPatterns, CapabilityKind.Environment))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Serialization patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, SerializationPatterns, CapabilityKind.Serialization))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Crypto patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, CryptoPatterns, CapabilityKind.Crypto))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Database patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, DatabasePatterns, CapabilityKind.Database))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Dynamic code patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, DynamicCodePatterns, CapabilityKind.DynamicCode))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Reflection patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, ReflectionPatterns, CapabilityKind.Reflection))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Native code patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, NativeCodePatterns, CapabilityKind.NativeCode))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// JNDI patterns (categorized as Other since it's Java-specific)
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, JndiPatterns, CapabilityKind.Other))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<JavaCapabilityEvidence> ScanPatterns(
|
||||
string line,
|
||||
int lineNumber,
|
||||
string filePath,
|
||||
(Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] patterns,
|
||||
CapabilityKind kind)
|
||||
{
|
||||
foreach (var (pattern, name, risk, confidence) in patterns)
|
||||
{
|
||||
if (pattern.IsMatch(line))
|
||||
{
|
||||
yield return new JavaCapabilityEvidence(
|
||||
kind: kind,
|
||||
sourceFile: filePath,
|
||||
sourceLine: lineNumber,
|
||||
pattern: name,
|
||||
snippet: line.Trim(),
|
||||
confidence: confidence,
|
||||
risk: risk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strips single-line (//) and multi-line (/* */) comments from Java source.
|
||||
/// </summary>
|
||||
private static string StripComments(string content)
|
||||
{
|
||||
var sb = new StringBuilder(content.Length);
|
||||
var i = 0;
|
||||
var inString = false;
|
||||
var inChar = false;
|
||||
var stringChar = '"';
|
||||
|
||||
while (i < content.Length)
|
||||
{
|
||||
// Handle escape sequences in strings
|
||||
if ((inString || inChar) && content[i] == '\\' && i + 1 < content.Length)
|
||||
{
|
||||
sb.Append(content[i]);
|
||||
sb.Append(content[i + 1]);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle string literals
|
||||
if (!inChar && content[i] == '"')
|
||||
{
|
||||
if (!inString)
|
||||
{
|
||||
inString = true;
|
||||
stringChar = '"';
|
||||
}
|
||||
else if (stringChar == '"')
|
||||
{
|
||||
inString = false;
|
||||
}
|
||||
sb.Append(content[i]);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle char literals
|
||||
if (!inString && content[i] == '\'')
|
||||
{
|
||||
if (!inChar)
|
||||
{
|
||||
inChar = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
inChar = false;
|
||||
}
|
||||
sb.Append(content[i]);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip comments only when not in string/char
|
||||
if (!inString && !inChar)
|
||||
{
|
||||
// Single-line comment
|
||||
if (i + 1 < content.Length && content[i] == '/' && content[i + 1] == '/')
|
||||
{
|
||||
// Skip until end of line
|
||||
while (i < content.Length && content[i] != '\n')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
if (i < content.Length)
|
||||
{
|
||||
sb.Append('\n');
|
||||
i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Multi-line comment
|
||||
if (i + 1 < content.Length && content[i] == '/' && content[i + 1] == '*')
|
||||
{
|
||||
i += 2;
|
||||
while (i + 1 < content.Length && !(content[i] == '*' && content[i + 1] == '/'))
|
||||
{
|
||||
// Preserve newlines for line number accuracy
|
||||
if (content[i] == '\n')
|
||||
{
|
||||
sb.Append('\n');
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i + 1 < content.Length)
|
||||
{
|
||||
i += 2; // Skip */
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(content[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<EnableDefaultItems>false</EnableDefaultItems>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user