From 48343ae77d2c76aa3c19aaf78d3aa0b330c3ae31 Mon Sep 17 00:00:00 2001 From: Troshkins Date: Thu, 11 Jun 2026 16:03:42 +0300 Subject: [PATCH 1/5] feat(lab1): juice shop deploy + PR template + triage report --- .github/PULL_REQUEST_TEMPLATE.md | 27 ++++++++++ .github/workflows/lab1-smoke.yml | 45 +++++++++++++++++ submissions/lab1.md | 84 ++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/lab1-smoke.yml 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..bf4cf59ad --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,27 @@ +## Goal + + + +## Changes + + + +- + +## Testing + + + +- + +## 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/.github/workflows/lab1-smoke.yml b/.github/workflows/lab1-smoke.yml new file mode 100644 index 000000000..c38df6740 --- /dev/null +++ b/.github/workflows/lab1-smoke.yml @@ -0,0 +1,45 @@ +name: Lab 1 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: Run OWASP 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 become healthy + run: | + for i in $(seq 1 30); do + if curl --silent --fail http://127.0.0.1:3000/rest/admin/application-version; then + echo + echo "Juice Shop version endpoint is healthy" + exit 0 + fi + + echo "Waiting for Juice Shop... attempt $i/30" + sleep 2 + done + + echo "Juice Shop did not become healthy within 60 seconds" + docker logs juice-shop + exit 1 + + - name: Verify homepage returns HTTP 200 + run: | + curl --silent --fail --show-error http://127.0.0.1:3000 > /dev/null + echo "Homepage returned HTTP 200" diff --git a/submissions/lab1.md b/submissions/lab1.md new file mode 100644 index 000000000..7e6851d17 --- /dev/null +++ b/submissions/lab1.md @@ -0,0 +1,84 @@ +# 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:fd58bdc9745416afce8184ee0666278a436574633ea7880365153a63bfd418b0 +- Host OS: Arch Linux, Linux 7.0.10-arch1-1, x86_64 +- Docker version: 29.5.2 + +### 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? Yes +- Container restart policy: No + +### Health Check +- HTTP code on `/`: 200 +- API check (first 200 chars of `/rest/products`): {"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-10T21:37:17.403Z" +- Container uptime: 12 hours + +### Initial Surface Snapshot (from browser exploration) +- Login/Registration visible: Yes +- Product listing/search present: Yes +- Admin or account area discoverable: Yes +- Client-side errors in DevTools console: No +- Pre-populated local storage / cookies: Before authentication, Local Storage for `http://127.0.0.1:3000` was empty. Cookies were present: `continueCode`, `language=en`, and `welcomebanner_status=dismiss`. After registration/login, a `token` value appeared in both Local Storage and Cookies; the token value was redacted because it is an authentication token. + +### Security Headers (Quick Look) +Run: `curl -I http://127.0.0.1:3000 2>&1 | head -20`. Paste output: +``` +% Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed + 0 9903 0 0 0 0 0 0 0 +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: Wed, 10 Jun 2026 21:37:19 GMT +ETag: W/"26af-19eb377e2a7" +Content-Type: text/html; charset=UTF-8 +Content-Length: 9903 +Vary: Accept-Encoding +Date: Thu, 11 Jun 2026 09:25:54 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` +- [X] `Strict-Transport-Security` +- [ ] `X-Content-Type-Options: nosniff` +- [ ] `X-Frame-Options` + +### Top 3 Risks Observed (2-3 sentences each, in your own words) +1. **Authentication token stored client-side** — After registration/login, a JWT-like `token` appeared in both Local Storage and Cookies. This matters because tokens stored in browser-accessible storage can become high-value targets. This maps to OWASP Top 10:2025 A07 — Identification & Authentication Failures. +2. **Missing security headers** — The initial HTTP response does not include several browser security headers. This matters because the browser receives fewer built-in protections against attacks. This maps to OWASP Top 10:2025 A06 +3. **Public product and review API surface** — Product data and review-related API calls are visible from the browser Network tab during normal browsing. It expands the attack surface and should be tested later for missing authorization checks, unauthorized data access, and unsafe input handling. This maps to OWASP Top 10:2025 A01 — Broken Access Control. + +## 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: [ ] Yes — pending until I open a draft PR and GitHub shows the template in the PR description. + +## GitHub Community + +Starring repositories matters in open source because it helps make useful projects more visible and also lets me quickly find them later. Following developers is useful for team projects and professional growth because it helps me track their public work, learn from their activity, and stay connected with people from the course. + +## Bonus: CI Smoke Test + +- Workflow file: `.github/workflows/lab1-smoke.yml` +- Trigger: `pull_request` on `main` +- Run URL (must be green): pending until the PR is opened and GitHub Actions runs +- Workflow run duration: pending +- Curl response excerpt: pending From 3322a98496c8bca8ad0ef348a2c4087d33b7258f Mon Sep 17 00:00:00 2001 From: Troshkins Date: Thu, 11 Jun 2026 16:45:09 +0300 Subject: [PATCH 2/5] docs(lab1): document smoke test run --- submissions/lab1.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/submissions/lab1.md b/submissions/lab1.md index 7e6851d17..1bbbcee39 100644 --- a/submissions/lab1.md +++ b/submissions/lab1.md @@ -69,7 +69,7 @@ Which of these are MISSING? (cross-reference Lecture 1 OWASP Top 10:2025 — A06 - Title is clear (`feat(labN): ` style) - No secrets/large temp files committed - Submission file at `submissions/labN.md` exists -- Auto-fill verified: [ ] Yes — pending until I open a draft PR and GitHub shows the template in the PR description. +- Auto-fill verified: [ ] No — the template file exists at `.github/PULL_REQUEST_TEMPLATE.md`, but it did not auto-fill for the first PR because this PR introduces the template itself. The PR body was filled manually using the same template structure. ## GitHub Community @@ -79,6 +79,11 @@ Starring repositories matters in open source because it helps make useful projec - Workflow file: `.github/workflows/lab1-smoke.yml` - Trigger: `pull_request` on `main` -- Run URL (must be green): pending until the PR is opened and GitHub Actions runs -- Workflow run duration: pending -- Curl response excerpt: pending +- Run URL (must be green): https://github.com/Troshkins/DevSecOps-Intro/actions/runs/27350872180 +- Workflow run duration: 21s +- Curl response excerpt: +Waiting for Juice Shop... attempt 1/30 +{"version":"20.0.0"} +Juice Shop version endpoint is healthy +0s +Homepage returned HTTP 200 From 4f69d126c2c73ca59ead32235198ec1a531ea596 Mon Sep 17 00:00:00 2001 From: Troshkins Date: Fri, 12 Jun 2026 13:15:13 +0300 Subject: [PATCH 3/5] feat(lab2): Threagile threat model secure variant and auth flow --- labs/lab2/threagile-model-auth.yaml | 398 ++++++++++++++++++++++++ labs/lab2/threagile-model-secure.yaml | 429 ++++++++++++++++++++++++++ submissions/lab2.md | 86 ++++++ 3 files changed, 913 insertions(+) create mode 100644 labs/lab2/threagile-model-auth.yaml create mode 100644 labs/lab2/threagile-model-secure.yaml create mode 100644 submissions/lab2.md diff --git a/labs/lab2/threagile-model-auth.yaml b/labs/lab2/threagile-model-auth.yaml new file mode 100644 index 000000000..ea80a9941 --- /dev/null +++ b/labs/lab2/threagile-model-auth.yaml @@ -0,0 +1,398 @@ +threagile_version: 1.0.0 + +title: Juice Shop Auth Flow Threat Model +date: 2025-09-18 + +author: + name: Pavel Troshkin + homepage: https://github.com/Troshkins + +management_summary_comment: > + Focused threat model for Juice Shop authentication: login, JWT issuing, + JWT verification, session handling, and admin endpoint access. + +business_criticality: important + +business_overview: + description: > + This model covers only the authentication flow, not the whole Juice Shop architecture. + images: [] + +technical_overview: + description: > + The browser sends credentials to the Auth API. The Auth API validates credentials + against the user store and requests a JWT from the token service. Later the browser + sends the JWT to an admin endpoint, which verifies the token and admin role. + images: [] + +questions: + MFA enabled?: "" + JWT keys protected?: "" + Admin role checked?: "" + Login rate limited?: "" + +abuse_cases: + Token theft: > + An attacker steals a JWT and reuses it to access protected endpoints. + JWT forgery: > + An attacker forges or modifies a JWT to impersonate an admin. + Admin bypass: > + A normal user tries to access admin endpoints without the admin role. + +security_requirements: + Server-side role checks: Admin access must be checked on the backend. + JWT key protection: JWT signing keys must be stored securely. + MFA for admins: Admin users should use a second authentication factor. + Rate limiting: Login endpoints should be rate-limited. + +tags_available: + - auth + - jwt + - admin + - secret + - browser + - api + - datastore + +data_assets: + + Credentials: + id: credentials + description: "Username and password used for login." + usage: business + tags: ["auth"] + origin: user-supplied + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: "Credentials are sensitive and can lead to account takeover." + + JWT Token: + id: jwt-token + description: "JWT returned after login and used in Authorization headers." + usage: business + tags: ["auth", "jwt"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: "JWTs prove identity and must not be stolen or modified." + + Session State: + id: session-state + description: "Authentication/session state after successful login." + usage: business + tags: ["auth"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: important + availability: important + justification_cia_rating: "Session state controls authenticated access." + + Admin Request: + id: admin-request + description: "Privileged request sent to admin-only endpoints." + usage: business + tags: ["admin"] + origin: user-supplied + owner: Lab Owner + quantity: many + confidentiality: internal + integrity: critical + availability: important + justification_cia_rating: "Admin requests can modify sensitive application state." + + JWT Signing Key: + id: jwt-key + description: "Secret key used to sign and verify JWTs." + usage: business + tags: ["secret", "jwt"] + origin: application + owner: Lab Owner + quantity: few + confidentiality: strictly-confidential + integrity: critical + availability: important + justification_cia_rating: "If the key leaks, attackers can forge valid JWTs." + +technical_assets: + + Browser: + id: browser + description: "User browser." + type: external-entity + usage: business + used_as_client_by_human: true + out_of_scope: false + justification_out_of_scope: + size: system + technology: browser + tags: ["browser"] + internet: true + machine: virtual + encryption: none + owner: External User + confidentiality: public + integrity: operational + availability: operational + justification_cia_rating: "The browser is outside our trust boundary." + multi_tenant: false + redundant: false + custom_developed_parts: false + data_assets_processed: + - credentials + - jwt-token + - session-state + - admin-request + data_assets_stored: + - jwt-token + - session-state + data_formats_accepted: + - json + communication_links: + Login to Auth API: + target: auth-api + description: "Browser sends login credentials to the Auth API." + protocol: https + authentication: none + authorization: none + tags: ["auth"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - credentials + data_assets_received: + - jwt-token + - session-state + + Admin call with JWT: + target: admin-api + description: "Browser calls an admin endpoint with a JWT." + protocol: https + authentication: token + authorization: enduser-identity-propagation + tags: ["admin", "jwt"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - jwt-token + - admin-request + data_assets_received: + - admin-request + + Auth API: + id: auth-api + description: "Login/register API." + type: process + usage: business + used_as_client_by_human: false + out_of_scope: false + justification_out_of_scope: + size: application + technology: web-service-rest + tags: ["api", "auth"] + internet: false + machine: container + encryption: transparent + owner: Lab Owner + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: "The Auth API processes credentials and creates sessions." + multi_tenant: false + redundant: false + custom_developed_parts: true + data_assets_processed: + - credentials + - jwt-token + - session-state + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: + Check credentials: + target: user-store + description: "Auth API checks credentials in the user credential store." + protocol: jdbc-encrypted + authentication: credentials + authorization: technical-user + tags: ["auth"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - credentials + data_assets_received: + - credentials + + Issue JWT: + target: token-service + description: "Auth API requests a signed JWT." + protocol: https + authentication: token + authorization: technical-user + tags: ["jwt"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - session-state + data_assets_received: + - jwt-token + + Token Service: + id: token-service + description: "Component that signs and verifies JWTs." + type: process + usage: business + used_as_client_by_human: false + out_of_scope: false + justification_out_of_scope: + size: component + technology: web-service-rest + tags: ["jwt", "secret"] + internet: false + machine: container + encryption: transparent + owner: Lab Owner + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: "This component controls JWT integrity." + multi_tenant: false + redundant: false + custom_developed_parts: true + data_assets_processed: + - jwt-token + - jwt-key + data_assets_stored: + - jwt-key + data_formats_accepted: + - json + communication_links: {} + + User Store: + id: user-store + description: "Database with user credentials and roles." + type: datastore + usage: business + used_as_client_by_human: false + out_of_scope: false + justification_out_of_scope: + size: component + technology: database + tags: ["datastore", "auth"] + internet: false + machine: container + encryption: data-with-symmetric-shared-key + owner: Lab Owner + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: "The store contains credentials and authorization data." + multi_tenant: false + redundant: false + custom_developed_parts: false + data_assets_processed: + - credentials + data_assets_stored: + - credentials + data_formats_accepted: + - json + communication_links: {} + + Admin API: + id: admin-api + description: "Admin-only endpoint." + type: process + usage: business + used_as_client_by_human: false + out_of_scope: false + justification_out_of_scope: + size: component + technology: web-service-rest + tags: ["api", "admin"] + internet: false + machine: container + encryption: transparent + owner: Lab Owner + confidentiality: internal + integrity: critical + availability: important + justification_cia_rating: "Unauthorized access gives elevated privileges." + multi_tenant: false + redundant: false + custom_developed_parts: true + data_assets_processed: + - jwt-token + - admin-request + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: + Verify JWT: + target: token-service + description: "Admin API verifies JWT signature and admin role." + protocol: https + authentication: token + authorization: technical-user + tags: ["jwt", "admin"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - jwt-token + data_assets_received: + - jwt-token + +trust_boundaries: + + Internet: + id: internet + description: "Untrusted user network." + type: network-dedicated-hoster + tags: [] + technical_assets_inside: + - browser + trust_boundaries_nested: + - container-network + + Container Network: + id: container-network + description: "Internal container network." + type: network-dedicated-hoster + tags: [] + technical_assets_inside: + - auth-api + - token-service + - user-store + - admin-api + trust_boundaries_nested: [] + +shared_runtimes: + + Auth Runtime: + id: auth-runtime + description: "Container runtime for auth components." + tags: [] + technical_assets_running: + - auth-api + - token-service + - admin-api + +individual_risk_categories: {} + +risk_tracking: {} diff --git a/labs/lab2/threagile-model-secure.yaml b/labs/lab2/threagile-model-secure.yaml new file mode 100644 index 000000000..9e1fcbd35 --- /dev/null +++ b/labs/lab2/threagile-model-secure.yaml @@ -0,0 +1,429 @@ +threagile_version: 1.0.0 + +title: OWASP Juice Shop — Local Lab Threat Model (Secure Variant) +date: 2025-09-18 + +author: + name: Student Name + homepage: https://example.edu + +management_summary_comment: > + Threat model for a local OWASP Juice Shop setup. Users access the app + either directly via HTTP on port 3000 or through an optional reverse proxy that + terminates TLS and adds security headers. The app runs in a container + and writes data to a host-mounted volume (for database, uploads, logs). + Optional outbound notifications (e.g., a challenge-solution WebHook) can be configured for integrations. + +business_criticality: important # archive, operational, important, critical, mission-critical + +business_overview: + description: > + Training environment for DevSecOps. This model covers a deliberately vulnerable + web application (OWASP Juice Shop) running locally in a Docker container. The focus is on a minimal architecture, STRIDE threat analysis, and actionable mitigations for the identified risks. + + images: + # - dfd.png: Data Flow Diagram (if exported from the tool) + +technical_overview: + description: > + A user’s web browser connects to the Juice Shop application (Node.js/Express server) either directly on **localhost:3000** (HTTP) or via a **reverse proxy** on ports 80/443 (with HTTPS). The Juice Shop server may issue outbound requests to external services (e.g., a configured **WebHook** for solved challenge notifications). All application data (the SQLite database, file uploads, logs) is stored on the host’s filesystem via a mounted volume. Key trust boundaries include the **Internet** (user & external services) → **Host** (local machine/VM) → **Container Network** (isolated app container). + images: [] + +questions: + Do you expose port 3000 beyond localhost?: "" + Do you use a reverse proxy with TLS and security headers?: "" + Are any outbound integrations (webhooks) configured?: "" + Is any sensitive data stored in logs or files?: "" + +abuse_cases: + Credential Stuffing / Brute Force: > + Attackers attempt repeated login attempts to guess credentials or exhaust system resources. + Stored XSS via Product Reviews: > + Malicious scripts are inserted into product reviews, getting stored and executed in other users’ browsers. + SSRF via Outbound Requests: > + Server-side requests (e.g. profile image URL fetch or WebHook callback) are abused to access internal network resources. + +security_requirements: + TLS in transit: Enforce HTTPS for user traffic via a TLS-terminating reverse proxy with strong ciphers and certificate management. + AuthZ on sensitive routes: Implement strict server-side authorization checks (role/permission) on admin or sensitive functionalities. + Rate limiting & lockouts: Apply rate limiting and account lockout policies to mitigate brute-force and automated attacks on authentication and expensive operations. + Secure headers: Add security headers (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, etc.) at the proxy or app to mitigate client-side attacks. + Secrets management: Protect secret keys and credentials (JWT signing keys, OAuth client secrets) – keep them out of code repos and avoid logging them. + +tags_available: + # Relevant technologies and environment tags + - docker + - nodejs + # Data and asset tags + - pii + - auth + - tokens + - logs + - public + - actor + - user + - optional + - proxy + - app + - storage + - volume + - saas + - webhook + # Communication tags + - primary + - direct + - egress + +# ========================= +# DATA ASSETS +# ========================= +data_assets: + + User Accounts: + id: user-accounts + description: "User profile data, credential hashes, emails." + usage: business + tags: ["pii", "auth"] + origin: user-supplied + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: > + Contains personal identifiers and authentication data. High confidentiality is required to protect user privacy, and integrity is critical to prevent account takeovers. + + Orders: + id: orders + description: "Order history, addresses, and payment metadata (no raw card numbers)." + usage: business + tags: ["pii"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: important + availability: important + justification_cia_rating: > + Contains users’ personal data and business transaction records. Integrity and confidentiality are important to prevent fraud or privacy breaches. + + Product Catalog: + id: product-catalog + description: "Product information (names, descriptions, prices) available to all users." + usage: business + tags: ["public"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: public + integrity: important + availability: important + justification_cia_rating: > + Product data is intended to be public, but its integrity is important (to avoid defacement or price manipulation that could mislead users). + + Tokens & Sessions: + id: tokens-sessions + description: "Session identifiers, JWTs for authenticated sessions, CSRF tokens." + usage: business + tags: ["auth", "tokens"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: important + availability: important + justification_cia_rating: > + If session tokens are compromised, attackers can hijack user sessions. They must be kept confidential and intact; availability is less critical (tokens can be reissued). + + Logs: + id: logs + description: "Application and access logs (may inadvertently contain PII or secrets)." + usage: devops + tags: ["logs"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: internal + integrity: important + availability: important + justification_cia_rating: > + Logs are for internal use (troubleshooting, monitoring). They should not be exposed publicly, and sensitive data should be sanitized to protect confidentiality. + +# ========================= +# TECHNICAL ASSETS +# ========================= +technical_assets: + + User Browser: + id: user-browser + description: "End-user web browser (client)." + type: external-entity + usage: business + used_as_client_by_human: true + out_of_scope: false + justification_out_of_scope: + size: system + technology: browser + tags: ["actor", "user"] + internet: true + machine: virtual + encryption: none + owner: External User + confidentiality: public + integrity: operational + availability: operational + justification_cia_rating: "Client controlled by end user (potentially an attacker)." + multi_tenant: false + redundant: false + custom_developed_parts: false + data_assets_processed: [] + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: + To Reverse Proxy (preferred): + target: reverse-proxy + description: "User browser to reverse proxy (HTTPS on 443)." + protocol: https + authentication: session-id + authorization: enduser-identity-propagation + tags: ["primary"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - tokens-sessions + data_assets_received: + - product-catalog + Direct to App (no proxy): + target: juice-shop + description: "Direct browser access to app via HTTPS; direct plain HTTP is disabled in the secure variant." + protocol: https + authentication: session-id + authorization: enduser-identity-propagation + tags: ["direct"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - tokens-sessions + data_assets_received: + - product-catalog + + Reverse Proxy: + id: reverse-proxy + description: "Optional reverse proxy (e.g., Nginx) for TLS termination and adding security headers." + type: process + usage: business + used_as_client_by_human: false + out_of_scope: false + justification_out_of_scope: + size: application + technology: reverse-proxy + tags: ["optional", "proxy"] + internet: false + machine: virtual + encryption: transparent + owner: Lab Owner + confidentiality: internal + integrity: important + availability: important + justification_cia_rating: "Not exposed to internet directly; improves security of inbound traffic." + multi_tenant: false + redundant: false + custom_developed_parts: false + data_assets_processed: + - product-catalog + - tokens-sessions + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: + To App: + target: juice-shop + description: "Proxy forwarding to app over HTTPS internally; end-user session identity is propagated to the application." + protocol: https + authentication: session-id + authorization: enduser-identity-propagation + tags: [] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - tokens-sessions + data_assets_received: + - product-catalog + + Juice Shop Application: + id: juice-shop + description: "OWASP Juice Shop server (Node.js/Express, v19.0.0). Secure variant: database access uses prepared statements / parameterized queries for all SQL operations." + type: process + usage: business + used_as_client_by_human: false + out_of_scope: false + justification_out_of_scope: + size: application + technology: web-server + tags: ["app", "nodejs"] + internet: false + machine: container + encryption: transparent + owner: Lab Owner + confidentiality: internal + integrity: important + availability: important + justification_cia_rating: "In-scope web application. In the secure variant, runtime storage is encrypted and SQL access is documented as prepared/parameterized." + multi_tenant: false + redundant: false + custom_developed_parts: true + data_assets_processed: + - user-accounts + - orders + - product-catalog + - tokens-sessions + data_assets_stored: + - logs + data_formats_accepted: + - json + communication_links: + To Challenge WebHook: + target: webhook-endpoint + description: "Optional outbound callback to external WebHook when a challenge is solved; HTTPS/TLS is enforced." + protocol: https + authentication: none + authorization: none + tags: ["egress"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - orders + + Persistent Storage: + id: persistent-storage + description: "Encrypted host-mounted volume for database, file uploads, and sanitized logs. Database access uses prepared statements / parameterized queries; plain log writes containing secrets are disabled." + type: datastore + usage: devops + used_as_client_by_human: false + out_of_scope: false + justification_out_of_scope: + size: component + technology: file-server + tags: ["storage", "volume"] + internet: false + machine: virtual + encryption: data-with-symmetric-shared-key + owner: Lab Owner + confidentiality: internal + integrity: important + availability: important + justification_cia_rating: "Local disk storage for the container; encrypted at rest because it contains database files and logs." + multi_tenant: false + redundant: false + custom_developed_parts: false + data_assets_processed: [] + data_assets_stored: + - logs + - user-accounts + - orders + - product-catalog + data_formats_accepted: + - file + communication_links: {} + + Webhook Endpoint: + id: webhook-endpoint + description: "External WebHook service (3rd-party, if configured for integrations)." + type: external-entity + usage: business + used_as_client_by_human: false + out_of_scope: true + justification_out_of_scope: "Third-party service to receive notifications (not under our control)." + size: system + technology: web-service-rest + tags: ["saas", "webhook"] + internet: true + machine: virtual + encryption: none + owner: Third-Party + confidentiality: internal + integrity: operational + availability: operational + justification_cia_rating: "External service that receives data (like order or challenge info). Treated as a trusted integration point but could be abused if misconfigured." + multi_tenant: true + redundant: true + custom_developed_parts: false + data_assets_processed: + - orders + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: {} + +# ========================= +# TRUST BOUNDARIES +# ========================= +trust_boundaries: + + Internet: + id: internet + description: "Untrusted public network (Internet)." + type: network-dedicated-hoster + tags: [] + technical_assets_inside: + - user-browser + - webhook-endpoint + trust_boundaries_nested: + - host + + Host: + id: host + description: "Local host machine / VM running the Docker environment." + type: network-dedicated-hoster + tags: [] + technical_assets_inside: + - reverse-proxy + - persistent-storage + trust_boundaries_nested: + - container-network + + Container Network: + id: container-network + description: "Docker container network (isolated internal network for containers)." + type: network-dedicated-hoster + tags: [] + technical_assets_inside: + - juice-shop + trust_boundaries_nested: [] + +# ========================= +# SHARED RUNTIMES +# ========================= +shared_runtimes: + + Docker Host: + id: docker-host + description: "Docker Engine and default bridge network on the host." + tags: ["docker"] + technical_assets_running: + - juice-shop + # If the reverse proxy is containerized, include it: + # - reverse-proxy + +# ========================= +# INDIVIDUAL RISK CATEGORIES (optional) +# ========================= +individual_risk_categories: {} + +# ========================= +# RISK TRACKING (optional) +# ========================= +risk_tracking: {} + +# (Optional diagram layout tweaks can be added here) +#diagram_tweak_edge_layout: spline +#diagram_tweak_layout_left_to_right: true diff --git a/submissions/lab2.md b/submissions/lab2.md new file mode 100644 index 000000000..46a223617 --- /dev/null +++ b/submissions/lab2.md @@ -0,0 +1,86 @@ +## Task 1: Baseline Threat Model + +### Risk count by severity +| Severity | Count | +|----------|------:| +| Critical | 0 | +| High | 0 | +| Elevated | 4 | +| Medium | 5 | +| Low | 14 | +| Total | 23 | + +### Top 5 risks (paste from `jq` output) +1. missing-authentication — Missing Authentication covering communication link To App from Reverse Proxy to Juice Shop Application; severity elevated; affecting juice-shop +2. unencrypted-communication — Unencrypted Communication named Direct to App (no proxy) between User Browser and Juice Shop Application transferring authentication data such as credentials, tokens, or session IDs; severity elevated; affecting user-browser +3. unencrypted-communication — Unencrypted Communication named To App between Reverse Proxy and Juice Shop Application; severity elevated; affecting reverse-proxy +4. cross-site-scripting — Cross-Site Scripting risk at Juice Shop Application; severity elevated; affecting juice-shop +5. unencrypted-asset — Unencrypted Technical Asset named Juice Shop Application; severity medium; affecting juice-shop + +### STRIDE mapping (Lecture 2 slide 7) +For each top-5 risk, name the STRIDE letter(s) it primarily violates: +- Risk 1: **** — +- Risk 2: ... + +### Trust boundary observation +The User Browser → Juice Shop Application flow, named Direct to App, crosses a trust boundary from the Internet/user-controlled zone into the application/container zone. + +This flow is particularly attractive to an attacker because it is directly reachable from outside the system and transfers authentication-sensitive data. Since it bypasses the reverse proxy, the attacker may also bypass TLS termination, security headers, and other proxy-level protections. + +## Task 2: Secure Variant & Diff + +### Risk count comparison +| Severity | Baseline | Secure | Δ | +|----------|---------:|-------:|--:| +| Critical | 0 | 0 | 0 | +| High | 0 | 0 | 0 | +| Elevated | 4 | 1 | 3 | +| Medium | 5 | 5 | 0 | +| Low | 14 | 12 | 2 | +| Total | 23 | 18 | 5 | + +### Which rules are GONE in the secure variant? + +1. `missing-authentication` — fixed by declaring authentication on the internal **Reverse Proxy → Juice Shop Application** communication link. +2. `unencrypted-asset` — fixed by changing the relevant technical asset/storage encryption from `none` to an encrypted/transparent storage option in the secure variant. +3. `unencrypted-communication` — fixed by changing insecure communication links from `http` to `https`, especially for the **Direct to App (no proxy)** and internal app traffic flows. + +### Which rules are STILL THERE in the secure variant? + +1. `cross-site-scripting` — This risk still fires because HTTPS and encrypted storage do not eliminate application-level XSS issues. Removing this risk would require code-level and browser-side mitigations, such as output encoding, input validation, safe template rendering, and a strict Content Security Policy. + +2. `missing-waf` — This risk still exists because the secure variant improves transport encryption, storage encryption, and authentication declarations, but it does not add a Web Application Firewall in front of Juice Shop. Eliminating it would require adding and modeling a WAF layer, for example ModSecurity/Coraza or a managed WAF, with rules for common web attacks. + +### Honesty check + +No, the total risk count did not drop more than 50%. The baseline model had **23** risks, while the secure variant has **18** risks, so the drop is **5** risks, or **21.74%**. + +This shows that the selected hardening changes are still useful: a small number of architectural changes removed several important risks related to missing authentication, unencrypted assets, and unencrypted communication. However, the remaining findings show that threat modeling does not reach zero risk through simple configuration changes alone. The rest would require deeper controls such as WAF integration, application hardening, CSRF/XSS protections, identity-store improvements, vault/secret management, and build infrastructure security. + +## Bonus Task: Auth Flow Threat Model + +### Risk count + +| Severity | Count | +| --------- | ----: | +| Critical | 0 | +| High | 0 | +| Elevated | 5 | +| Medium | 13 | +| Low | 5 | +| Total | 23 | + +### Three auth-specific risks (NOT in the baseline model's top 5) + +1. **missing-authentication-second-factor** — STRIDE: **S / E** — Mitigation: Require MFA for admin users and other privileged operations. This makes stolen credentials or a stolen JWT less useful for impersonation and privilege escalation. + +2. **missing-vault** — STRIDE: **I / S / E** — Mitigation: Store JWT signing keys and other authentication secrets in a dedicated vault or secret-management system. Access to the signing key should be restricted, audited, and rotated so that compromise of one application container does not immediately allow token forgery. + +3. **sql-nosql-injection** — STRIDE: **T / E** — Mitigation: Use parameterized queries or ORM-safe methods when the Auth API checks credentials in the User Store. Login input must be validated and never concatenated into SQL/NoSQL queries, because injection in the authentication path can lead to account bypass or role manipulation. + +### Reflection + +Building the focused auth-flow model surfaced risks that the baseline architecture model did not show in its top findings. The baseline model mostly highlighted broad architecture issues such as unencrypted communication, missing authentication between components, XSS, and unencrypted assets. + +The auth-specific model made the login path, JWT issuance, JWT verification, credential store, signing key, and admin endpoint explicit. This made it easier to see spoofing and elevation-of-privilege risks around stolen tokens, missing MFA, weak secret storage, and injection in the credential-checking flow. + From b9d281f80474504ede2ab4a471930fd6cd1c4b57 Mon Sep 17 00:00:00 2001 From: Troshkins Date: Sat, 13 Jun 2026 23:30:22 +0300 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 9151da06d147dd90bf7e92d9cb1f36b362c5df4a Mon Sep 17 00:00:00 2001 From: Troshkins Date: Sun, 14 Jun 2026 12:57:17 +0300 Subject: [PATCH 5/5] docs(lab3): add submission screenshot --- .pre-commit-config.yaml | 10 +++ submissions/lab3-screen.png | Bin 0 -> 55738 bytes submissions/lab3.md | 140 +++++++++++++++++++++++++++++++++++- 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 .pre-commit-config.yaml create mode 100644 submissions/lab3-screen.png diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..d903900c1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.28.0 + hooks: + - id: gitleaks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files diff --git a/submissions/lab3-screen.png b/submissions/lab3-screen.png new file mode 100644 index 0000000000000000000000000000000000000000..20f6f66764ef134e5f1755190aa73e996917c68b GIT binary patch literal 55738 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 R;q#P(w1oVVFNo)F{~LyRhU5SM literal 0 HcmV?d00001 diff --git a/submissions/lab3.md b/submissions/lab3.md index 61fa24bee..16a8444b9 100644 --- a/submissions/lab3.md +++ b/submissions/lab3.md @@ -1 +1,139 @@ -lab3 signing test +# Lab 3 — Submission + +## Task 1: SSH Commit Signing + +### Local configuration +- `git config --global gpg.format` → ssh +- `git config --global user.signingkey` → /home/sato/.ssh/id_ed25519.pub +- `git config --global commit.gpgsign` → true + +### Local verification +Output of `git log --show-signature -1`: +commit b9d281f80474504ede2ab4a471930fd6cd1c4b57 (HEAD -> feature/lab3, origin/feature/lab3) +Good signature from my Git signing key +Author: Troshkins +Date: Sat Jun 13 23:30:22 2026 +0300 + + test: first signed commit + + +### GitHub verification +- Direct link to your most recent commit on GitHub: https://github.com/inno-devops-labs/DevSecOps-Intro/commit/b9d281f80474504ede2ab4a471930fd6cd1c4b57 +- Screenshot of the Verified badge: https://github.com/Troshkins/DevSecOps-Intro/tree/feature/lab3/submissions/lab3-screen.png + +### One-paragraph reflection (2-3 sentences) +A forged-author commit could allow an attacker or careless teammate to push malicious code while making it look like another developer wrote it. This creates a STRIDE-R / Repudiation problem because the real author can deny responsibility and the team cannot rely on the Git author field alone. A Verified badge makes this attack visible because it proves whether the commit was signed by a trusted key connected to the claimed GitHub account. + +## Task 2: Pre-commit + gitleaks + +### `.pre-commit-config.yaml` + +```yaml +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.28.0 + hooks: + - id: gitleaks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files +``` + +### `pre-commit install` output + +```text +pre-commit installed at .git/hooks/pre-commit +``` + +### `pre-commit run --all-files` output + +```text +Detect hardcoded secrets.................................................Passed +check for added large files..............................................Passed +``` + +Note: initially, `detect-private-key` was tested but removed because it failed on an existing file from another lab: `labs/lab6/vulnerable-iac/ansible/configure.yml`. Since this file is outside Lab 3 scope, I kept `check-added-large-files` as the required additional hook. + +### The blocked commit + +Output of the `git commit` that gitleaks blocked: + +```text +[WARNING] Unstaged files detected. +[INFO] Stashing unstaged files to /home/sato/.cache/pre-commit/patch1781385202-2797103. +Detect hardcoded secrets.................................................Failed +- hook id: gitleaks +- exit code: 1 + + +○ + │╲ + │ ○ + ○ ░ + ░ gitleaks + +Finding: GH_PAT=REDACTED +Secret: REDACTED +RuleID: github-pat +Entropy: 4.143943 +File: submissions/leak-attempt.txt +Line: 2 +Fingerprint: submissions/leak-attempt.txt:github-pat:2 + +12:13AM INF 0 commits scanned. +12:13AM INF scanned ~101 bytes (101 bytes) in 93.3ms +12:13AM WRN leaks found: 1 + +check for added large files..............................................Passed +[INFO] Restored changes from /home/sato/.cache/pre-commit/patch1781385202-2797103. +``` + +### Tune-out exercise + +1. **Inline allowlist** + +Inline allowlist via .gitleaks.toml is appropriate when you need to permit a specific known secret example, such as a demonstration string used in training or documentation. This is acceptable as long as the allowlist is highly specific: a particular rule ID, a specific fake value, or another clearly safe example. The approach becomes risky if the allowlist is too broad, because real secrets with a similar format may bypass detection. + +2. **Path exclusion** + +Path exclusion, for example excluding the docs/ directory, is appropriate only when the directory truly contains documentation and regularly uses fake secrets as examples. This is riskier because gitleaks stops scanning the entire path. If someone accidentally commits a real token into docs/, the scanner will no longer detect it. + +## Bonus: History Rewrite + +### Before + +```text +d0489be (HEAD -> master) docs: add usage notes +f38439a feat: empty log +b7bb2ec feat: add config +b0c1134 init +``` + +Output of `git log -p | grep -c 'ghp_'`: **2** + +### After + +```text +b5075be (HEAD -> master) docs: add usage notes +889f164 feat: empty log +69d9a2f feat: add config +edfc8e8 init +``` + +Output of `git log -p | grep -c 'ghp_'`: **0** + +Output of `git log -p | grep -c 'REDACTED'`: **2** + +### The two-step pattern in real life + +1. `git filter-repo --replace-text replacements.txt` — rewrite locally. +2. Rotate the leaked secret. Rewriting history only removes the exposed value from Git history, but it does not make the leaked credential safe again. In a real incident, the old key must be revoked and replaced, because it may already have been copied from the repository. + +### Two real-world gotchas I discovered + +1. `git filter-repo` does not just edit files; it rewrites Git history. After the rewrite, all commit hashes changed, so this operation would require coordination in a real shared repository. + +2. The bonus repo must stay outside the course fork. The sandbox contains a deliberately planted fake secret, so committing `/tmp/lab3-bonus` or its files into the course repository would defeat the purpose of the exercise. +