From 627ea9fadd9e07b0dde1ecd76df5a45b9394f25f Mon Sep 17 00:00:00 2001 From: AskoRBINKAs Date: Wed, 10 Jun 2026 00:43:53 +0300 Subject: [PATCH 1/8] tasks 1,2,3 done --- .github/PULL_REQUEST_TEMPLATE.md | 32 +++++++ .github/workflows/lab1-smoke.yaml | 40 ++++++++ submissions/lab1.md | 150 ++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/lab1-smoke.yaml 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..d16d77f4b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,32 @@ +## Goal + + +## Changes + + +- + +## Testing + + +```bash +# paste commands here +``` + +**Observed output:** + +``` +# paste output here +``` + +## Artifacts & Screenshots + + +- + +--- + +## Checklist +- [ ] Title is clear (`feat(labN): ` style) +- [ ] No secrets/large temp files committed +- [ ] Submission file at `submissions/labN.md` exists \ No newline at end of file diff --git a/.github/workflows/lab1-smoke.yaml b/.github/workflows/lab1-smoke.yaml new file mode 100644 index 000000000..43d97f092 --- /dev/null +++ b/.github/workflows/lab1-smoke.yaml @@ -0,0 +1,40 @@ +# .github/workflows/lab1-smoke.yml +name: Smoke Test Juice Shop +on: + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + smoke-test: + runs-on: ubuntu-latest + services: + juice-shop: + image: bkimminich/juice-shop:v20.0.0 + ports: + - 3000:3000 + options: >- + --health-cmd="curl --silent --fail http://localhost:3000/rest/admin/application-version || exit 1" + --health-interval=5s + --health-timeout=3s + --health-retries=10 + steps: + - name: Wait for Juice Shop to be ready + run: | + for i in $(seq 1 30); do + if curl --silent --fail http://localhost:3000/rest/admin/application-version >/dev/null; then + echo "Juice Shop is ready!" + exit 0 + fi + echo "Waiting for Juice Shop... (attempt $i/30)" + sleep 2 + done + echo "Error: Juice Shop failed to start within 60 seconds" + exit 1 + + - name: Verify homepage returns 200 + run: | + curl --silent --fail http://localhost:3000/rest/admin/application-version + echo "smoke test passed" \ No newline at end of file diff --git a/submissions/lab1.md b/submissions/lab1.md new file mode 100644 index 000000000..425bdf9cf --- /dev/null +++ b/submissions/lab1.md @@ -0,0 +1,150 @@ +# 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: Windows 11 +- Docker version: 28.1.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 (explain if No) +- Container restart policy: no + +### Health Check +- HTTP code on `/`: 200 +- API check (first 200 chars of `/rest/products`): +```html + + + + Error: Unexpected path: /rest/products + + + +
+

OWASP Juice Shop (Express ^4.22.1)

+

500 Error: Unexpected path: /rest/products

