Skip to content

Feature/lab8#4

Open
raaller wants to merge 5 commits into
mainfrom
feature/lab8
Open

Feature/lab8#4
raaller wants to merge 5 commits into
mainfrom
feature/lab8

Conversation

@raaller

@raaller raaller commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Goal

Sign the OWASP Juice Shop v20.0.0 container image by immutable digest using Cosign, demonstrate that a substituted image cannot pass signature verification, attach verified CycloneDX SBOM and SLSA provenance attestations, and implement blob signing as a mitigation for the Codecov 2021 attack class.

Changes

  • submissions/lab8.md

    • Cosign and Docker environment information
    • Local OCI registry setup evidence
    • Original Juice Shop image digest
    • Image signing and verification output
    • Tampered-image verification failure
    • Original-image verification after the tamper test
    • Digest-binding security analysis
    • CycloneDX SBOM attestation evidence
    • Source and attested SBOM comparison
    • SLSA provenance attestation evidence
    • Admission-time SBOM verification discussion
    • Blob signing and verification evidence
    • Tampered-blob verification failure
    • Codecov 2021 mitigation analysis
  • labs/lab8/keys/cosign.pub

    • Adds the public Cosign verification key
    • The corresponding private key is excluded from Git
  • labs/lab8/keys/.gitignore

    • Prevents cosign.key from being committed
  • labs/lab8/results/

    • Saves original image-signature verification evidence
    • Saves expected tampered-image verification failure
    • Saves decoded CycloneDX attestation evidence
    • Saves SBOM component-count and normalized-content comparisons
    • Saves SLSA provenance predicate and verification evidence
    • Saves the signed release blob and Cosign bundle
    • Saves successful original-blob verification
    • Saves expected tampered-blob verification failure

Environment

Docker: 27.5.1
Cosign: v2.4.3
jq:     1.7.1

Testing

Local registry and image push

docker run -d \
  --name lab8-registry \
  -p 127.0.0.1:5000:5000 \
  registry:3

docker pull bkimminich/juice-shop:v20.0.0

docker tag \
  bkimminich/juice-shop:v20.0.0 \
  localhost:5000/juice-shop:v20.0.0

docker push localhost:5000/juice-shop:v20.0.0

Result:

Original digest:
localhost:5000/juice-shop@sha256:8c76bce948965bcb2ad33c24a659d58f307d679ff48ec253a3d29138329f3c0d

Cosign image signing

COSIGN_PASSWORD=<redacted> cosign sign \
  --key labs/lab8/keys/cosign.key \
  --yes \
  --tlog-upload=false \
  --allow-http-registry \
  localhost:5000/juice-shop@sha256:8c76bce948965bcb2ad33c24a659d58f307d679ff48ec253a3d29138329f3c0d

Result:

Pushing signature to: localhost:5000/juice-shop

Original image verification

cosign verify \
  --key labs/lab8/keys/cosign.pub \
  --insecure-ignore-tlog \
  --allow-http-registry \
  localhost:5000/juice-shop@sha256:8c76bce948965bcb2ad33c24a659d58f307d679ff48ec253a3d29138329f3c0d

Result:

The cosign claims were validated.
The signatures were verified against the specified public key.

Verified manifest digest:

sha256:8c76bce948965bcb2ad33c24a659d58f307d679ff48ec253a3d29138329f3c0d

Tampered-image verification

Alpine 3.20 was pushed under a Juice Shop-looking tag:

docker pull alpine:3.20

docker tag \
  alpine:3.20 \
  localhost:5000/juice-shop:v20.0.0-tampered

docker push localhost:5000/juice-shop:v20.0.0-tampered

Tampered digest:

localhost:5000/juice-shop@sha256:6c2a9711b0a9f32b0239d9222eb1072309cf46c6431d319ae249186d811a987c

Verification:

cosign verify \
  --key labs/lab8/keys/cosign.pub \
  --insecure-ignore-tlog \
  --allow-http-registry \
  localhost:5000/juice-shop@sha256:6c2a9711b0a9f32b0239d9222eb1072309cf46c6431d319ae249186d811a987c

Result:

Exit code: 10

Error: no signatures found
error during command execution: no signatures found

The failure is expected because the signature is bound to the original immutable digest rather than the mutable image tag.

Original-image sanity verification

The original digest was verified again after the tamper test:

The cosign claims were validated.
The signatures were verified against the specified public key.

This confirms that pushing an unsigned image under another tag does not affect the signature associated with the original digest.

CycloneDX SBOM attestation

COSIGN_PASSWORD=<redacted> cosign attest \
  --key labs/lab8/keys/cosign.key \
  --type cyclonedx \
  --predicate labs/lab4/juice-shop.cdx.json \
  --yes \
  --tlog-upload=false \
  --allow-http-registry \
  localhost:5000/juice-shop@sha256:8c76bce948965bcb2ad33c24a659d58f307d679ff48ec253a3d29138329f3c0d

Verification:

cosign verify-attestation \
  --key labs/lab8/keys/cosign.pub \
  --insecure-ignore-tlog \
  --allow-http-registry \
  --type cyclonedx \
  localhost:5000/juice-shop@sha256:8c76bce948965bcb2ad33c24a659d58f307d679ff48ec253a3d29138329f3c0d

