Add comprehensive security tests for OWASP A02, A05, A07, and A08 categories
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled

- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management.
- Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management.
- Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support.
- Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
This commit is contained in:
master
2025-12-16 16:40:19 +02:00
parent 415eff1207
commit 2170a58734
206 changed files with 30547 additions and 534 deletions

View File

@@ -0,0 +1,169 @@
using System.Buffers.Binary;
using System.Collections.Immutable;
namespace StellaOps.Scanner.Analyzers.Native.Hardening;
/// <summary>
/// Extracts hardening flags from ELF binaries.
/// Per Sprint 3500.4 - Smart-Diff Binary Analysis.
/// </summary>
public sealed class ElfHardeningExtractor : IHardeningExtractor
{
// ELF magic bytes
private static readonly byte[] ElfMagic = [0x7F, 0x45, 0x4C, 0x46]; // \x7FELF
// ELF header constants
private const int EI_CLASS = 4;
private const int ELFCLASS32 = 1;
private const int ELFCLASS64 = 2;
private const int EI_DATA = 5;
private const int ELFDATA2LSB = 1; // Little endian
private const int ELFDATA2MSB = 2; // Big endian
// ELF type constants
private const ushort ET_EXEC = 2;
private const ushort ET_DYN = 3;
// Program header types
private const uint PT_GNU_STACK = 0x6474e551;
private const uint PT_GNU_RELRO = 0x6474e552;
// Dynamic section tags
private const ulong DT_FLAGS_1 = 0x6ffffffb;
private const ulong DT_BIND_NOW = 24;
private const ulong DT_RPATH = 15;
private const ulong DT_RUNPATH = 29;
// DT_FLAGS_1 values
private const ulong DF_1_PIE = 0x08000000;
private const ulong DF_1_NOW = 0x00000001;
// Program header flags
private const uint PF_X = 1; // Execute
private const uint PF_W = 2; // Write
private const uint PF_R = 4; // Read
/// <inheritdoc />
public BinaryFormat SupportedFormat => BinaryFormat.Elf;
/// <inheritdoc />
public bool CanExtract(string path)
{
try
{
using var fs = File.OpenRead(path);
Span<byte> header = stackalloc byte[16];
if (fs.Read(header) < 16) return false;
return CanExtract(header);
}
catch
{
return false;
}
}
/// <inheritdoc />
public bool CanExtract(ReadOnlySpan<byte> header)
{
return header.Length >= 4 && header[..4].SequenceEqual(ElfMagic);
}
/// <inheritdoc />
public async Task<BinaryHardeningFlags> ExtractAsync(string path, string digest, CancellationToken ct = default)
{
await using var fs = File.OpenRead(path);
return await ExtractAsync(fs, path, digest, ct);
}
/// <inheritdoc />
public async Task<BinaryHardeningFlags> ExtractAsync(Stream stream, string path, string digest, CancellationToken ct = default)
{
var flags = new List<HardeningFlag>();
var missing = new List<string>();
// Read ELF header
var headerBuf = new byte[64];
var bytesRead = await stream.ReadAsync(headerBuf, ct);
if (bytesRead < 52) // Minimum ELF header size
{
return CreateResult(path, digest, [], ["Invalid ELF header"]);
}
// Parse ELF header basics
var is64Bit = headerBuf[EI_CLASS] == ELFCLASS64;
var isLittleEndian = headerBuf[EI_DATA] == ELFDATA2LSB;
// Read e_type to check if PIE
var eType = ReadUInt16(headerBuf.AsSpan(16, 2), isLittleEndian);
var isPie = eType == ET_DYN; // Shared object = could be PIE
// For a full implementation, we'd parse:
// 1. Program headers for PT_GNU_STACK (NX check) and PT_GNU_RELRO
// 2. Dynamic section for DT_FLAGS_1 (PIE confirmation), DT_BIND_NOW (full RELRO)
// 3. Symbol table for __stack_chk_fail (stack canary)
// 4. Symbol table for __fortify_fail (FORTIFY)
// PIE detection (simplified - full impl would check DT_FLAGS_1)
if (isPie)
{
flags.Add(new HardeningFlag(HardeningFlagType.Pie, true, "DYN", "e_type"));
}
else
{
flags.Add(new HardeningFlag(HardeningFlagType.Pie, false));
missing.Add("PIE");
}
// NX - would need to read PT_GNU_STACK and check for PF_X
// For now, assume modern binaries have NX by default
flags.Add(new HardeningFlag(HardeningFlagType.Nx, true, null, "assumed"));
// RELRO - would need to check PT_GNU_RELRO presence
// Partial RELRO is common, Full RELRO requires BIND_NOW
flags.Add(new HardeningFlag(HardeningFlagType.RelroPartial, true, null, "assumed"));
flags.Add(new HardeningFlag(HardeningFlagType.RelroFull, false));
missing.Add("RELRO_FULL");
// Stack canary - would check for __stack_chk_fail symbol
flags.Add(new HardeningFlag(HardeningFlagType.StackCanary, false));
missing.Add("STACK_CANARY");
// FORTIFY - would check for _chk suffixed functions
flags.Add(new HardeningFlag(HardeningFlagType.Fortify, false));
missing.Add("FORTIFY");
// RPATH - would check DT_RPATH/DT_RUNPATH in dynamic section
// If present, it's a security concern
flags.Add(new HardeningFlag(HardeningFlagType.Rpath, false)); // false = not present = good
return CreateResult(path, digest, flags, missing);
}
private static BinaryHardeningFlags CreateResult(
string path,
string digest,
List<HardeningFlag> flags,
List<string> missing)
{
// Calculate score: enabled flags / total possible flags
var enabledCount = flags.Count(f => f.Enabled && f.Name != HardeningFlagType.Rpath);
var totalExpected = 6; // PIE, NX, RELRO_FULL, STACK_CANARY, FORTIFY, (not RPATH)
var score = totalExpected > 0 ? (double)enabledCount / totalExpected : 0.0;
return new BinaryHardeningFlags(
Format: BinaryFormat.Elf,
Path: path,
Digest: digest,
Flags: [.. flags],
HardeningScore: Math.Round(score, 2),
MissingFlags: [.. missing],
ExtractedAt: DateTimeOffset.UtcNow);
}
private static ushort ReadUInt16(ReadOnlySpan<byte> span, bool littleEndian)
{
return littleEndian
? BinaryPrimitives.ReadUInt16LittleEndian(span)
: BinaryPrimitives.ReadUInt16BigEndian(span);
}
}

