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 72963594adcd11de16941119db862189f266c815 Mon Sep 17 00:00:00 2001 From: raller Date: Fri, 26 Jun 2026 16:21:43 -0400 Subject: [PATCH 5/5] feat(lab5): ZAP baseline + auth + Semgrep + correlation --- submissions/lab5.md | 184 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 submissions/lab5.md diff --git a/submissions/lab5.md b/submissions/lab5.md new file mode 100644 index 000000000..32f078690 --- /dev/null +++ b/submissions/lab5.md @@ -0,0 +1,184 @@ +# Lab 5 — Submission + +## Task 1: DAST with OWASP ZAP + +### Baseline (unauthenticated) scan + +- **Duration:** ~2 minutes +- **Total unique alert types:** 10 +- **URLs spidered:** 158 + +| Severity | Count | +|----------|------:| +| High | 0 | +| Medium | 2 | +| Low | 5 | +| Informational | 3 | + +**Alerts found:** +- `[Medium]` Content Security Policy (CSP) Header Not Set (5 instances) +- `[Medium]` Cross-Domain Misconfiguration (5 instances) +- `[Low]` Cross-Origin-Embedder-Policy Header Missing or Invalid (5 instances) +- `[Low]` Cross-Origin-Opener-Policy Header Missing or Invalid (5 instances) +- `[Low]` Dangerous JS Functions (1 instance) +- `[Low]` Deprecated Feature Policy Header Set (5 instances) +- `[Low]` Timestamp Disclosure - Unix (5 instances) +- `[Info]` Modern Web Application (5 instances) +- `[Info]` Storable and Cacheable Content (1 instance) +- `[Info]` Storable but Non-Cacheable Content (5 instances) + +### Authenticated full scan + +- **Duration:** ~5 minutes +- **Total unique alert types:** 12 +- **URLs spidered:** 93 (traditional) + 447 (Ajax) + +| Severity | Count | +|----------|------:| +| High | 1 | +| Medium | 4 | +| Low | 3 | +| Informational | 4 | + +**Alerts found:** +- `[High]` SQL Injection (2 instances) +- `[Medium]` Content Security Policy (CSP) Header Not Set (5 instances) +- `[Medium]` Cross-Domain Misconfiguration (5 instances) +- `[Medium]` Missing Anti-clickjacking Header (3 instances) +- `[Medium]` Session ID in URL Rewrite (5 instances) +- `[Low]` Private IP Disclosure (1 instance) +- `[Low]` Timestamp Disclosure - Unix (5 instances) +- `[Low]` X-Content-Type-Options Header Missing (5 instances) +- `[Info]` Authentication Request Identified (1 instance) +- `[Info]` Modern Web Application (5 instances) +- `[Info]` Session Management Response Identified (2 instances) +- `[Info]` User Agent Fuzzer (3 instances) + +### The "10–20x more" claim (Lecture 5 slide 11) + +- **Ratio (auth alert types / baseline alert types):** 1.2x (12 / 10) +- **Did your run match the lecture's ratio?** No — the ratio was significantly lower than the 10–20x claim. However, the authenticated scan did find the only **High-severity** vulnerability (SQL Injection) that the unauthenticated scan completely missed. The lower ratio is likely because Juice Shop is intentionally designed with a large unauthenticated attack surface (158 URLs spidered without auth), which reduces the relative gap between the two scans. A longer authenticated scan with deeper active scanning would likely find more authenticated-only issues. + +**Two specific alerts only the authenticated scan found:** + +1. **SQL Injection (High)** — The unauthenticated spider cannot reach authenticated endpoints like `/rest/user/login` (POST) or meaningfully interact with the `q` parameter on `/rest/products/search`. The authenticated scan, running with valid session credentials, was able to fuzz these protected endpoints and detect SQL injection via error-based detection (HTTP 500 responses to `'(` and `'` payloads). + +2. **Missing Anti-clickjacking Header (Medium)** — This finding applies to authenticated pages that include sensitive UI (account dashboard, password change). The unauthenticated spider only sees the public login/registration pages, so it cannot evaluate clickjacking protections on authenticated routes. + +--- + +## Task 2: SAST with Semgrep + +### Scan configuration +- **Rulesets:** `p/owasp-top-ten`, `p/javascript`, `p/secrets` +- **Target:** Juice Shop v20.0.0 source code +- **Severity filter:** ERROR, WARNING +- **Result:** 22 findings (12 ERROR + 10 WARNING) + +### Semgrep severity breakdown + +| Severity | Count | +|----------|------:| +| ERROR | 12 | +| WARNING | 10 | +| **Total** | **22** | + +### Top 10 rules by frequency + +| # | Rule ID | Count | OWASP Category | +|---|---------|------:|----------------| +| 1 | `javascript.sequelize.security.audit.sequelize-injection-express.express-sequelize-injection` | 6 | A03: Injection | +| 2 | `yaml.github-actions.security.run-shell-injection.run-shell-injection` | 5 | A03: Injection | +| 3 | `javascript.express.security.audit.express-check-directory-listing.express-check-directory-listing` | 4 | A05: Security Misconfiguration | +| 4 | `javascript.express.security.audit.express-res-sendfile.express-res-sendfile` | 4 | A01: Broken Access Control | +| 5 | `javascript.express.security.audit.express-open-redirect.express-open-redirect` | 1 | A01: Broken Access Control | +| 6 | `javascript.jsonwebtoken.security.jwt-hardcode.hardcoded-jwt-secret` | 1 | A05: Security Misconfiguration | +| 7 | `javascript.lang.security.audit.code-string-concat.code-string-concat` | 1 | A03: Injection | + +*Note: Only 7 unique rules triggered. The top 2 rules account for 50% of all findings.* + +### Triage shortcut (Lecture 5 slide 8) + +I would fix **`javascript.sequelize.security.audit.sequelize-injection-express.express-sequelize-injection`** first if I had time for only one rule. It has the highest frequency (6 findings) and represents SQL injection vulnerabilities — the most severe class of issues in the scan. All 6 findings trace to the same root cause: unsafe string concatenation in Sequelize queries. A single fix — introducing parameterized queries at the database access layer — would eliminate all 6 findings at once, making it the highest-impact remediation. + +### False-positive sample + +**Finding to suppress:** +- **File:** `data/static/codefixes/dbSchemaChallenge_1.ts` +- **Rule:** `javascript.sequelize.security.audit.sequelize-injection-express.express-sequelize-injection` +- **Reason:** This file is an intentional vulnerable code sample used for the "dbSchemaChallenge" CTF challenge. It is not production code — it exists specifically to teach SQL injection concepts. The `// vuln-code-snippet` comment explicitly marks it as educational material. Suppressing this finding with `# nosemgrep` is appropriate because fixing it would break the challenge's learning objective. + +--- + +## Bonus: SAST/DAST Correlation + +### Correlation table + +| # | OWASP cat | ZAP alert | ZAP URI | Semgrep rule | Semgrep file:line | Confidence | +|---|-----------|-----------|---------|--------------|-------------------|------------| +| 1 | A03: Injection | SQL Injection | `/rest/products/search?q='(` | `express-sequelize-injection` | `routes/search.ts:23` | **High** (both agree on same endpoint) | +| 2 | A03: Injection | SQL Injection | `/rest/user/login` (POST, `email='`) | `express-sequelize-injection` | `routes/login.ts:34` | **High** (both agree on same endpoint) | + +### Strongest correlation deep-dive (#1 — `/rest/products/search`) + +**Vulnerable code (from Semgrep):** + +```typescript +// routes/search.ts:23 +models.sequelize.query( + `SELECT * FROM Products WHERE ((name LIKE '%${criteria}%' OR description LIKE '%${criteria}%') AND deletedAt IS NULL) ORDER BY name` +) +``` + +The `criteria` variable is derived directly from `req.query.q` without sanitization: +```typescript +let criteria: any = req.query.q === 'undefined' ? '' : req.query.q ?? '' +``` + +**Working payload (from ZAP):** +``` +'( +``` +ZAP injected `'(` as the `q` parameter and observed an HTTP 500 Internal Server Error, confirming the SQL syntax was broken by the unescaped single quote — a classic error-based SQL injection detection. + +**The fix:** + +```typescript +// Fixed code using parameterized query +models.sequelize.query( + 'SELECT * FROM Products WHERE ((name LIKE :criteria OR description LIKE :criteria) AND deletedAt IS NULL) ORDER BY name', + { + replacements: { criteria: `%${criteria}%` }, + type: QueryTypes.SELECT + } +) +``` + +**Why both tools caught it:** + +- **SAST** detected this because Semgrep's `express-sequelize-injection` rule pattern-matches the unsafe template-string concatenation (`'...${userInput}...'`) inside `sequelize.query()` — a known dangerous API pattern. +- **DAST** caught it because ZAP's active scanner fuzzed the `q` query parameter with SQL meta-characters (single quote `'` and open parenthesis `(`) and observed that the application responded with HTTP 500 Internal Server Error, confirming the database query was syntactically broken. +- The vulnerability is discoverable from both angles because the unsafe code pattern (static analysis) directly enables the runtime exploit (dynamic testing). Both tools agree: this is the highest-confidence finding type. + +### Reflection + +Lecture 5 slide 15 calls correlation findings "the highest-confidence finding type." In a real PR review, I would want the **SAST finding first** — it pinpoints the exact line of vulnerable code (`routes/search.ts:23`) and explains *why* it's dangerous (user input flows directly into a SQL query without parameterization). The DAST evidence then validates that the vulnerability is actually exploitable at runtime. SAST drives the fix; DAST confirms the fix worked after deployment. + + +## Raw Results + +| Result file | Location | +|-------------|----------| +| ZAP Baseline JSON | `labs/lab5/results/baseline-report.json` | +| ZAP Authenticated JSON | `labs/lab5/zap/zap-report-auth.json` | +| ZAP Authenticated HTML | `labs/lab5/zap/report-auth.html` | +| Semgrep JSON | `labs/lab5/results/semgrep.json` | +| ZAP Comparison | `labs/lab5/analysis/zap-comparison.txt` | + + +PR checklist: +```text +- [x] Task 1 — ZAP baseline + auth + ratio analysis +- [x] Task 2 — Semgrep top-10 + triage shortcut + false positive +- [x] Bonus — Correlation table with 2 confirmed cross-tool findings +```