Results:

CycloneDX version:          1.6
Source component count:    3069
Attested component count:  3069
Component-count diff:      empty
Full normalized SBOM diff: empty

The decoded attestation is bound to:

sha256:8c76bce948965bcb2ad33c24a659d58f307d679ff48ec253a3d29138329f3c0d

Predicate type:

https://cyclonedx.org/bom

SLSA provenance attestation

COSIGN_PASSWORD=<redacted> cosign attest \
  --key labs/lab8/keys/cosign.key \
  --type slsaprovenance \
  --predicate labs/lab8/results/provenance-predicate.json \
  --yes \
  --tlog-upload=false \
  --allow-http-registry \
  localhost:5000/juice-shop@sha256:8c76bce948965bcb2ad33c24a659d58f307d679ff48ec253a3d29138329f3c0d

Verification:

cosign verify-attestation \
  --key labs/lab8/keys/cosign.pub \
  --insecure-ignore-tlog \
  --allow-http-registry \
  --type slsaprovenance \
  localhost:5000/juice-shop@sha256:8c76bce948965bcb2ad33c24a659d58f307d679ff48ec253a3d29138329f3c0d

Results:

Builder ID:
https://localhost/lab8-artemii-mashanov

Build type:
https://example.com/lab8/local-build

Source URI:
https://github.com/raaller/DevSecOps-Intro.git

Source revision:
7f777bf2b97dee0362254be52e4d9aba16948893

Predicate type:

https://slsa.dev/provenance/v0.2

Blob signing

COSIGN_PASSWORD=<redacted> cosign sign-blob \
  --key labs/lab8/keys/cosign.key \
  --yes \
  --bundle labs/lab8/results/my-tool.tar.gz.bundle \
  labs/lab8/results/my-tool.tar.gz

Created artifacts:

labs/lab8/results/my-tool.tar.gz
labs/lab8/results/my-tool.tar.gz.bundle

Original blob verification

cosign verify-blob \
  --key labs/lab8/keys/cosign.pub \
  --bundle labs/lab8/results/my-tool.tar.gz.bundle \
  --insecure-ignore-tlog \
  labs/lab8/results/my-tool.tar.gz

Result:

Verified OK

Tampered-blob verification

A malicious payload was appended to a copy of the signed archive before verification.

Result:

Exit code: 1

Error: invalid signature when validating ASN.1 encoded signature
error during command execution: invalid signature when validating ASN.1 encoded signature

The failure is expected because the signature is bound to the original artifact byte stream.

Security analysis

Digest binding

OCI tags are mutable and can be reassigned to different manifests. Cosign signs the immutable manifest digest, so replacing or retagging an image produces a different digest that cannot satisfy the original signature.

Signed SBOM

An image signature proves that a specific digest was approved by the expected signer, but it does not describe the image contents. The signed CycloneDX attestation provides a verifiable software inventory bound to the same digest, allowing admission policies and incident-response tooling to determine whether a vulnerable dependency is present.

Codecov 2021 mitigation

The Codecov uploader attack relied on consumers executing modified remote content without authenticating its bytes. Running cosign verify-blob before executing the downloaded artifact would reject an attacker-modified uploader because the downloaded content would no longer match the signed artifact.

Artifacts

  • Submission report:

    • submissions/lab8.md
  • Cosign key material:

    • labs/lab8/keys/cosign.pub
    • labs/lab8/keys/.gitignore
  • Image signing evidence:

    • labs/lab8/results/juice-shop-digest.txt
    • labs/lab8/results/sign-image.txt
    • labs/lab8/results/verify-original.json
    • labs/lab8/results/verify-tampered.txt
    • labs/lab8/results/verify-tampered.exit-code.txt
    • labs/lab8/results/verify-original-sanity.json
  • SBOM attestation evidence:

    • labs/lab8/results/sbom-attest.txt
    • labs/lab8/results/sbom-attestation-report-excerpt.json
    • labs/lab8/results/sbom-component-counts.txt
    • labs/lab8/results/sbom-component-count-diff.txt
    • labs/lab8/results/sbom-full-normalized-diff.txt
  • Provenance evidence:

    • labs/lab8/results/provenance-predicate.json
    • labs/lab8/results/provenance-attest.txt
    • labs/lab8/results/provenance-statement.json
    • labs/lab8/results/provenance-verify.jsonl
  • Blob-signing evidence:

    • labs/lab8/results/my-tool.tar.gz
    • labs/lab8/results/my-tool.tar.gz.bundle
    • labs/lab8/results/blob-sign.txt
    • labs/lab8/results/blob-verify-success.txt
    • labs/lab8/results/blob-tamper.txt
    • labs/lab8/results/blob-tamper.exit-code.txt

The private key labs/lab8/keys/cosign.key is intentionally excluded from Git.

Checklist

  • Title follows the feat(labN): <topic> convention
  • No generated scanner output committed
  • Submission report exists at submissions/lab8.md
  • Task 1 — Image signed + tamper demo (both shown)
  • Task 2 — SBOM + provenance attestations attached and verified
  • Bonus — Blob signed + verify-blob success + tamper failure

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant