From 22ad763583bd0b46d70807d15a5e0b59a74a97cc Mon Sep 17 00:00:00 2001 From: raller Date: Fri, 12 Jun 2026 07:31:29 -0400 Subject: [PATCH 1/5] feat(lab1): add CI smoke test workflow --- .github/workflows/lab1-smoke.yml | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/lab1-smoke.yml diff --git a/.github/workflows/lab1-smoke.yml b/.github/workflows/lab1-smoke.yml new file mode 100644 index 000000000..0f1f17093 --- /dev/null +++ b/.github/workflows/lab1-smoke.yml @@ -0,0 +1,49 @@ +name: Lab 1 — Juice Shop Smoke Test + +on: + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + smoke-test: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Pull and run Juice Shop + run: | + docker run -d --name juice-shop \ + -p 127.0.0.1:3000:3000 \ + bkimminich/juice-shop:v20.0.0 + + - name: Wait for Juice Shop to be healthy + run: | + echo "Waiting for Juice Shop to start..." + for i in $(seq 1 30); do + if curl --silent --fail http://127.0.0.1:3000/rest/admin/application-version >/dev/null; then + echo "Juice Shop is up!" + exit 0 + fi + echo "Attempt $i/30 — not ready yet, sleeping 2s..." + sleep 2 + done + echo "Juice Shop failed to start within 60s" + docker logs juice-shop + exit 1 + + - name: Verify homepage returns HTTP 200 + run: | + curl -I -s http://127.0.0.1:3000 | head -5 + curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" http://127.0.0.1:3000 + + - name: Verify product API + run: | + curl -s http://127.0.0.1:3000/api/Products | jq '.data | length' + + - name: Verify version endpoint + run: | + curl -s http://127.0.0.1:3000/rest/admin/application-version | jq From a838731b40d08503e8db54f6d3085ddb5f948f43 Mon Sep 17 00:00:00 2001 From: raller Date: Fri, 12 Jun 2026 07:56:29 -0400 Subject: [PATCH 2/5] feat(lab1): juice shop deploy + PR template + triage report --- .github/PULL_REQUEST_TEMPLATE.md | 24 ++++++++ submissions/lab1.md | 100 +++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 submissions/lab1.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..0092ff1b1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,24 @@ +## Goal + + +## Changes + +- +- +- + +## Testing + +```bash +# +# +``` + +## Artifacts & Screenshots + +- + +## Checklist +- [ ] Title is clear (`feat(labN): ` style) +- [ ] No secrets/large temp files committed +- [ ] Submission file at `submissions/labN.md` exists diff --git a/submissions/lab1.md b/submissions/lab1.md new file mode 100644 index 000000000..59c20ab82 --- /dev/null +++ b/submissions/lab1.md @@ -0,0 +1,100 @@ +# Lab 1 — Submission + +## Triage Report: OWASP Juice Shop + +### Scope & Asset +- Asset: OWASP Juice Shop (local lab instance) +- Image: `bkimminich/juice-shop:v20.0.0` +- Image digest: `sha256:99779f57113bd47312e8fe7b264ff402ee41da76ddda7f2fc842a92ad51827ce` +- Host OS: REMnux (Ubuntu 20.04-based) +- Docker version: `Docker version 26.1.3, build 26.1.3-0ubuntu1~20.04.1` + +### Deployment Details +- Run command used: `docker run -d --name juice-shop -p 127.0.0.1:3000:3000 bkimminich/juice-shop:v20.0.0` +- Access URL: http://127.0.0.1:3000 +- Network exposure: 127.0.0.1 only? [x] Yes [ ] No +- Container restart policy: default `no` (no `--restart` flag used) + +### Health Check +- HTTP code on `/`: `200` +- API check (first 200 chars of `/api/Products`): + ```json + {"status":"success","data":[{"id":1,"name":"Apple Juice (1000ml)","description":"The all-time classic.","price":1.99,"deluxePrice":0.99,"image":"apple_juice.jpg","createdAt":"2026-06-12T10:31:40.266Z" + ``` +- Container uptime: 4bd57343c74b bkimminich/juice-shop:v20.0.0 "/nodejs/bin/node /j…" 14 minutes ago Up 14 minutes 127.0.0.1:3000->3000/tcp juice-shop + +### Initial Surface Snapshot (from browser exploration) +- Login/Registration visible: [x] Yes [ ] No — notes: Login and Sign Up buttons present +- Product listing/search present: [x] Yes [ ] No — notes: Product cards displayed on homepage, search field available +- Admin or account area discoverable: [ ] Yes [x] No — notes: No direct admin link on landing page; authentication required +- Client-side errors in DevTools console: [ ] Yes [x] No — notes: Console clean, no errors detected +- Pre-populated local storage / cookies: language (set to 'en'), token (empty until login) + + +### Security Headers (Quick Look) +HTTP/1.1 200 OK +Access-Control-Allow-Origin: * +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +Feature-Policy: payment 'self' +X-Recruiting: /#/jobs +Accept-Ranges: bytes +Cache-Control: public, max-age=0 +Last-Modified: Fri, 12 Jun 2026 10:31:40 GMT +ETag: W/"26af-19ebb633283" +Content-Type: text/html; charset=UTF-8 +Content-Length: 9903 +Vary: Accept-Encoding +Date: Fri, 12 Jun 2026 10:34:55 GMT +Connection: keep-alive +Keep-Alive: timeout=5 + +Which of these are MISSING? (cross-reference Lecture 1 OWASP Top 10:2025 — A06) +- [x] `Content-Security-Policy` - MISSING +- [x] `Strict-Transport-Security` - MISSING +- [ ] `X-Content-Type-Options: nosniff` +- [ ] `X-Frame-Options` + +### Top 3 Risks Observed (2-3 sentences each, in your own words) +1. Broken Access Control (A01) — The API endpoint /api/Products//reviews returns data without authentication. This allows an unauthenticated attacker to read other users' reviews and potentially exfiltrate data, violating the principle of least privilege. +2. Cryptographic Failures (A02) — Absence of the HSTS header and operation over HTTP (in local environment) means that in production traffic could be intercepted. If the application transmits credentials or tokens without TLS, this leads to sensitive data exposure. +3. Security Misconfiguration (A05) — The complete absence of CSP and HSTS indicates a default 'open' configuration. Combined with intentionally vulnerable Juice Shop code, this creates a broad surface for XSS, clickjacking, and MIME-sniffing attacks. + +## PR Template Setup + +- File: `.github/PULL_REQUEST_TEMPLATE.md` +- Sections included: Goal / Changes / Testing / Artifacts & Screenshots +- Checklist items: + - Title is clear (`feat(labN): ` style) + - No secrets/large temp files committed + - Submission file at `submissions/labN.md` exists +- Auto-fill verified: [x] Yes — PR description showed my template (screenshot or link to draft PR) + +## GitHub Community + +### Actions Completed +- [x] Starred course repository +- [x] Starred [simple-container-com/api](https://github.com/simple-container-com/api) +- [x] Following Professor [@Cre-eD](https://github.com/Cre-eD) +- [x] Following TA [@Naghme98](https://github.com/Naghme98) +- [x] Following TA [@pierrepicaud](https://github.com/pierrepicaud) +- [x] Following 3+ classmates: `<, @username2, @username3>` + +### Why Stars Matter in Open Source +Stars are the currency of attention in the open-source ecosystem. A repository with 1000+ stars attracts more contributors and sponsors than an equivalent one with 10 stars. + +## Bonus: CI Smoke Test + +- Workflow file: `.github/workflows/lab1-smoke.yml` +- Trigger: `pull_request` on main +- Run URL (must be green): https://github.com/raaller/DevSecOps-Intro/actions/runs/27413678469 +- Workflow run duration: 27s +- Curl response excerpt: + ``` + HTTP/1.1 200 OK + Access-Control-Allow-Origin: * + X-Content-Type-Options: nosniff + X-Frame-Options: SAMEORIGIN + Feature-Policy: payment 'self' + HTTP Status: 200 + ``` From fe4ae7ec4217f1a31ab3863869934a99f6c26986 Mon Sep 17 00:00:00 2001 From: raller Date: Fri, 12 Jun 2026 07:57:42 -0400 Subject: [PATCH 3/5] feat(lab1): juice shop deploy + PR template + triage report --- submissions/lab1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submissions/lab1.md b/submissions/lab1.md index 59c20ab82..d7bb7a6c1 100644 --- a/submissions/lab1.md +++ b/submissions/lab1.md @@ -78,7 +78,7 @@ Which of these are MISSING? (cross-reference Lecture 1 OWASP Top 10:2025 — A06 - [x] Following Professor [@Cre-eD](https://github.com/Cre-eD) - [x] Following TA [@Naghme98](https://github.com/Naghme98) - [x] Following TA [@pierrepicaud](https://github.com/pierrepicaud) -- [x] Following 3+ classmates: `<, @username2, @username3>` +- [x] Following 3+ classmates ### Why Stars Matter in Open Source Stars are the currency of attention in the open-source ecosystem. A repository with 1000+ stars attracts more contributors and sponsors than an equivalent one with 10 stars. From 7f777bf2b97dee0362254be52e4d9aba16948893 Mon Sep 17 00:00:00 2001 From: raller Date: Thu, 18 Jun 2026 15:29:27 -0400 Subject: [PATCH 4/5] test: first signed commit --- submissions/lab3.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 submissions/lab3.md diff --git a/submissions/lab3.md b/submissions/lab3.md new file mode 100644 index 000000000..61fa24bee --- /dev/null +++ b/submissions/lab3.md @@ -0,0 +1 @@ +lab3 signing test From 7c584606d650a30ee4c3660fd66dea3256a5e19d Mon Sep 17 00:00:00 2001 From: raller Date: Fri, 17 Jul 2026 12:10:13 -0400 Subject: [PATCH 5/5] feat(lab12): kata vs runc isolation + perf + escape PoC --- submissions/lab12.md | 287 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 submissions/lab12.md diff --git a/submissions/lab12.md b/submissions/lab12.md new file mode 100644 index 000000000..d6a3ab507 --- /dev/null +++ b/submissions/lab12.md @@ -0,0 +1,287 @@ +# Lab 12 — BONUS — Submission + +## Task 1: Install + Hello-World + +### Host environment + +- **Operating system:** Ubuntu 20.04.6 LTS (REMnux) +- **Kernel (host):** + +```text +Linux remnux 5.15.0-139-generic #149~20.04.1-Ubuntu SMP Wed Apr 16 08:29:56 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux +``` + +- **KVM accessible:** + +```text +=== loaded KVM modules === +kvm_intel 380928 0 +kvm 1019904 1 kvm_intel + +=== /dev/kvm === +crw-rw----+ 1 root kvm 10, 232 Jul 17 11:55 /dev/kvm +``` + +- **containerd version:** + +```text +containerd github.com/containerd/containerd 1.7.24 +``` + +- **nerdctl version:** + +```text +nerdctl version 2.3.4 +``` + +- **runc version:** + +```text +runc version 1.1.12-0ubuntu2~20.04.1 +spec: 1.0.2-dev +go: go1.21.1 +libseccomp: 2.5.1 +``` + +### Kata installation + +- **Kata version:** `3.32.0` +- **Kata hypervisor configuration:** QEMU through the runtime-rs configuration + `/opt/kata/share/defaults/kata-containers/runtime-rs/configuration-qemu-runtime-rs.toml`. + QEMU was used because the Dragonball runtime did not complete reliably under nested virtualization in VirtualBox. +- **containerd runtime configuration:** + +```toml +[plugins.'io.containerd.grpc.v1.cri'.containerd.runtimes.kata] + runtime_type = 'io.containerd.kata.v2' +``` + +### Kernel inside containers + +**runc:** + +```text +Linux 9730d7860109 5.15.0-139-generic #149~20.04.1-Ubuntu SMP Wed Apr 16 08:29:56 UTC 2025 x86_64 Linux +--- cpuinfo --- +processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +``` + +**Kata:** + +```text +Linux 1c60be6cde44 6.18.35 #1 SMP Mon Jun 15 12:55:58 UTC 2026 x86_64 Linux +--- cpuinfo --- +processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +``` + +The runc container reports the same `5.15.0-139-generic` kernel as the host because runc isolates processes with Linux namespaces and cgroups while sharing the host kernel. The Kata container reports a separate `6.18.35` guest kernel because the workload executes inside a dedicated micro-VM. Consequently, the exact runc-specific code path involved in CVE-2024-21626 is not used as the Kata container boundary. Even if code compromises the guest workload, reaching the outer host additionally requires a vulnerability in the Kata runtime, hypervisor, or a shared-device interface. + +Evidence: + +- [`runc-kernel.txt`](../labs/lab12/results/runc-kernel.txt) +- [`kata-kernel.txt`](../labs/lab12/results/kata-kernel.txt) +- [`kernel-release-comparison.txt`](../labs/lab12/results/kernel-release-comparison.txt) + +## Task 2: Isolation + Performance + +### Isolation: `/dev` diff + +```diff +--- runc-devs.txt ++++ kata-devs.txt +@@ -1,4 +1,3 @@ +-core + fd + full + mqueue +``` + +The device lists were almost identical for this minimal Alpine workload, but the runc container exposed the additional `/dev/core` compatibility link while the Kata guest did not. The small visible diff does not mean the runtimes provide equivalent isolation: the primary boundary is the separate guest kernel and VMM, not the number of device entries. + +### Isolation: capability sets + +**runc:** + +```text +CapInh: 0000000000000000 +CapPrm: 00000000a80425fb +CapEff: 00000000a80425fb +CapBnd: 00000000a80425fb +CapAmb: 0000000000000000 +``` + +**Kata:** + +```text +CapInh: 0000000000000000 +CapPrm: 00000000a80425fb +CapEff: 00000000a80425fb +CapBnd: 00000000a80425fb +CapAmb: 0000000000000000 +``` + +The capability masks were identical. Capabilities constrain processes relative to the kernel that services their system calls: with runc this is the host kernel, whereas with Kata it is the guest kernel. Therefore, identical masks do not remove Kata's additional VM boundary. + +### Startup time + +One warm-up run was performed for each runtime, followed by five measured runs. + +| Runtime | Run 1 (s) | Run 2 (s) | Run 3 (s) | Run 4 (s) | Run 5 (s) | Average (s) | Median (s) | +|---|---:|---:|---:|---:|---:|---:|---:| +| runc | 0.5266 | 0.5523 | 0.4812 | 0.4239 | 0.3721 | **0.4712** | 0.4812 | +| Kata | 7.8558 | 7.5746 | 7.0677 | 6.5455 | 7.2109 | **7.2509** | 7.2109 | + +**Measured cold-start overhead: approximately `15.39×`.** + +This is higher than the approximate `5×` reference value discussed in Reading 12. The measurement was performed inside REMnux running under VirtualBox with nested VT-x, so each Kata start includes micro-VM initialization through an additional virtualization layer. The result is valid for this test environment but should not be generalized to bare-metal Kata deployments. + +### I/O throughput: 100 MiB `dd` through `/dev/null` + +| Runtime | Elapsed time | Reported throughput | +|---|---:|---:| +| runc | 0.004719 s | **20.7 GB/s** | +| Kata | 0.023807 s | **4.1 GB/s** | + +For this synthetic in-memory copy, Kata achieved approximately one fifth of the runc throughput, or roughly `5.05×` lower throughput. This benchmark does not represent persistent disk performance because both input and output were memory-backed pseudo-devices; it primarily captures guest execution and virtualization overhead for this specific command. + +### Trade-off analysis + +Kata is appropriate for multi-tenant CI runners, hosted build systems, plugin execution, and SaaS platforms that execute code supplied by mutually untrusted customers. In those environments, isolating each workload behind a separate guest kernel materially reduces the impact of host-kernel and runc-runtime escape classes, and the measured startup cost can be accepted or amortized by longer-running workloads. It is less attractive for trusted, single-tenant, short-lived batch jobs or latency-sensitive functions where the additional VM boot time dominates useful execution. A production decision should also account for operational complexity, memory density, workload compatibility, and whether the threat model actually includes hostile tenants. + +Evidence: + +- [`dev-diff.txt`](../labs/lab12/results/dev-diff.txt) +- [`runc-caps.txt`](../labs/lab12/results/runc-caps.txt) +- [`kata-caps.txt`](../labs/lab12/results/kata-caps.txt) +- [`startup-bench.csv`](../labs/lab12/results/startup-bench.csv) +- [`startup-summary.txt`](../labs/lab12/results/startup-summary.txt) +- [`runc-io.txt`](../labs/lab12/results/runc-io.txt) +- [`kata-io.txt`](../labs/lab12/results/kata-io.txt) + +## Bonus: Privileged-Container Host Escape Demonstration + +### Vector chosen + +- **Option:** B — privileged-container host write, implemented through the host PID namespace and `/proc/1/root`. +- **Why:** A privileged runc container with `--pid=host` can resolve PID 1's root filesystem through `/proc/1/root` and modify a host file without an explicit host-directory bind mount. The same request is rejected by the Kata runtime and the host file remains unchanged. + +The assignment's suggested writable bind-mount form was not used as the isolation proof. A writable bind mount explicitly delegates the selected host path to the workload and Kata normally transports such shared directories through virtio-fs or another host–guest filesystem mechanism. Successful access to an explicitly shared path is therefore not, by itself, a VM escape. + +### Test preparation + +```bash +sudo touch /tmp/lab12-target +sudo chown root:root /tmp/lab12-target +printf '%s\n' original | sudo tee /tmp/lab12-target +``` + +### runc: host write succeeds + +Command: + +```bash +sudo nerdctl run \ + --name lab12-pid-runc \ + --rm \ + --net=none \ + --privileged \ + --pid=host \ + alpine:3.20 \ + sh -c ' + echo "OVERWRITTEN THROUGH HOST PID NAMESPACE" \ + > /proc/1/root/tmp/lab12-target + cat /proc/1/root/tmp/lab12-target + ' +``` + +Container output: + +```text +OVERWRITTEN THROUGH HOST PID NAMESPACE +``` + +Host verification: + +```bash +sudo cat /tmp/lab12-target +``` + +```text +OVERWRITTEN THROUGH HOST PID NAMESPACE +``` + +Result: container exit status `0`; the host file was modified. + +### Kata: same host-namespace request is blocked + +After resetting the target to `original`, the same request was run with the Kata runtime: + +```bash +sudo nerdctl run \ + --name lab12-pid-kata \ + --rm \ + --net=none \ + --runtime=io.containerd.kata.v2 \ + --privileged \ + --pid=host \ + alpine:3.20 \ + sh -c ' + echo "ATTEMPTED THROUGH KATA PID NAMESPACE" \ + > /proc/1/root/tmp/lab12-target + cat /proc/1/root/tmp/lab12-target + ' +``` + +Runtime output: + +```text +time="2026-07-17T12:00:28-04:00" level=fatal msg="failed to create shim task: ... get host path failed ... No such file or directory (os error 2)" +``` + +Host verification: + +```bash +sudo cat /tmp/lab12-target +``` + +```text +original +``` + +Result: Kata container creation returned exit status `1`; the host file remained unchanged. + +### Threat-model implication + +With runc and `--pid=host`, the container shares the host PID namespace, so `/proc/1/root` resolves to the host init process's root filesystem. Combined with `--privileged`, this allows direct host modification and demonstrates why privileged host-namespace containers are effectively outside the normal container security boundary. Kata cannot expose the outer host PID namespace in the same way because the workload is created inside a separate VM with its own kernel and process namespace; in this run, the runtime rejected the required host-path mapping before container creation. + +This maps to multi-tenant CI runners and misconfigured Kubernetes workloads that grant privileged mode or host namespace access to untrusted jobs. Kata adds a strong boundary against this class, but it does not make privileged workloads harmless and does not address every threat. It does not inherently eliminate hypervisor vulnerabilities, virtio-fs/shared-device bugs, denial of service, microarchitectural side channels, cross-tenant timing leakage, or attacks against resources that the operator explicitly shares with the guest. Confidential Containers are required when the threat model also treats the host administrator or host infrastructure as untrusted. + +Evidence: + +- [`bonus-pid-summary.txt`](../labs/lab12/results/bonus-pid-summary.txt) +- [`bonus-pid-runc-container.txt`](../labs/lab12/results/bonus-pid-runc-container.txt) +- [`bonus-pid-runc-host.txt`](../labs/lab12/results/bonus-pid-runc-host.txt) +- [`bonus-pid-kata-container.txt`](../labs/lab12/results/bonus-pid-kata-container.txt) +- [`bonus-pid-kata-host.txt`](../labs/lab12/results/bonus-pid-kata-host.txt) +- [`collector-status.txt`](../labs/lab12/results/collector-status.txt) + +## Result summary + +| Requirement | Result | +|---|---| +| Kata 3.x installed | PASS — `3.32.0` | +| containerd Kata runtime registered | PASS — `io.containerd.kata.v2` | +| runc workload runs | PASS | +| Kata workload runs | PASS | +| Different kernels demonstrated | PASS — host/runc `5.15.0-139`, Kata `6.18.35` | +| `/dev` comparison captured | PASS | +| Capability comparison captured | PASS | +| Five startup measurements per runtime | PASS | +| I/O benchmark captured | PASS | +| runc privileged host write | PASS — host file overwritten | +| Kata host-write attempt blocked | PASS — runtime rejected request, host file unchanged | +| Collector final status | **SUCCESS** |