Add SBOM, symbols, traces, and VEX files for CVE-2022-21661 SQLi case
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Created CycloneDX and SPDX SBOM files for both reachable and unreachable images.
- Added symbols.json detailing function entry and sink points in the WordPress code.
- Included runtime traces for function calls in both reachable and unreachable scenarios.
- Developed OpenVEX files indicating vulnerability status and justification for both cases.
- Updated README for evaluator harness to guide integration with scanner output.
This commit is contained in:
master
2025-11-08 20:53:45 +02:00
parent 515975edc5
commit 536f6249a6
837 changed files with 37279 additions and 14675 deletions

View File

@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace StellaOps.Scanner.Reachability;
public sealed class ReachabilityGraphBuilder
{
private const string GraphSchemaVersion = "1.0";
private readonly HashSet<string> nodes = new(StringComparer.Ordinal);
private readonly HashSet<ReachabilityEdge> edges = new();
public ReachabilityGraphBuilder AddNode(string symbolId)
{
if (!string.IsNullOrWhiteSpace(symbolId))
{
nodes.Add(symbolId.Trim());
}
return this;
}
public ReachabilityGraphBuilder AddEdge(string from, string to, string kind = "call")
{
if (string.IsNullOrWhiteSpace(from) || string.IsNullOrWhiteSpace(to))
{
return this;
}
var edge = new ReachabilityEdge(from.Trim(), to.Trim(), string.IsNullOrWhiteSpace(kind) ? "call" : kind.Trim());
edges.Add(edge);
nodes.Add(edge.From);
nodes.Add(edge.To);
return this;
}
public string BuildJson(bool indented = true)
{
var payload = new ReachabilityGraphPayload
{
SchemaVersion = GraphSchemaVersion,
Nodes = nodes.Select(id => new ReachabilityNode(id)).ToList(),
Edges = edges.Select(edge => new ReachabilityEdgePayload(edge.From, edge.To, edge.Kind)).ToList()
};
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = indented
};
return JsonSerializer.Serialize(payload, options);
}
public static ReachabilityGraphBuilder FromFixture(string variantPath)
{
ArgumentException.ThrowIfNullOrWhiteSpace(variantPath);
var builder = new ReachabilityGraphBuilder();
foreach (var fileName in new[] { "callgraph.static.json", "callgraph.framework.json" })
{
var path = Path.Combine(variantPath, fileName);
if (!File.Exists(path))
{
continue;
}
using var stream = File.OpenRead(path);
using var document = JsonDocument.Parse(stream);
var root = document.RootElement;
if (root.TryGetProperty("nodes", out var nodesElement) && nodesElement.ValueKind == JsonValueKind.Array)
{
foreach (var node in nodesElement.EnumerateArray())
{
var sid = node.TryGetProperty("sid", out var sidElement)
? sidElement.GetString()
: node.GetProperty("id").GetString();
builder.AddNode(sid ?? string.Empty);
}
}
if (root.TryGetProperty("edges", out var edgesElement) && edgesElement.ValueKind == JsonValueKind.Array)
{
foreach (var edge in edgesElement.EnumerateArray())
{
var from = edge.TryGetProperty("from", out var fromEl)
? fromEl.GetString()
: edge.GetProperty("source").GetString();
var to = edge.TryGetProperty("to", out var toEl)
? toEl.GetString()
: edge.GetProperty("target").GetString();
var kind = edge.TryGetProperty("kind", out var kindEl)
? kindEl.GetString()
: edge.TryGetProperty("type", out var typeEl)
? typeEl.GetString()
: "call";
builder.AddEdge(from ?? string.Empty, to ?? string.Empty, kind ?? "call");
}
}
}
return builder;
}
private sealed record ReachabilityEdge(string From, string To, string Kind);
private sealed record ReachabilityNode(string Sid);
private sealed record ReachabilityEdgePayload(string From, string To, string Kind);
private sealed record ReachabilityGraphPayload
{
public string SchemaVersion { get; set; } = GraphSchemaVersion;
public List<ReachabilityNode> Nodes { get; set; } = new();
public List<ReachabilityEdgePayload> Edges { get; set; } = new();
}
}

View File

