Описание
gitsign verify accepts signatures over go-git-normalized bytes, enabling trust confusion on malformed commits
Summary
gitsign verify and gitsign verify-tag re-encode commit/tag objects through go-git's EncodeWithoutSignature before checking the signature, instead of verifying against the raw git object bytes. For malformed objects with duplicate tree headers, git-core and go-git parse different trees: git-core uses the first, go-git uses the second. A signature crafted over the go-git-normalized form (second tree) passes gitsign verify while git-core resolves the commit to a completely different tree. This breaks the invariant that a verified signature, the commit semantics git-core presents to users, and the object hash logged in Rekor all refer to the same content.
Severity
Medium (CVSS 3.1: 5.7)
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N
- Attack Vector: Network — a malformed commit can be distributed via any accessible git remote
- Attack Complexity: High — exploitation requires crafting malformed objects that also bypass git server fsck checks (not universally enabled)
- Privileges Required: None — the most impactful form (signature replay) requires no signing key
- User Interaction: Required — a victim must run
gitsign verifyon the malformed commit - Scope: Unchanged — impact is confined to the repository under verification
- Confidentiality Impact: None
- Integrity Impact: High — a verified signature appears to endorse content different from what git-core resolves and presents to users
- Availability Impact: None
Affected Component
internal/commands/verify/verify.go—(o *options).Run(line 75)internal/commands/verify-tag/verify_tag.go—(o *options).Run(line 77)pkg/git/verify.go—ObjectHash(lines 126–158, specifically thecommit()round-trip at 161–176)
CWE
- CWE-347: Improper Verification of Cryptographic Signature
- CWE-295: Improper Certificate Validation (secondary — the mismatch allows a cert to appear to cover content it never covered)
Description
Root cause: re-encoding instead of raw-byte verification
When gitsign verify is invoked, the commit is opened via go-git and its body is reconstructed through EncodeWithoutSignature before being passed to the cryptographic verifier:
The same pattern appears in verify-tag:
The loose-parsing assumption in go-git
The codebase itself acknowledges the problem in ObjectHash:
go-git's loose parsing means that for a commit containing two tree headers, it silently discards the first and retains the second. EncodeWithoutSignature then produces a canonical commit body containing only the second tree — which can differ from what git-core resolves.
Divergent verification paths confirm the inconsistency
The git verify-commit path (internal/commands/root/verify.go) receives the raw commit bytes directly from git-core and does not re-encode them:
The two paths therefore reach opposite conclusions for the same malformed commit: git verify-commit fails (raw bytes with both trees ≠ signed canonical bytes), while gitsign verify succeeds (re-encoded bytes match signed bytes).
Concrete attack: signature replay without a signing key
An attacker does not need a signing key to trigger the confusion. Given any existing legitimately gitsign-signed commit from Alice:
An attacker crafts a new malformed commit object:
gitsign verify: go-git picks T1, re-encodes, Alice's signature verifies. Output: "Good signature from alice@corp.com."git log/git-core: uses T2 (attacker-controlled content).- Rekor lookup:
ObjectHashalso goes through the go-git round-trip, so the logged hash is the T1-canonical hash — consistent with the forged verification output but not with the actual raw object.
The attack requires only that the malformed object be accepted into the local repository (bypassing server-side fsck), and that the victim runs gitsign verify.
Proof of Concept
Expected output:
Impact
- Signature binding bypass:
gitsign verifyreports a valid signature from a trusted identity for a commit that git-core resolves to completely different content (a different tree). - Signature replay without a key: An attacker can reuse any existing gitsign-signed commit to produce a new commit that passes
gitsign verifybut points to attacker-controlled content, without possessing any signing key. - Rekor tlog inconsistency:
ObjectHashalso goes through the go-git round-trip, so the hash stored in or looked up from the transparency log is the normalized hash, not the raw object hash. An auditor cross-referencing the tlog hash against the actual object store will see a mismatch. - Verification path divergence:
git verify-commitandgitsign verifyreach opposite verdicts for the same malformed commit, undermining auditability.
Recommended Remediation
Option 1: Verify against raw bytes (preferred)
Change the gitsign verify and gitsign verify-tag CLI commands to read the raw object bytes from the git object store and strip the signature header manually, mirroring what git-core does and what commandVerify already does when called by git verify-commit:
This aligns the CLI verification path with the commandVerify (git verify-commit) path that already handles raw bytes correctly.
Option 2: Detect and reject malformed objects
Add a pre-verification check in ObjectHash and in the verification path that rejects objects with duplicate field headers (duplicate tree, parent, author, committer), returning an error rather than silently normalizing:
This is a defense-in-depth measure but does not address the fundamental architectural issue of verifying re-encoded bytes.
Credit
This vulnerability was discovered and reported by bugbunny.ai.
Пакеты
github.com/sigstore/gitsign
< 0.16.0
0.16.0
Связанные уязвимости
Gitsign is a keyless Sigstore to signing tool for Git commits with your a GitHub / OIDC identity. Prior to 0.16.0, gitsign verify and gitsign verify-tag re-encode commit/tag objects through go-git's EncodeWithoutSignature before checking the signature, instead of verifying against the raw git object bytes. For malformed objects with duplicate tree headers, git-core and go-git parse different trees: git-core uses the first, go-git uses the second. A signature crafted over the go-git-normalized form (second tree) passes gitsign verify while git-core resolves the commit to a completely different tree. This breaks the invariant that a verified signature, the commit semantics git-core presents to users, and the object hash logged in Rekor all refer to the same content. This vulnerability is fixed in 0.16.0.
Gitsign is a keyless Sigstore to signing tool for Git commits with your a GitHub / OIDC identity. Prior to 0.16.0, gitsign verify and gitsign verify-tag re-encode commit/tag objects through go-git's EncodeWithoutSignature before checking the signature, instead of verifying against the raw git object bytes. For malformed objects with duplicate tree headers, git-core and go-git parse different trees: git-core uses the first, go-git uses the second. A signature crafted over the go-git-normalized form (second tree) passes gitsign verify while git-core resolves the commit to a completely different tree. This breaks the invariant that a verified signature, the commit semantics git-core presents to users, and the object hash logged in Rekor all refer to the same content. This vulnerability is fixed in 0.16.0.
Gitsign is a keyless Sigstore to signing tool for Git commits with your a GitHub / OIDC identity. Prior to 0.16.0, gitsign verify and gitsign verify-tag re-encode commit/tag objects through go-git's EncodeWithoutSignature before checking the signature, instead of verifying against the raw git object bytes. For malformed objects with duplicate tree headers, git-core and go-git parse different trees: git-core uses the first, go-git uses the second. A signature crafted over the go-git-normalized form (second tree) passes gitsign verify while git-core resolves the commit to a completely different tree. This breaks the invariant that a verified signature, the commit semantics git-core presents to users, and the object hash logged in Rekor all refer to the same content. This vulnerability is fixed in 0.16.0.
Gitsign is a keyless Sigstore to signing tool for Git commits with you ...