+
  •    at /juice-shop/build/routes/angular.js:42:18
  •    at /juice-shop/build/lib/utils.js:225:26
  •    at Layer.handle [as handle +
+ + +``` + +Container uptime: +```shell +NAMES STATUS PORTS +juice-shop Up 8 minutes 127.0.0.1:3000->3000/tcp +``` + +### Initial Surface Snapshot (from browser exploration) +- Login/Registration visible: [x] Yes [ ] No — notes: login/registration page visible, it also has sign-in with google. +- Product listing/search present: [X] Yes [ ] No — notes: it has paginated product listing with keyword search. +- Admin or account area discoverable: [X] Yes [ ] No — notes: by guessing and manual fuzzing `/#/administration` endpoint was discovered, but it returns 403. +- Client-side errors in DevTools console: [ ] Yes [X] No — notes: no errors was discovered. +- Pre-populated local storage / cookies: local storage is empty. In cookies was found `language` cookie with value `en`. `cookieconsent_status` appeared after accepting in UI. All cookies has `HttpOnly=false`. + +### Security Headers (Quick Look) +Headers: +``` +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: Tue, 09 Jun 2026 21:07:56 GMT +ETag: W/"26af-19eae36a199" +Content-Type: text/html; charset=UTF-8 +Content-Length: 9903 +Vary: Accept-Encoding +Date: Tue, 09 Jun 2026 21:22:51 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 + +1. Missing CSP Header: Without Content Security Policy (CSP), your site is vulnerable to XSS attacks because the browser will execute any malicious script injected into your pages. This allows attackers to steal user data, session cookies, or perform unauthorized actions on behalf of the user. +2. Insecure CORS settings: Using `Access-Control-Allow-Origin: *` is insecure because it allows any website to read your API's response, potentially exposing sensitive user data. It also disables credentials like cookies or authorization headers, which are often necessary for authenticated requests. This wildcard approach breaks security boundaries, making your application vulnerable to cross-site request forgery and data leaks. +3. Stacktrace exposing: Exposing stack traces reveals internal implementation details like file paths, function names, and library versions, giving attackers valuable information to exploit specific vulnerabilities. It also leaks sensitive logic about your application's architecture, making it easier for hackers to craft targeted attacks such as SQL injection or path traversal based on the revealed structure. + +## PR Template Setup + +- File: `.github/PULL_REQUEST_TEMPLATE.md` +- Sections included: Goal / Changes / Testing / Artifacts & Screenshots +- Checklist items: +- Auto-fill verified: [ ] Yes — PR description showed my template (screenshot or link to draft PR) + +## GitHub Community + +### Actions completed +- [x] Starred [inno-devops-labs/DevSecOps-Intro](https://github.com/inno-devops-labs/DevSecOps-Intro) +- [x] Starred [simple-container-com/api](https://github.com/simple-container-com/api) +- Following professor and TAs on GitHub: + - [x] [@Cre-eD](https://github.com/Cre-eD) (professor) + - [x] [@Naghme98](https://github.com/Naghme98) (TA) + - [x] [@pierrepicaud](https://github.com/pierrepicaud) (TA) +- Following 3 classmates: + - [x] [@RC-5555](https://github.com/RC-5555) + - [x] [@Jestersw](https://github.com/jestersw) + - [x] [@0xsmk](https://github.com/0xsmk) + +### Why stars matter + +Starring repositories helps open source projects gain visibility, attract contributors, and shows maintainers that their work is valued. Following developers keeps you updated on their activity, fosters collaboration in team projects, and exposes you to best practices and new techniques that accelerate your professional growth. + +## Bonus: CI Smoke Test + +- Workflow file: `.github/workflows/lab1-smoke.yml` +- Trigger: `pull_request` on main +- Run URL (must be green): +- Workflow run duration: +- Curl response excerpt: \ No newline at end of file From 1f2b71b631df3b5634c827318eb4183435b3c87a Mon Sep 17 00:00:00 2001 From: AskoRBINKAs Date: Wed, 10 Jun 2026 00:49:23 +0300 Subject: [PATCH 2/8] fixed smoke workflow --- .github/workflows/lab1-smoke.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/lab1-smoke.yaml b/.github/workflows/lab1-smoke.yaml index 43d97f092..5cb31e186 100644 --- a/.github/workflows/lab1-smoke.yaml +++ b/.github/workflows/lab1-smoke.yaml @@ -15,11 +15,6 @@ jobs: image: bkimminich/juice-shop:v20.0.0 ports: - 3000:3000 - options: >- - --health-cmd="curl --silent --fail http://localhost:3000/rest/admin/application-version || exit 1" - --health-interval=5s - --health-timeout=3s - --health-retries=10 steps: - name: Wait for Juice Shop to be ready run: | From 37ac905d0ed3dae689696cf430a9760049f470da Mon Sep 17 00:00:00 2001 From: AskoRBINKAs Date: Wed, 10 Jun 2026 00:50:42 +0300 Subject: [PATCH 3/8] change curl params in workflow --- .github/workflows/lab1-smoke.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lab1-smoke.yaml b/.github/workflows/lab1-smoke.yaml index 5cb31e186..30ca375bc 100644 --- a/.github/workflows/lab1-smoke.yaml +++ b/.github/workflows/lab1-smoke.yaml @@ -31,5 +31,5 @@ jobs: - name: Verify homepage returns 200 run: | - curl --silent --fail http://localhost:3000/rest/admin/application-version + curl -v http://localhost:3000/rest/admin/application-version echo "smoke test passed" \ No newline at end of file From 43ebcb926eb6de6c78297d53cc9696d02201225b Mon Sep 17 00:00:00 2001 From: AskoRBINKAs Date: Wed, 10 Jun 2026 00:58:52 +0300 Subject: [PATCH 4/8] completed bonus task --- submissions/lab1.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/submissions/lab1.md b/submissions/lab1.md index 425bdf9cf..32b2e2425 100644 --- a/submissions/lab1.md +++ b/submissions/lab1.md @@ -145,6 +145,21 @@ Starring repositories helps open source projects gain visibility, attract contri - Workflow file: `.github/workflows/lab1-smoke.yml` - Trigger: `pull_request` on main -- Run URL (must be green): -- Workflow run duration: -- Curl response excerpt: \ No newline at end of file +- Run URL (must be green): [](https://github.com/AskoRBINKAs/DevSecOps-Intro/actions/runs/27238144616/job/80435256342) +- Workflow run duration: 16s +- 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' +< X-Recruiting: /#/jobs +< Content-Type: application/json; charset=utf-8 +< Content-Length: 20 +< ETag: W/"14-+EBpZnfu193JzIOBjXsY1+KveN8" +< Vary: Accept-Encoding +< Date: Tue, 09 Jun 2026 21:51:05 GMT +< Connection: keep-alive +< Keep-Alive: timeout=5 +``` \ No newline at end of file From d58a54a9891894cf87c0abc0b7da2468ce81f9de Mon Sep 17 00:00:00 2001 From: AskoRBINKAs Date: Wed, 10 Jun 2026 00:59:37 +0300 Subject: [PATCH 5/8] 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 32b2e2425..0b2ff35ce 100644 --- a/submissions/lab1.md +++ b/submissions/lab1.md @@ -139,7 +139,7 @@ Which of these are MISSING? (cross-reference Lecture 1 OWASP Top 10:2025 — A06 ### Why stars matter -Starring repositories helps open source projects gain visibility, attract contributors, and shows maintainers that their work is valued. Following developers keeps you updated on their activity, fosters collaboration in team projects, and exposes you to best practices and new techniques that accelerate your professional growth. +Starring repositories helps open source projects gain visibility, attract contributors, and shows maintainers that their work is valued. Following developers keeps you updated on their activity, fosters collaboration in team projects, and exposes you to best practices and new techniques that accelerate your professional growth. ## Bonus: CI Smoke Test From 7f398276576d9171c12897b9ca09c0a0167b8421 Mon Sep 17 00:00:00 2001 From: AskoRBINKAs Date: Thu, 11 Jun 2026 22:07:27 +0300 Subject: [PATCH 6/8] feat(lab2): Threagile threat model + secure variant + auth flow --- labs/lab2/threagile-model-auth.yaml | 475 ++++++++++++++++++++++++++ labs/lab2/threagile-model-secure.yaml | 424 +++++++++++++++++++++++ submissions/lab2.md | 102 ++++++ 3 files changed, 1001 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..518ae5f87 --- /dev/null +++ b/labs/lab2/threagile-model-auth.yaml @@ -0,0 +1,475 @@ +threagile_version: 1.0.0 + +title: OWASP Juice Shop — Authentication Flow Threat Model +date: 2025-09-18 + +author: + name: Student Name + homepage: https://example.edu + +management_summary_comment: > + Focused threat model for the Juice Shop login, JWT issuance, protected API, + and administrator authorization flow. + +business_criticality: critical + +business_overview: + description: > + Users authenticate with credentials, receive a signed JWT, and present it + when accessing protected user and administrator endpoints. + images: [] + +technical_overview: + description: > + The browser sends credentials to the Auth API over HTTPS. The Auth API + checks the credential store and requests a JWT from the Token Signer. + Protected and administrator endpoints verify every JWT with the signer; + the administrator endpoint additionally requires the admin role claim. + images: [] + +questions: + Are passwords hashed with a modern password hashing function?: "" + Are JWT signing keys rotated and stored in a vault?: "" + Is the admin role checked server-side on every request?: "" + Are login attempts rate limited and monitored?: "" + +abuse_cases: + Credential Stuffing: > + An attacker automates login attempts using leaked username and password pairs. + JWT Forgery: > + An attacker steals or guesses the signing key and creates a token containing an admin role. + Broken Role Authorization: > + A valid non-admin user manipulates token claims or calls an admin endpoint that fails to enforce the role. + +security_requirements: + Password protection: Store passwords using a modern salted password hash. + Login protection: Apply rate limiting, lockouts, and MFA for sensitive accounts. + JWT validation: Verify signature, issuer, audience, expiry, and algorithm on every protected request. + Signing key protection: Store signing keys in a vault and rotate them regularly. + Admin authorization: Enforce the admin role server-side on every administrator operation. + TLS: Encrypt all authentication and token traffic in transit. + +tags_available: + - auth + - jwt + - credentials + - admin + - browser + - api + - database + - signing + - protected + +data_assets: + + Credentials: + id: credentials + description: "Username and password supplied during login or registration." + usage: business + tags: ["auth", "credentials"] + origin: user-supplied + owner: Lab Owner + quantity: many + confidentiality: strictly-confidential + integrity: critical + availability: important + justification_cia_rating: > + Disclosure enables account takeover and modification can redirect authentication. + + JWT Token: + id: jwt-token + description: "Signed JWT containing user identity, role, issue time, and expiry." + usage: business + tags: ["auth", "jwt"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: > + Token integrity is critical because authorization decisions depend on its claims. + + Session State: + id: session-state + description: "Server-side authentication and token revocation state." + usage: business + tags: ["auth"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: > + Session corruption can permit hijacking or bypass revocation. + + Admin Operation Requests: + id: admin-operations + description: "Privileged requests that modify users, products, and application state." + usage: business + tags: ["admin"] + origin: user-supplied + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: critical + availability: critical + justification_cia_rating: > + Unauthorized or modified admin operations can compromise the entire application. + + JWT Signing Key: + id: jwt-signing-key + description: "Secret key used to sign and verify JWT tokens." + usage: business + tags: ["auth", "jwt", "signing"] + origin: application + owner: Lab Owner + quantity: very-few + confidentiality: strictly-confidential + integrity: critical + availability: critical + justification_cia_rating: > + Key disclosure or tampering permits forged administrator tokens. + +technical_assets: + + Browser: + id: browser + description: "Untrusted user-controlled 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 may be controlled by an attacker." + multi_tenant: false + redundant: false + custom_developed_parts: false + data_assets_processed: + - credentials + - jwt-token + - admin-operations + data_assets_stored: + - jwt-token + data_formats_accepted: + - json + communication_links: + Login and Register: + target: auth-api + description: "Submit credentials and receive a signed JWT over HTTPS." + protocol: https + authentication: credentials + authorization: enduser-identity-propagation + tags: ["auth"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - credentials + data_assets_received: + - jwt-token + - session-state + Access Protected API: + target: protected-api + description: "Call a protected endpoint with JWT in the Authorization header." + protocol: https + authentication: token + authorization: enduser-identity-propagation + tags: ["protected"] + vpn: false + ip_filtered: false + readonly: true + usage: business + data_assets_sent: + - jwt-token + data_assets_received: + - session-state + Access Admin Endpoint: + target: admin-endpoint + description: "Submit an admin operation with a JWT that must contain the admin role." + protocol: https + authentication: token + authorization: enduser-identity-propagation + tags: ["admin"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - jwt-token + - admin-operations + data_assets_received: + - session-state + + Auth API: + id: auth-api + description: "Login and registration API." + 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: ["auth", "api"] + internet: false + machine: container + encryption: transparent + owner: Lab Owner + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: "Authenticates users and initiates JWT issuance." + multi_tenant: true + redundant: false + custom_developed_parts: true + data_assets_processed: + - credentials + - jwt-token + - session-state + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: + Read Credential Store: + target: user-db + description: "Validate credentials using parameterized queries." + protocol: jdbc-encrypted + authentication: credentials + authorization: technical-user + tags: ["database"] + vpn: false + ip_filtered: true + readonly: false + usage: business + data_assets_sent: + - credentials + - session-state + data_assets_received: + - credentials + - session-state + Request JWT: + target: token-signer + description: "Request a signed JWT after successful authentication." + protocol: https + authentication: client-certificate + authorization: technical-user + tags: ["signing"] + vpn: false + ip_filtered: true + readonly: false + usage: business + data_assets_sent: + - session-state + data_assets_received: + - jwt-token + + Token Signer: + id: token-signer + description: "Issues JWTs and verifies signatures and registered claims." + type: process + usage: business + used_as_client_by_human: false + out_of_scope: false + justification_out_of_scope: + size: component + technology: identity-provider + tags: ["auth", "jwt", "signing"] + internet: false + machine: container + encryption: none + owner: Lab Owner + confidentiality: strictly-confidential + integrity: critical + availability: critical + justification_cia_rating: "Compromise permits token forgery and authentication bypass." + multi_tenant: true + redundant: false + custom_developed_parts: true + data_assets_processed: + - jwt-token + - session-state + - jwt-signing-key + data_assets_stored: + - jwt-signing-key + data_formats_accepted: + - json + communication_links: {} + + User DB Credential Store: + id: user-db + description: "Credential hashes, account roles, and session revocation state." + type: datastore + usage: business + used_as_client_by_human: false + out_of_scope: false + justification_out_of_scope: + size: component + technology: identity-store-database + tags: ["auth", "database"] + internet: false + machine: container + encryption: data-with-symmetric-shared-key + owner: Lab Owner + confidentiality: strictly-confidential + integrity: critical + availability: important + justification_cia_rating: "Contains credential hashes and authorization roles." + multi_tenant: true + redundant: false + custom_developed_parts: false + data_assets_processed: + - credentials + - session-state + data_assets_stored: + - credentials + - session-state + data_formats_accepted: + - json + communication_links: {} + + Protected API: + id: protected-api + description: "Representative authenticated user API 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", "protected"] + internet: false + machine: container + encryption: transparent + owner: Lab Owner + confidentiality: confidential + integrity: important + availability: important + justification_cia_rating: "Returns authenticated user data." + multi_tenant: true + redundant: false + custom_developed_parts: true + data_assets_processed: + - jwt-token + - session-state + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: + Verify User JWT: + target: token-signer + description: "Verify JWT signature, issuer, audience, algorithm, and expiry." + protocol: https + authentication: client-certificate + authorization: technical-user + tags: ["auth", "jwt"] + vpn: false + ip_filtered: true + readonly: true + usage: business + data_assets_sent: + - jwt-token + data_assets_received: + - session-state + + Admin Endpoint: + id: admin-endpoint + description: "Privileged API that requires a verified JWT with the admin role." + 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: confidential + integrity: critical + availability: critical + justification_cia_rating: "Executes privileged operations affecting the whole application." + multi_tenant: true + redundant: false + custom_developed_parts: true + data_assets_processed: + - jwt-token + - session-state + - admin-operations + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: + Verify Admin JWT: + target: token-signer + description: "Verify JWT signature and registered claims, then require the admin role." + protocol: https + authentication: client-certificate + authorization: technical-user + tags: ["auth", "jwt", "admin"] + vpn: false + ip_filtered: true + readonly: true + usage: business + data_assets_sent: + - jwt-token + - admin-operations + data_assets_received: + - session-state + +trust_boundaries: + + Internet: + id: internet + description: "Untrusted public network containing user-controlled clients." + type: network-dedicated-hoster + tags: [] + technical_assets_inside: + - browser + trust_boundaries_nested: + - auth-container + + Auth Container: + id: auth-container + description: "Application container network for authentication services." + type: network-dedicated-hoster + tags: [] + technical_assets_inside: + - auth-api + - token-signer + - user-db + - protected-api + - admin-endpoint + trust_boundaries_nested: [] + +shared_runtimes: + + Authentication Runtime: + id: auth-runtime + description: "Container runtime hosting the authentication components." + tags: ["auth"] + technical_assets_running: + - auth-api + - token-signer + - user-db + - protected-api + - admin-endpoint + +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..2920867f0 --- /dev/null +++ b/labs/lab2/threagile-model-secure.yaml @@ -0,0 +1,424 @@ +threagile_version: 1.0.0 + +title: OWASP Juice Shop — Secure Variant Threat Model +date: 2025-09-18 + +author: + name: Student Name + homepage: https://example.edu + +management_summary_comment: > + Hardened variant of the local OWASP Juice Shop model. All browser and proxy + traffic uses TLS, outbound callbacks use HTTPS, and the persistent database + and logs are encrypted at rest. Database access uses an encrypted channel, + a dedicated technical identity, and parameterized queries. + +business_criticality: important + +business_overview: + description: > + Training environment for DevSecOps. This secure variant applies low-cost + transport, storage, and database-query hardening to the baseline model. + images: [] + +technical_overview: + description: > + A user's web browser connects to Juice Shop directly or through a reverse + proxy using HTTPS. Proxy-to-application and outbound WebHook traffic also + use TLS. Application data is written to encrypted persistent storage over + an encrypted database channel using parameterized queries. + images: [] + +questions: + Do you expose port 3000 beyond localhost?: "Only through HTTPS." + Do you use a reverse proxy with TLS and security headers?: "Yes." + Are any outbound integrations (webhooks) configured?: "Optional; HTTPS only." + Is any sensitive data stored in logs or files?: "Sensitive data is minimized and storage is encrypted." + +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 are abused to access internal network resources. + +security_requirements: + TLS in transit: Enforce HTTPS for all user, proxy, and outbound integration traffic. + AuthZ on sensitive routes: Implement strict server-side authorization checks on sensitive functionality. + Rate limiting & lockouts: Apply rate limiting and account lockout policies. + Secure headers: Add HSTS, CSP, X-Frame-Options, and X-Content-Type-Options. + Secrets management: Protect secret keys and credentials and avoid logging them. + Encrypted storage: Encrypt database files, uploads, and logs at rest. + Parameterized queries: Use prepared statements for all database access. + +tags_available: + - docker + - nodejs + - pii + - auth + - tokens + - logs + - public + - actor + - user + - optional + - proxy + - app + - storage + - volume + - saas + - webhook + - primary + - direct + - egress + - database + +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. + + Orders: + id: orders + description: "Order history, addresses, and payment metadata." + usage: business + tags: ["pii"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: important + availability: important + justification_cia_rating: > + Contains personal data and business transaction records. + + Product Catalog: + id: product-catalog + description: "Public product information." + usage: business + tags: ["public"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: public + integrity: important + availability: important + justification_cia_rating: > + Product data is public, but its integrity must be protected. + + Tokens & Sessions: + id: tokens-sessions + description: "Session identifiers, JWTs, and CSRF tokens." + usage: business + tags: ["auth", "tokens"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: important + availability: important + justification_cia_rating: > + Compromised tokens permit session hijacking. + + Logs: + id: logs + description: "Sanitized application and access logs." + usage: devops + tags: ["logs"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: internal + integrity: important + availability: important + justification_cia_rating: > + Logs are internal and must be protected from disclosure and tampering. + +technical_assets: + + User Browser: + id: user-browser + description: "End-user web 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: ["actor", "user"] + internet: true + machine: virtual + encryption: none + owner: External User + confidentiality: public + integrity: operational + availability: operational + justification_cia_rating: "Client controlled by the end user." + multi_tenant: false + redundant: false + custom_developed_parts: false + data_assets_processed: [] + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: + To Reverse Proxy: + target: reverse-proxy + description: "User browser to reverse proxy using HTTPS on port 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: + target: juice-shop + description: "Direct browser access is permitted only over HTTPS." + 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: "Reverse proxy for TLS termination and 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: "Protects inbound traffic and applies security headers." + 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: "Authenticated TLS forwarding from the proxy to Juice Shop." + protocol: https + authentication: client-certificate + authorization: technical-user + tags: [] + vpn: false + ip_filtered: true + 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)." + 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." + multi_tenant: false + redundant: false + custom_developed_parts: true + data_assets_processed: + - user-accounts + - orders + - product-catalog + - tokens-sessions + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: + To Persistent Storage: + target: persistent-storage + description: "Encrypted database access using prepared statements and parameterized queries; sanitized logs are written to encrypted storage." + protocol: jdbc-encrypted + authentication: credentials + authorization: technical-user + tags: ["database"] + vpn: false + ip_filtered: true + readonly: false + usage: business + data_assets_sent: + - user-accounts + - orders + - product-catalog + - logs + data_assets_received: + - user-accounts + - orders + - product-catalog + To Challenge WebHook: + target: webhook-endpoint + description: "Optional outbound callback over HTTPS." + 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 storage for database, uploads, and sanitized logs." + type: datastore + usage: devops + used_as_client_by_human: false + out_of_scope: false + justification_out_of_scope: + size: component + technology: database + tags: ["storage", "volume", "database"] + internet: false + machine: virtual + encryption: data-with-symmetric-shared-key + owner: Lab Owner + confidentiality: internal + integrity: important + availability: important + justification_cia_rating: "Sensitive database and log data is encrypted at rest." + 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 third-party WebHook service." + type: external-entity + usage: business + used_as_client_by_human: false + out_of_scope: true + justification_out_of_scope: "Third-party service 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 integration endpoint." + multi_tenant: true + redundant: true + custom_developed_parts: false + data_assets_processed: + - orders + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: {} + +trust_boundaries: + + Internet: + id: internet + description: "Untrusted public network." + type: network-dedicated-hoster + tags: [] + technical_assets_inside: + - user-browser + - webhook-endpoint + trust_boundaries_nested: + - host + + Host: + id: host + description: "Host machine 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: "Isolated Docker container network." + type: network-dedicated-hoster + tags: [] + technical_assets_inside: + - juice-shop + trust_boundaries_nested: [] + +shared_runtimes: + + Docker Host: + id: docker-host + description: "Docker Engine and default bridge network on the host." + tags: ["docker"] + technical_assets_running: + - juice-shop + +individual_risk_categories: {} + +risk_tracking: {} diff --git a/submissions/lab2.md b/submissions/lab2.md new file mode 100644 index 000000000..aa4765667 --- /dev/null +++ b/submissions/lab2.md @@ -0,0 +1,102 @@ +## Task 1: Baseline Threat Model + +### Risk count by severity + +| Severity | Count | +|----------|------:| +| Critical | 0 | +| High | 0 | +| Elevated | 4 | +| Medium | 14 | +| Low | 5 | +| **Total** | **23** | + +### Top 5 risks + +1. **cross-site-scripting** — Cross-Site Scripting (XSS); severity Elevated; affecting `juice-shop`. +2. **unencrypted-communication** — Unencrypted communication on `Direct to App (no proxy)`; severity Elevated; affecting `user-browser` and `juice-shop`. +3. **unencrypted-communication** — Unencrypted communication on `To App`; severity Elevated; affecting `reverse-proxy` and `juice-shop`. +4. **missing-authentication** — Missing authentication on `To App`; severity Elevated; affecting `juice-shop`. +5. **server-side-request-forgery** — Server-Side Request Forgery (SSRF) through `To Challenge WebHook`; severity Medium; affecting `juice-shop` and `webhook-endpoint`. + +### STRIDE mapping + +1. **cross-site-scripting — T (Tampering):** attacker-controlled script content changes the page behavior and executes in another user's trusted browser context. +2. **unencrypted-communication (browser to app) — I (Information Disclosure):** HTTP exposes session identifiers and other authentication data to anyone able to observe the connection. +3. **unencrypted-communication (proxy to app) — T/I (Tampering and Information Disclosure):** an attacker with access to the host or container network can read or modify traffic after TLS terminates at the proxy. +4. **missing-authentication — S/E (Spoofing and Elevation of Privilege):** an unauthenticated caller can impersonate a trusted upstream component and reach application functionality without proving its identity. +5. **server-side-request-forgery — E/I (Elevation of Privilege and Information Disclosure):** SSRF lets an attacker use the application's network privileges to reach resources and retrieve information that the attacker cannot access directly. + +### Trust boundary observation + +The `User Browser -> Juice Shop Application` arrow named +`Direct to App (no proxy)` crosses from the untrusted Internet boundary through +the Host boundary into the Container Network. It is particularly attractive +because it bypasses the TLS-terminating reverse proxy and sends session data over +HTTP, giving an attacker an opportunity to observe or alter authentication +traffic before it reaches the application. + +## Task 2: Secure Variant & Diff + +### Risk count comparison + +| Severity | Baseline | Secure | Delta | +|----------|---------:|-------:|------:| +| Critical | 0 | 0 | 0 | +| High | 0 | 0 | 0 | +| Elevated | 4 | 2 | -2 | +| Medium | 14 | 12 | -2 | +| Low | 5 | 4 | -1 | +| **Total** | **23** | **18** | **-5** | + +### Rules gone in the secure variant + +1. **unencrypted-communication** — fixed by changing direct browser access and proxy-to-application traffic from HTTP to HTTPS. The outbound WebHook also remains HTTPS. +2. **missing-authentication** — fixed by authenticating the proxy-to-application link with a client certificate and authorizing it as a technical user. +3. **unencrypted-asset** — fixed by declaring transparent encryption for the application runtime and symmetric-key encryption for persistent database and log storage. + +### Which rules are STILL THERE in the secure variant? + +1. **cross-site-scripting** — TLS and storage encryption protect data in transit and at rest, but they do not validate or encode attacker-controlled HTML and JavaScript. This risk still requires contextual output encoding, input handling, and a restrictive Content Security Policy. +2. **server-side-request-forgery** — HTTPS protects the WebHook connection from interception but does not prevent the application from being induced to request an attacker-selected destination. This requires destination allowlisting, URL validation, redirect restrictions, and egress network controls. + +The explicit database communication link also causes Threagile to report +**sql-nosql-injection**. The model declares prepared statements and parameterized +queries as the mitigation, but Threagile 0.9.1 conservatively fires this rule for +database access protocols regardless of that description. + +### Honesty check + +No. The total fell from 23 to 18, a reduction of about 22%, not more than 50%. +The transport encryption, authenticated internal link, and encrypted storage are +relatively low-cost changes that remove several broad exposure risks. Eliminating +the remaining risks requires more targeted controls and architecture work, such +as XSS and CSRF defenses, WebHook egress restrictions, MFA, a WAF, a secrets +vault, build-pipeline modeling, and container hardening. + +## Bonus Task: Auth Flow Threat Model + +### Risk count + +| Severity | Count | +|----------|------:| +| Critical | 0 | +| High | 1 | +| Elevated | 8 | +| Medium | 20 | +| Low | 7 | +| **Total** | **36** | + +### Three auth-specific risks not in the baseline top 5 + +1. **sql-nosql-injection** — STRIDE: **T/E (Tampering and Elevation of Privilege)** — An injection against the credential store could alter account records or roles and grant administrator access. Use parameterized queries, a least-privileged database identity, strict input validation, and tests that verify query parameters cannot change SQL structure. +2. **missing-identity-provider-isolation** — STRIDE: **S/E (Spoofing and Elevation of Privilege)** — The Token Signer and credential store share a network boundary with ordinary application endpoints, so compromise of one endpoint creates a path toward the signing key and user roles. Place identity components in a dedicated network segment, allow only explicit authenticated callers, and keep the signing key in an isolated vault or HSM. +3. **missing-authentication-second-factor** — STRIDE: **S (Spoofing)** — A stolen password or JWT is sufficient to impersonate a user, including on the admin flow. Require MFA for administrator and sensitive actions, combine it with short-lived tokens, and re-authenticate before privileged operations. + +### Reflection + +The architecture-level baseline showed broad web and transport risks but did not +represent the signing key, role-bearing JWT, credential lookup, or per-request +token verification. Modeling the feature separately exposed concrete account +takeover paths: credential-store injection, compromise of a non-isolated token +signer, and single-factor authentication on privileged requests. From ea0bfeb5dd4da6c7c775898c55050477d33f89a0 Mon Sep 17 00:00:00 2001 From: Timur Iakovlev Date: Thu, 18 Jun 2026 10:45:12 +0300 Subject: [PATCH 7/8] 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 2476aac74a15efc74e1a3ec1c5d5da6469acdafc Mon Sep 17 00:00:00 2001 From: Timur Iakovlev Date: Thu, 18 Jun 2026 11:19:21 +0300 Subject: [PATCH 8/8] feat(lab3): SSH signing + gitleaks pre-commit + history rewrite practice --- .pre-commit-config.yaml | 11 ++++ submissions/lab3.md | 115 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..a4865101d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.30.1 + hooks: + - id: gitleaks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: detect-private-key + - id: check-added-large-files diff --git a/submissions/lab3.md b/submissions/lab3.md index 61fa24bee..ad7a4e724 100644 --- a/submissions/lab3.md +++ b/submissions/lab3.md @@ -1 +1,114 @@ -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/shadex/.ssh/id_ed25519.pub` +- `git config --global commit.gpgsign` → `true` + +### Local verification +Output of `git log --show-signature -1`: +```bash +commit ea0bfeb5dd4da6c7c775898c55050477d33f89a0 (HEAD -> feature/lab3, origin/feature/lab3) +Good "git" signature for namespaces=git with ED25519 key SHA256:REDACTED +Author: Timur Iakovlev +Date: Thu Jun 18 10:45:12 2026 +0300 + + test: first signed commit +``` + + +### GitHub verification +- Direct link to your most recent commit on GitHub: `https://github.com/AskoRBINKAs/DevSecOps-Intro/commit/ea0bfeb5dd4da6c7c775898c55050477d33f89a0` +- Screenshot of the Verified badge: `https://github.com/AskoRBINKAs/DevSecOps-Intro/pull/3#issuecomment-4739579803` + +### One-paragraph reflection (2-3 sentences) +A forged-author commit would let an attacker push malicious or risky code while making it look like a trusted teammate wrote it, creating a repudiation problem because the real teammate could deny making the change and the team would have weak proof of who actually did it. In a real codebase, this could be used to sneak in a backdoor, disable tests, or change security logic while shifting blame to someone else. The GitHub Verified badge makes the attack visible because only commits signed with a trusted key show as verified, so an unsigned or incorrectly signed forged-author commit stands out as not cryptographically tied to the claimed author. + +## Task 2: Pre-commit + gitleaks + +### `.pre-commit-config.yaml` (paste the full content) +```yaml +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.30.1 + hooks: + - id: gitleaks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: detect-private-key + - id: check-added-large-files +``` + +### `pre-commit install` output +```text +pre-commit installed at .git/hooks/pre-commit +``` + +### The blocked commit +Output of the git commit that gitleaks blocked (the failing hook output): +```bash +[WARNING] Unstaged files detected. +[INFO] Stashing unstaged files to /home/shadex/.cache/pre-commit/patch1781769679-155635. +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 + +11:01AM INF 0 commits scanned. +11:01AM INF scanned ~101 bytes (101 bytes) in 46.4ms +11:01AM WRN leaks found: 1 + +detect private key.......................................................Passed +check for added large files..............................................Passed +[INFO] Restored changes from /home/shadex/.cache/pre-commit/patch1781769679-155635. +``` + +### Tune-out exercise +1. **Inline allowlist** - a targeted `[allowlist]` rule in `.gitleaks.toml` is OK when the example string is intentionally fake, stable, and narrowly scoped to a specific rule or exact value. This keeps scanning active for the rest of the repository and avoids hiding real secrets in nearby files. +2. **Path exclusion** - excluding `docs/` with `paths: [docs/]` is risky because it creates a blind spot where a real credential could be committed without being scanned. I would only use it for a tightly controlled generated-docs path, and even then I would prefer exact allowlisted examples over excluding the whole directory. + +## Bonus: History Rewrite + +### Before +```text +bf711f4 docs: add usage notes +25d7116 feat: empty log +616f7e3 feat: add config +a8d4c80 init +``` +Output of `git log -p | grep -c 'ghp_'`: **2** + +### After +```text +044aa3b docs: add usage notes +900cf40 feat: empty log +38c68e2 feat: add config +c8384fb 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 and revoke the exposed secret everywhere it was valid, then force-push the rewritten history and coordinate with teammates to reclone or reset. Rewriting removes the secret from Git history, but it does not make a leaked credential safe again. + +### Two real-world gotchas you discovered +1. `git filter-repo` was installed as a Python module, but `git filter-repo` was not available as a Git subcommand in this environment. I had to run the same tool as `python -m git_filter_repo --replace-text /tmp/replace.txt`. +2. `filter-repo` refused to rewrite the sandbox history because the repo did not look like a fresh clone: it reported more than one `HEAD` reflog entry. Since this was a throwaway `/tmp/lab3-bonus` repo with no remote, I reran it with `--force`.