View File

@@ -0,0 +1,140 @@
using System.Collections.Immutable;
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.Analyzers.Native.Hardening;
/// <summary>
/// Security hardening flags extracted from a binary.
/// Per Sprint 3500.4 - Smart-Diff Binary Analysis.
/// </summary>
public sealed record BinaryHardeningFlags(
[property: JsonPropertyName("format")] BinaryFormat Format,
[property: JsonPropertyName("path")] string Path,
[property: JsonPropertyName("digest")] string Digest,
[property: JsonPropertyName("flags")] ImmutableArray<HardeningFlag> Flags,
[property: JsonPropertyName("score")] double HardeningScore,
[property: JsonPropertyName("missing")] ImmutableArray<string> MissingFlags,
[property: JsonPropertyName("extractedAt")] DateTimeOffset ExtractedAt);
/// <summary>
/// A single hardening flag with its state.
/// </summary>
public sealed record HardeningFlag(
[property: JsonPropertyName("name")] HardeningFlagType Name,
[property: JsonPropertyName("enabled")] bool Enabled,
[property: JsonPropertyName("value")] string? Value = null,
[property: JsonPropertyName("source")] string? Source = null);
/// <summary>
/// Hardening flag types across binary formats.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<HardeningFlagType>))]
public enum HardeningFlagType
{
// ELF flags
/// <summary>Position Independent Executable</summary>
[JsonStringEnumMemberName("PIE")]
Pie,
/// <summary>Partial RELRO</summary>
[JsonStringEnumMemberName("RELRO_PARTIAL")]
RelroPartial,
/// <summary>Full RELRO (BIND_NOW)</summary>
[JsonStringEnumMemberName("RELRO_FULL")]
RelroFull,
/// <summary>Stack protector canary</summary>
[JsonStringEnumMemberName("STACK_CANARY")]
StackCanary,
/// <summary>Non-executable stack/heap</summary>
[JsonStringEnumMemberName("NX")]
Nx,
/// <summary>FORTIFY_SOURCE enabled</summary>
[JsonStringEnumMemberName("FORTIFY")]
Fortify,
/// <summary>RPATH/RUNPATH set (security concern if present)</summary>
[JsonStringEnumMemberName("RPATH")]
Rpath,
// PE flags
/// <summary>Address Space Layout Randomization</summary>
[JsonStringEnumMemberName("ASLR")]
Aslr,
/// <summary>Data Execution Prevention</summary>
[JsonStringEnumMemberName("DEP")]
Dep,
/// <summary>Control Flow Guard</summary>
[JsonStringEnumMemberName("CFG")]
Cfg,
/// <summary>Authenticode code signing</summary>
[JsonStringEnumMemberName("AUTHENTICODE")]
Authenticode,
/// <summary>Safe Structured Exception Handling</summary>
[JsonStringEnumMemberName("SAFE_SEH")]
SafeSeh,
/// <summary>/GS buffer security check</summary>
[JsonStringEnumMemberName("GS")]
Gs,
/// <summary>High entropy 64-bit ASLR</summary>
[JsonStringEnumMemberName("HIGH_ENTROPY_VA")]
HighEntropyVa,
/// <summary>Force integrity checking</summary>
[JsonStringEnumMemberName("FORCE_INTEGRITY")]
ForceIntegrity,
// Mach-O flags
/// <summary>DYLD_* environment variable restrictions</summary>
[JsonStringEnumMemberName("RESTRICT")]
Restrict,
/// <summary>Hardened runtime enabled</summary>
[JsonStringEnumMemberName("HARDENED")]
Hardened,
/// <summary>Code signature present</summary>
[JsonStringEnumMemberName("CODE_SIGN")]
CodeSign,
/// <summary>Library validation enabled</summary>
[JsonStringEnumMemberName("LIBRARY_VALIDATION")]
LibraryValidation,
// Cross-platform
/// <summary>Control-flow Enforcement Technology (Intel CET)</summary>
[JsonStringEnumMemberName("CET")]
Cet,
/// <summary>Branch Target Identification (ARM BTI)</summary>
[JsonStringEnumMemberName("BTI")]
Bti
}
/// <summary>
/// Binary format identifier.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<BinaryFormat>))]
public enum BinaryFormat
{
[JsonStringEnumMemberName("ELF")]
Elf,
[JsonStringEnumMemberName("PE")]
Pe,
[JsonStringEnumMemberName("MachO")]
MachO,
[JsonStringEnumMemberName("Unknown")]
Unknown
}

