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/.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/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/lab1.md b/submissions/lab1.md new file mode 100644 index 000000000..1bbbcee39 --- /dev/null +++ b/submissions/lab1.md @@ -0,0 +1,89 @@ +# 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: [ ] 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 + +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): 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 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. + diff --git a/submissions/lab3-screen.png b/submissions/lab3-screen.png new file mode 100644 index 000000000..20f6f6676 Binary files /dev/null and b/submissions/lab3-screen.png differ diff --git a/submissions/lab3.md b/submissions/lab3.md new file mode 100644 index 000000000..16a8444b9 --- /dev/null +++ b/submissions/lab3.md @@ -0,0 +1,139 @@ +# 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. +