@@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Replay.Core;
namespace StellaOps.Scanner.Reachability;
/// <summary>
/// Helper that projects reachability artifacts into the replay manifest.
/// </summary>
public sealed class ReachabilityReplayWriter
{
/// <summary>
/// Attaches reachability graphs and runtime traces to the supplied replay manifest.
/// </summary>
public void AttachEvidence(
ReplayManifest manifest,
IEnumerable<ReachabilityReplayGraph>? graphs,
IEnumerable<ReachabilityReplayTrace>? traces)
{
ArgumentNullException.ThrowIfNull(manifest);
WriteGraphs(manifest, graphs);
WriteTraces(manifest, traces);
}
private static void WriteGraphs(ReplayManifest manifest, IEnumerable<ReachabilityReplayGraph>? graphs)
{
if (graphs is null)
{
return;
}
var sanitized = graphs
.Where(graph => graph is not null)
.Select(graph => NormalizeGraph(graph!))
.Where(graph => graph is not null)
.Select(graph => graph!)
.DistinctBy(graph => (graph.Kind, graph.CasUri, graph.Sha256, graph.Analyzer, graph.Version))
.OrderBy(graph => graph.CasUri, StringComparer.Ordinal)
.ThenBy(graph => graph.Kind, StringComparer.Ordinal)
.ToList();
foreach (var graph in sanitized)
{
manifest.AddReachabilityGraph(new ReplayReachabilityGraphReference
{
Kind = graph.Kind,
CasUri = graph.CasUri,
Sha256 = graph.Sha256,
Analyzer = graph.Analyzer,
Version = graph.Version
});
}
}
private static void WriteTraces(ReplayManifest manifest, IEnumerable<ReachabilityReplayTrace>? traces)
{
if (traces is null)
{
return;
}
var normalized = traces
.Where(trace => trace is not null)
.Select(trace => NormalizeTrace(trace!))
.Where(trace => trace is not null)
.Select(trace => trace!)
.ToList();
var collapsed = normalized
.GroupBy(trace => (trace.Source, trace.CasUri, trace.Sha256))
.Select(group => group.OrderBy(t => t.RecordedAt).First())
.OrderBy(trace => trace.RecordedAt)
.ThenBy(trace => trace.CasUri, StringComparer.Ordinal)
.ToList();
foreach (var trace in collapsed)
{
manifest.AddReachabilityTrace(new ReplayReachabilityTraceReference
{
Source = trace.Source,
CasUri = trace.CasUri,
Sha256 = trace.Sha256,
RecordedAt = trace.RecordedAt
});
}
}
private static NormalizedGraph? NormalizeGraph(ReachabilityReplayGraph graph)
{
var casUri = Normalize(graph.CasUri);
if (string.IsNullOrEmpty(casUri))
{
return null;
}
return new NormalizedGraph(
Kind: Normalize(graph.Kind) ?? "static",
CasUri: casUri,
Sha256: NormalizeHash(graph.Sha256),
Analyzer: Normalize(graph.Analyzer) ?? string.Empty,
Version: Normalize(graph.Version) ?? string.Empty);
}
private static NormalizedTrace? NormalizeTrace(ReachabilityReplayTrace trace)
{
var casUri = Normalize(trace.CasUri);
if (string.IsNullOrEmpty(casUri))
{
return null;
}
return new NormalizedTrace(
Source: Normalize(trace.Source) ?? string.Empty,
CasUri: casUri,
Sha256: NormalizeHash(trace.Sha256),
RecordedAt: trace.RecordedAt.ToUniversalTime());
}
private static string? Normalize(string? value)
=> string.IsNullOrWhiteSpace(value) ? null : value.Trim();
private static string NormalizeHash(string? hash)
=> string.IsNullOrWhiteSpace(hash) ? string.Empty : hash.Trim().ToLowerInvariant();
private sealed record NormalizedGraph(
string Kind,
string CasUri,
string Sha256,
string Analyzer,
string Version);
private sealed record NormalizedTrace(
string Source,
string CasUri,
string Sha256,
DateTimeOffset RecordedAt);
}
/// <summary>
/// Describes a CAS-backed reachability graph emitted by Scanner.
/// </summary>
public sealed record ReachabilityReplayGraph(
string? Kind,
string? CasUri,
string? Sha256,
string? Analyzer,
string? Version);
/// <summary>
/// Describes a runtime trace artifact emitted by Scanner/Zastava.
/// </summary>
public sealed record ReachabilityReplayTrace(
string? Source,
string? CasUri,
string? Sha256,
DateTimeOffset RecordedAt);

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="10.0.0-preview.7.25380.108" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Replay.Core\StellaOps.Replay.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
namespace StellaOps.Scanner.Surface.FS;
@@ -20,12 +20,14 @@ public sealed class FileSurfaceManifestStore :
{
private readonly ILogger<FileSurfaceManifestStore> _logger;
private readonly SurfaceManifestPathBuilder _pathBuilder;
private readonly ICryptoHash _hash;
private readonly SemaphoreSlim _publishGate = new(1, 1);
public FileSurfaceManifestStore(
IOptions<SurfaceCacheOptions> cacheOptions,
IOptions<SurfaceManifestStoreOptions> storeOptions,
ILogger<FileSurfaceManifestStore> logger)
ILogger<FileSurfaceManifestStore> logger,
ICryptoHash hash)
{
if (cacheOptions is null)
{
@@ -38,6 +40,7 @@ public sealed class FileSurfaceManifestStore :
}
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_hash = hash ?? throw new ArgumentNullException(nameof(hash));
_pathBuilder = new SurfaceManifestPathBuilder(cacheOptions.Value, storeOptions.Value);
}
@@ -183,11 +186,10 @@ public sealed class FileSurfaceManifestStore :
};
}
private static string ComputeDigest(ReadOnlySpan<byte> bytes)
private string ComputeDigest(ReadOnlySpan<byte> bytes)
{
using var sha = SHA256.Create();
var hash = sha.ComputeHash(bytes);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
var digest = _hash.ComputeHash(bytes, HashAlgorithms.Sha256);
return $"sha256:{Convert.ToHexString(digest).ToLowerInvariant()}";
}
private static SurfaceManifestArtifact NormalizeArtifact(SurfaceManifestArtifact artifact)

View File

@@ -16,12 +16,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0-preview.7.25380.108" />
<PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\\..\\..\\__Libraries\\StellaOps.Cryptography\\StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>