Raw JSON
{
"$schema": "https://docs.oasis-open.org/csaf/csaf/v2.1/schema/csaf.json",
"document": {
"category": "csaf_security_advisory",
"csaf_version": "2.1",
"distribution": {
"tlp": {
"label": "CLEAR"
}
},
"lang": "en",
"notes": [
{
"category": "summary",
"text": "`--restore-dump` rebuilt an annotation database from a dump file without verifying any of the five checksum sidecars that the viewer's own dump writer emits beside it, and replayed `.sql` dumps through `execute_batch` with the SQLite engine's full authority — no ATTACH restriction and no validation of the schema the replay produced.",
"title": "Summary"
},
{
"category": "details",
"text": "1.6.54 fixes both halves. Integrity: restore_dump now calls the new `sidecar::verify_sidecars`, which re-derives every digest whose sidecar exists and refuses the restore on any mismatch; a source with NO sidecar beside it is refused as well, because an attacker who can rewrite a dump can equally delete its sidecars, so verifying only what happens to be present would verify nothing. Replay authority: restore_sql_dump sets SQLITE_LIMIT_ATTACHED = 0 (via the rusqlite `limits` feature) so ATTACH fails inside the engine — a limit, not a text filter, so quoting and comment tricks cannot evade it — and a new post-replay check refuses any TRIGGER or VIEW the dump left behind (the executable schema: a trigger fires on every later annotation write, a view re-points a name the application reads) and requires the four annotation tables to be present. Additional bare tables are deliberately permitted — they execute nothing and the application never reads a table it does not know, so refusing them would break legitimate dumps without closing any execution path. Both are pinned by regression tests (`restore_dump_refuses_a_tampered_or_unverifiable_dump`, `restore_sql_dump_refuses_attach_and_rogue_schema`) and by a new coverage-guided fuzz target, `fuzz_verify_sidecars`. That target earned its place on its first run: it found that a sidecar which existed but was not readable UTF-8 (a single 0xFF byte) was silently skipped as though absent, contradicting the verifier's own documented error contract. Present-but-unreadable is now a refusal. The restore path was fail-closed either way — an unreadable sidecar left the verified count at zero, which the caller already refuses — so this tightened the contract rather than closing a second hole.",
"title": "Remediation"
},
{
"category": "general",
"text": "Upgrade to grundschutz-oscal-viewer 1.6.54. Operator-visible behaviour change: a dump with no checksum sidecar beside it is now REFUSED. Restoring a hand-made or third-party dump requires generating a sidecar first, for example `shasum -a 256 dump.sql > dump.sql.sha-256`. Dumps produced by the viewer already carry all five and restore unchanged. Keep dump directories on storage only the operator can write to; the checksum verification detects tampering but does not prevent it.",
"title": "Recommended Action"
},
{
"category": "general",
"text": "Discovered by ndaal's own OWASP ASVS v5.0.0 audit of grundschutz-oscal-viewer on 2026-08-13, which produced the product's first STRIDE threat model; this defect is recorded there as S-T3/S-E1. No external report, CVE or RUSTSEC advisory exists for it.",
"title": "Discovery"
},
{
"category": "general",
"text": "grundschutz-oscal-viewer is a secure, memory-safe, single-binary offline viewer for the BSI Grundschutz++ OSCAL catalog — embedded catalog data, filters and full-text search served over a TLS 1.3 (rustls) HTTP/1.1 + HTTP/2 listener, with an annotations workspace offering optional XChaCha20-Poly1305 encryption-at-rest and embedded SQLite storage.",
"title": "Product Description"
},
{
"category": "legal_disclaimer",
"text": "THIS DOCUMENT IS PROVIDED ON AN 'AS IS' BASIS AND DOES NOT IMPLY ANY KIND OF GUARANTEE OR WARRANTY. ndaal Gesellschaft fuer Sicherheit in der Informationstechnik mbH & Co KG DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS INFORMATION INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.",
"title": "Legal Disclaimer"
}
],
"publisher": {
"category": "vendor",
"contact_details": "security@ndaal.eu",
"issuing_authority": "ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH & Co KG Security Team",
"name": "ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH & Co KG",
"namespace": "https://ndaal.eu/csaf"
},
"references": [
{
"category": "self",
"summary": "This advisory in CSAF 2.1 format",
"url": "https://gitlab.com/vPierre/ndaal_public_csaf_information/-/raw/main/csaf/2026/416/ndaal-sa-2026-416.json"
},
{
"category": "external",
"summary": "grundschutz-oscal-viewer source code",
"url": "https://gitlab.com/vPierre/ndaal_public_bsi_grundschutz_oscal_viewer"
},
{
"category": "external",
"summary": "grundschutz-oscal-viewer on crates.io",
"url": "https://crates.io/crates/grundschutz-oscal-viewer"
}
],
"title": "ndaal Security Advisory: Database restore accepted unverified dumps and replayed SQL with unrestricted engine authority (grundschutz-oscal-viewer < 1.6.54)",
"tracking": {
"current_release_date": "2026-08-13T12:00:00.000Z",
"generator": {
"engine": {
"name": "ndaal CSAF Generator",
"version": "1.0.0"
}
},
"id": "ndaal-sa-2026-416",
"initial_release_date": "2026-08-13T12:00:00.000Z",
"revision_history": [
{
"date": "2026-08-13T12:00:00.000Z",
"number": "1.0.0",
"summary": "Initial advisory: S-T3/S-E1 remediated in grundschutz-oscal-viewer 1.6.54."
}
],
"status": "final",
"version": "1.0.0"
}
},
"product_tree": {
"branches": [
{
"category": "vendor",
"name": "ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH & Co KG",
"branches": [
{
"category": "product_name",
"name": "grundschutz-oscal-viewer",
"branches": [
{
"category": "product_version",
"name": "1.6.53",
"product": {
"name": "ndaal Grundschutz++ OSCAL Viewer 1.6.53",
"product_id": "CSAFPID-0001"
}
},
{
"category": "product_version",
"name": "1.6.54",
"product": {
"name": "ndaal Grundschutz++ OSCAL Viewer 1.6.54",
"product_id": "CSAFPID-0002"
}
}
]
}
]
}
]
},
"vulnerabilities": [
{
"ids": [
{
"system_name": "ndaal Grundschutz++ OSCAL Viewer threat model",
"text": "S-T3/S-E1"
}
],
"notes": [
{
"category": "description",
"text": "Two defects on the same operator-initiated path. (1) Integrity: run_dump writes .sha-256/.sha-512/.sha3-512/.blake3-512/.shake256-512 sidecars for every dump, but restore_dump (src/dbdump.rs) validated only the SQLite magic, UTF-8 encoding and size caps — never the digests. Anyone able to write to the dump directory could therefore alter a dump, and a later restore would present the rewritten annotation history as authentic, defeating the append-only versioning the product relies on for its audit trail. (2) Replay authority: Store::restore_sql_dump (src/store.rs) executed the dump text as a single batch, so a hostile `.sql` could ATTACH a second database — reading or overwriting any file the viewer process could reach — and could leave triggers or views behind that fire on every subsequent annotation write, persisting long after the restore completed. Exploitation requires write access to a dump file the operator later chooses to restore, and the operator action of running --restore-dump, which is why the vector is AV:L/PR:L with user interaction required. Impact when those preconditions are met is high for both confidentiality and integrity. Rated CWE-345 with CWE-501 (trust-boundary violation) as the secondary weakness; ASVS V1.3 (safe deserialization of untrusted input) and V13.4.",
"title": "Description"
},
{
"category": "details",
"text": "1.6.54 fixes both halves. Integrity: restore_dump now calls the new `sidecar::verify_sidecars`, which re-derives every digest whose sidecar exists and refuses the restore on any mismatch; a source with NO sidecar beside it is refused as well, because an attacker who can rewrite a dump can equally delete its sidecars, so verifying only what happens to be present would verify nothing. Replay authority: restore_sql_dump sets SQLITE_LIMIT_ATTACHED = 0 (via the rusqlite `limits` feature) so ATTACH fails inside the engine — a limit, not a text filter, so quoting and comment tricks cannot evade it — and a new post-replay check refuses any TRIGGER or VIEW the dump left behind (the executable schema: a trigger fires on every later annotation write, a view re-points a name the application reads) and requires the four annotation tables to be present. Additional bare tables are deliberately permitted — they execute nothing and the application never reads a table it does not know, so refusing them would break legitimate dumps without closing any execution path. Both are pinned by regression tests (`restore_dump_refuses_a_tampered_or_unverifiable_dump`, `restore_sql_dump_refuses_attach_and_rogue_schema`) and by a new coverage-guided fuzz target, `fuzz_verify_sidecars`. That target earned its place on its first run: it found that a sidecar which existed but was not readable UTF-8 (a single 0xFF byte) was silently skipped as though absent, contradicting the verifier's own documented error contract. Present-but-unreadable is now a refusal. The restore path was fail-closed either way — an unreadable sidecar left the verified count at zero, which the caller already refuses — so this tightened the contract rather than closing a second hole. CVSS v3.1 base score 6.6 (MEDIUM), vector CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N. CVSS v4.0 base score 6.8 (MEDIUM), vector CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:A/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N.",
"title": "Remediation Detail"
}
],
"product_status": {
"known_affected": [
"CSAFPID-0001"
],
"fixed": [
"CSAFPID-0002"
]
},
"remediations": [
{
"category": "vendor_fix",
"details": "Upgrade to grundschutz-oscal-viewer 1.6.54. Operator-visible behaviour change: a dump with no checksum sidecar beside it is now REFUSED. Restoring a hand-made or third-party dump requires generating a sidecar first, for example `shasum -a 256 dump.sql > dump.sql.sha-256`. Dumps produced by the viewer already carry all five and restore unchanged. Keep dump directories on storage only the operator can write to; the checksum verification detects tampering but does not prevent it.",
"product_ids": [
"CSAFPID-0002"
]
}
],
"metrics": [
{
"content": {
"cvss_v3": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N",
"baseScore": 6.6,
"baseSeverity": "MEDIUM",
"attackVector": "LOCAL",
"attackComplexity": "LOW",
"privilegesRequired": "LOW",
"userInteraction": "REQUIRED",
"scope": "UNCHANGED",
"confidentialityImpact": "HIGH",
"integrityImpact": "HIGH",
"availabilityImpact": "NONE"
},
"cvss_v4": {
"version": "4.0",
"vectorString": "CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:A/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N",
"baseScore": 6.8,
"baseSeverity": "MEDIUM",
"attackVector": "LOCAL",
"attackComplexity": "LOW",
"attackRequirements": "NONE",
"privilegesRequired": "LOW",
"userInteraction": "ACTIVE",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"subAvailabilityImpact": "NONE"
}
},
"products": [
"CSAFPID-0001"
]
}
],
"threats": [
{
"category": "impact",
"details": "Two defects on the same operator-initiated path. (1) Integrity: run_dump writes .sha-256/.sha-512/.sha3-512/.blake3-512/.shake256-512 sidecars for every dump, but restore_dump (src/dbdump.rs) validated only the SQLite magic, UTF-8 encoding and size caps — never the digests. Anyone able to write to the dump directory could therefore alter a dump, and a later restore would present the rewritten annotation history as authentic, defeating the append-only versioning the product relies on for its audit trail. (2) Replay authority: Store::restore_sql_dump (src/store.rs) executed the dump text as a single batch, so a hostile `.sql` could ATTACH a second database — reading or overwriting any file the viewer process could reach — and could leave triggers or views behind that fire on every subsequent annotation write, persisting long after the restore completed. Exploitation requires write access to a dump file the operator later chooses to restore, and the operator action of running --restore-dump, which is why the vector is AV:L/PR:L with user interaction required. Impact when those preconditions are met is high for both confidentiality and integrity. Rated CWE-345 with CWE-501 (trust-boundary violation) as the secondary weakness; ASVS V1.3 (safe deserialization of untrusted input) and V13.4.",
"product_ids": [
"CSAFPID-0001"
]
}
],
"title": "Database restore accepted unverified dumps and replayed SQL with unrestricted engine authority",
"references": [
{
"category": "external",
"summary": "Threat model entry",
"url": "https://gitlab.com/vPierre/ndaal_public_bsi_grundschutz_oscal_viewer/-/blob/main/documentation/threat-model/stride.md"
}
]
}
]
}