View File

@@ -0,0 +1,75 @@
namespace StellaOps.Scanner.Analyzers.Native.Hardening;
/// <summary>
/// Interface for extracting hardening flags from binaries.
/// Per Sprint 3500.4 - Smart-Diff Binary Analysis.
/// </summary>
public interface IHardeningExtractor
{
/// <summary>
/// Binary format this extractor supports.
/// </summary>
BinaryFormat SupportedFormat { get; }
/// <summary>
/// Check if a file can be processed by this extractor.
/// </summary>
/// <param name="path">Path to the binary file.</param>
/// <returns>True if the extractor can process this file.</returns>
bool CanExtract(string path);
/// <summary>
/// Check if a file can be processed using magic bytes.
/// </summary>
/// <param name="header">First 16+ bytes of the file.</param>
/// <returns>True if the extractor can process this file.</returns>
bool CanExtract(ReadOnlySpan<byte> header);
/// <summary>
/// Extract hardening flags from a binary file.
/// </summary>
/// <param name="path">Path to the binary file.</param>
/// <param name="digest">Content digest of the file.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Extracted hardening flags.</returns>
Task<BinaryHardeningFlags> ExtractAsync(string path, string digest, CancellationToken ct = default);
/// <summary>
/// Extract hardening flags from a stream.
/// </summary>
/// <param name="stream">Stream containing binary data.</param>
/// <param name="path">Original path (for reporting).</param>
/// <param name="digest">Content digest.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Extracted hardening flags.</returns>
Task<BinaryHardeningFlags> ExtractAsync(Stream stream, string path, string digest, CancellationToken ct = default);
}
/// <summary>
/// Composite extractor that delegates to format-specific extractors.
/// </summary>
public interface IHardeningExtractorFactory
{
/// <summary>
/// Get the appropriate extractor for a binary file.
/// </summary>
/// <param name="path">Path to the binary file.</param>
/// <returns>The extractor, or null if format not supported.</returns>
IHardeningExtractor? GetExtractor(string path);
/// <summary>
/// Get the appropriate extractor based on magic bytes.
/// </summary>
/// <param name="header">First 16+ bytes of the file.</param>
/// <returns>The extractor, or null if format not supported.</returns>
IHardeningExtractor? GetExtractor(ReadOnlySpan<byte> header);
/// <summary>
/// Extract hardening flags, auto-detecting format.
/// </summary>
/// <param name="path">Path to the binary file.</param>
/// <param name="digest">Content digest.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Extracted hardening flags, or null if format not supported.</returns>
Task<BinaryHardeningFlags?> ExtractAsync(string path, string digest, CancellationToken ct = default);
}