diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..0c9eb5964 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,28 @@ +## Goal + + + + +## Changes + + + + +## Testing + + + +``` + +``` +## Artifacts \& Screenshots + + + +* [`submissions/labN.md`](submissions/labN.md) + +## Checklist + +* \[ ] Title is clear (`feat(labN): ` style) +* \[ ] No secrets or 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..e46a3248a --- /dev/null +++ b/.github/workflows/lab1-smoke.yml @@ -0,0 +1,34 @@ +name: Lab 1 — Juice Shop Smoke Test + +on: + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + smoke-test: + runs-on: ubuntu-latest + + steps: + - name: Start Juice Shop + run: | + docker run -d --name juice-shop \ + -p 127.0.0.1:3000:3000 \ + bkimminich/juice-shop:v20.0.0 + + - name: Wait for Juice Shop to be ready (up to 60s) + run: | + for i in $(seq 1 30); do + curl --silent --fail http://127.0.0.1:3000/rest/admin/application-version > /dev/null && exit 0 + sleep 2 + done + docker logs juice-shop + exit 1 + + - name: Verify homepage returns HTTP 200 + run: | + STATUS=$(curl --silent --output /dev/null --write-out "%{http_code}" http://127.0.0.1:3000) + echo "HTTP status: $STATUS" + [ "$STATUS" = "200" ] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..d33e0e523 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.21.2 # проверь актуальный тег на github.com/gitleaks/gitleaks/releases + hooks: + - id: gitleaks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: detect-private-key + - 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..e16beea63 --- /dev/null +++ b/labs/lab2/threagile-model-auth.yaml @@ -0,0 +1,459 @@ +threagile_version: 1.0.0 + +title: OWASP Juice Shop — Authentication Flow Threat Model +date: 2026-06-12 + +author: + name: Student Name + homepage: https://example.edu + +management_summary_comment: > + Focused threat model for the Juice Shop authentication flow: login, JWT issuance, + session use, and access to admin endpoints. The scope is deliberately narrow — + only assets and communication links that participate in authentication and + authorization are modeled. This surfaces auth-specific risks that a broad + architecture model tends to miss. + +business_criticality: critical + +business_overview: + description: > + The authentication subsystem of OWASP Juice Shop handles user login, + credential validation, JWT token signing and verification, and access control + to privileged (admin) endpoints. Compromise of any component in this flow + leads directly to account takeover or privilege escalation. + +technical_overview: + description: > + A browser submits credentials to the Auth API endpoint over HTTP. The Auth API + validates credentials against the User Credential Store and, on success, requests + a signed JWT from the Token Signer component. The signed JWT is returned to the + browser and used on subsequent requests. Each protected endpoint verifies the JWT + before processing the request. The Admin Endpoint additionally checks that the + JWT payload contains an admin role claim. + images: [] + +questions: + Are JWT signing keys rotated regularly?: "" + Is the signing key stored outside application memory?: "" + Are failed login attempts rate-limited?: "" + Is the admin role claim validated server-side on every request?: "" + +abuse_cases: + JWT Forgery via Weak Secret: > + Attacker discovers or brute-forces the JWT signing key and forges tokens with + arbitrary claims (e.g., admin role), gaining full privilege escalation. + Credential Brute Force: > + Attacker submits repeated login requests with common passwords to compromise + accounts with no lockout or rate limiting in place. + Token Replay after Logout: > + Attacker captures a valid JWT (e.g., via XSS or network sniffing) and replays + it after the legitimate user has logged out, since JWTs are stateless and + Juice Shop does not maintain a server-side revocation list. + Privilege Escalation via Role Claim Tampering: > + If the JWT secret is known or the signature is not verified, an attacker can + modify the role claim in the token payload to gain admin access. + +security_requirements: + Strong JWT signing key: Use a cryptographically random secret of at least 256 bits; store it in a secrets manager, not in source code. + JWT signature verification on every request: Every protected endpoint must verify the JWT signature before trusting any claim in the payload. + Short token expiry: Issue JWTs with short expiry (e.g., 15 minutes) and implement refresh token rotation to limit the window for token replay. + Rate limiting on login: Apply per-IP and per-account rate limiting on the login endpoint to mitigate brute force attacks. + Admin role checked server-side: The admin role claim must be validated server-side on every request to an admin endpoint; client-supplied role values must never be trusted. + +tags_available: + - browser + - auth + - jwt + - admin + - credentials + - tokens + - signing + - api + - store + - actor + +# ========================= +# DATA ASSETS +# ========================= +data_assets: + + Credentials: + id: credentials + description: "Username and password submitted by the user at login." + usage: business + tags: ["auth", "credentials"] + origin: user-supplied + owner: Lab Owner + quantity: many + confidentiality: strictly-confidential + integrity: critical + availability: important + justification_cia_rating: > + Plaintext credentials are the most sensitive data in the auth flow. + Exposure allows immediate account takeover; integrity is critical to + prevent credential substitution attacks. + + JWT Token: + id: jwt-token + description: "Signed JSON Web Token issued after successful login, used to authenticate subsequent requests." + usage: business + tags: ["auth", "tokens", "jwt"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: > + A valid JWT grants access to protected resources. Integrity is critical — + if the signature can be forged or the payload tampered with, an attacker + gains arbitrary access. Confidentiality is high to prevent token theft. + + JWT Signing Key: + id: jwt-signing-key + description: "Secret key used by the Token Signer to sign and verify JWTs." + usage: devops + tags: ["auth", "tokens", "jwt"] + origin: application + owner: Lab Owner + quantity: very-few + confidentiality: strictly-confidential + integrity: critical + availability: critical + justification_cia_rating: > + Compromise of the signing key allows unlimited JWT forgery. This is the + highest-value secret in the auth subsystem; loss of integrity or + confidentiality is catastrophic. + + User Session State: + id: user-session-state + description: "Client-side session state derived from the JWT (user ID, role, email)." + usage: business + tags: ["auth", "tokens"] + origin: application + owner: Lab Owner + quantity: many + confidentiality: confidential + integrity: important + availability: operational + justification_cia_rating: > + Session state is derived from the JWT and used to make authorization + decisions in the browser. Tampering with it (e.g., via XSS) can lead + to privilege escalation on the client side. + + Admin Operation Requests: + id: admin-requests + description: "Requests to admin-only endpoints (e.g., user management, challenge status)." + usage: business + tags: ["admin", "auth"] + origin: user-supplied + owner: Lab Owner + quantity: few + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: > + Admin operations have the highest impact — they can modify users, expose + all data, and alter application state. Both confidentiality and integrity + are critical to prevent unauthorized admin actions. + +# ========================= +# TECHNICAL ASSETS +# ========================= +technical_assets: + + Browser: + id: browser + description: "End-user web browser submitting credentials and consuming JWTs." + 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", "browser"] + internet: true + machine: virtual + encryption: none + owner: External User + confidentiality: public + integrity: operational + availability: operational + justification_cia_rating: "Client controlled by the end user; treated as untrusted input source." + multi_tenant: false + redundant: false + custom_developed_parts: false + data_assets_processed: + - jwt-token + - user-session-state + data_assets_stored: + - jwt-token + - user-session-state + data_formats_accepted: + - json + communication_links: + Login Request: + target: auth-api + description: "Browser sends credentials (username + password) to the Auth API login endpoint over HTTP." + protocol: http + authentication: none + authorization: none + tags: ["auth"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - credentials + data_assets_received: + - jwt-token + Authenticated API Request: + target: auth-api + description: "Browser sends JWT in Authorization header to access protected endpoints." + protocol: http + authentication: token + authorization: enduser-identity-propagation + tags: ["auth"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - jwt-token + data_assets_received: + - user-session-state + Admin Endpoint Request: + target: admin-endpoint + description: "Browser sends JWT with admin role claim to access admin-only functionality." + protocol: http + authentication: token + authorization: enduser-identity-propagation + tags: ["admin", "auth"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - jwt-token + - admin-requests + data_assets_received: [] + + Auth API: + id: auth-api + description: "Juice Shop authentication endpoint handling login, registration, and JWT issuance." + 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", "auth"] + internet: false + machine: container + encryption: none + owner: Lab Owner + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: > + The auth API is the entry point for all authentication. Its integrity is + critical — any bypass or misconfiguration directly enables account takeover. + multi_tenant: false + redundant: false + custom_developed_parts: true + data_assets_processed: + - credentials + - jwt-token + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: + Credential Lookup: + target: credential-store + description: "Auth API queries the User Credential Store to validate submitted credentials." + protocol: http + authentication: none + authorization: none + tags: ["auth"] + vpn: false + ip_filtered: false + readonly: true + usage: business + data_assets_sent: + - credentials + data_assets_received: + - credentials + Request JWT Signing: + target: token-signer + description: "Auth API requests a signed JWT from the Token Signer after successful credential validation." + protocol: http + authentication: none + authorization: none + tags: ["auth", "jwt"] + vpn: false + ip_filtered: false + readonly: false + usage: business + data_assets_sent: + - user-session-state + data_assets_received: + - jwt-token + + Token Signer: + id: token-signer + description: "Component responsible for signing JWTs with the application secret key and verifying JWT signatures on incoming requests." + 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", "jwt", "signing"] + internet: false + machine: container + encryption: none + owner: Lab Owner + confidentiality: strictly-confidential + integrity: critical + availability: critical + justification_cia_rating: > + The Token Signer holds the JWT signing key in memory. Compromise of this + component is equivalent to compromise of the entire authentication system. + multi_tenant: false + redundant: false + custom_developed_parts: true + data_assets_processed: + - jwt-token + - jwt-signing-key + data_assets_stored: + - jwt-signing-key + data_formats_accepted: + - json + communication_links: {} + + Credential Store: + id: credential-store + description: "User credential store (SQLite database) holding hashed passwords and user records." + type: datastore + usage: business + used_as_client_by_human: false + out_of_scope: false + justification_out_of_scope: + size: component + technology: database + tags: ["store", "auth"] + internet: false + machine: virtual + encryption: none + owner: Lab Owner + confidentiality: strictly-confidential + integrity: critical + availability: important + justification_cia_rating: > + Contains hashed user passwords and account data. Direct access allows + offline password cracking. Integrity is critical to prevent account + record manipulation. + multi_tenant: false + redundant: false + custom_developed_parts: false + data_assets_processed: [] + data_assets_stored: + - credentials + data_formats_accepted: + - file + communication_links: {} + + Admin Endpoint: + id: admin-endpoint + description: "Admin-only API endpoints (e.g., /#/administration) that require a valid JWT with admin role claim." + 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: ["admin", "api", "auth"] + internet: false + machine: container + encryption: none + owner: Lab Owner + confidentiality: confidential + integrity: critical + availability: important + justification_cia_rating: > + Admin endpoints expose full application control. Unauthorized access leads + to complete compromise of application data and user accounts. + multi_tenant: false + redundant: false + custom_developed_parts: true + data_assets_processed: + - jwt-token + - admin-requests + data_assets_stored: [] + data_formats_accepted: + - json + communication_links: + Verify JWT for Admin: + target: token-signer + description: "Admin endpoint forwards the JWT to the Token Signer for signature verification and role claim validation." + protocol: http + authentication: token + authorization: technical-user + tags: ["auth", "jwt"] + vpn: false + ip_filtered: false + readonly: true + usage: business + data_assets_sent: + - jwt-token + data_assets_received: + - jwt-token + +# ========================= +# TRUST BOUNDARIES +# ========================= +trust_boundaries: + + Internet: + id: internet + description: "Untrusted public network where the browser operates." + type: network-dedicated-hoster + tags: [] + technical_assets_inside: + - browser + trust_boundaries_nested: + - container-network + + Container Network: + id: container-network + description: "Docker container network hosting the Juice Shop auth components." + type: network-dedicated-hoster + tags: [] + technical_assets_inside: + - auth-api + - token-signer + - credential-store + - admin-endpoint + trust_boundaries_nested: [] + +# ========================= +# SHARED RUNTIMES +# ========================= +shared_runtimes: + + Auth Container: + id: auth-container + description: "Docker container running the Juice Shop Node.js process (auth API, token signer, admin endpoint all run in-process)." + tags: ["auth"] + technical_assets_running: + - auth-api + - token-signer + - 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..898ba732a --- /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 +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 (HTTP on 3000)." + 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 (HTTP on 3000 internally)." + protocol: https + authentication: token + authorization: technical-user + 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)." + 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: data-with-symmetric-shared-key + owner: Lab Owner + confidentiality: internal + integrity: important + availability: important + justification_cia_rating: "In-scope web application (contains all business logic and vulnerabilities by design)." + 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 (HTTP POST) to external WebHook when a challenge is solved." + 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: "Host-mounted volume for database, file uploads, and logs." + 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 – not directly exposed, but if compromised it contains sensitive data (database 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..bbcfbc5a0 --- /dev/null +++ b/submissions/lab1.md @@ -0,0 +1,101 @@ +# 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: Docker version 29.4.2, build 055a478 + +### Deployment Details + +* Run command used: `docker run -d --name juice-shop -p 127.0.0.1:3000:3000 bkimminich/juice-shop:v20.0.0` +* Access URL: http://127.0.0.1:3000 +* Network exposure: 127.0.0.1 only? \[x] Yes \[ ] No +* Container restart policy: default `no` + +### Health Check + +* HTTP code on `/`: 200 +* Product count from `/api/Products`: 46 items returned +* Container uptime (from `docker ps`): + +``` + NAMES STATUS PORTS + juice-shop Up 12 minutes 127.0.0.1:3000->3000/tcp + ``` + +* Version check (`/rest/admin/application-version`): + +```json + {"version":"20.0.0"} + ``` + +### Initial Surface Snapshot (from browser exploration) + +* Login/Registration visible: \[x] Yes \[ ] No — Account menu (top-right) leads to `/#/login`; registration available from the login page +* Product listing/search present: \[x] Yes \[ ] No — product grid loads on the homepage with images, prices, and add-to-cart buttons +* Admin or account area discoverable: \[x] Yes \[ ] No — Account menu is visible without authentication; admin panel path (`/#/administration`) is discoverable via JavaScript source +* Client-side errors in DevTools console: \[ ] Yes \[x] No — Firefox DevTools console showed no errors on page load +* Pre-populated local storage / cookies: Two cookies set automatically on first visit: + + * `language=en` — stores the selected UI language + * `welcomebanner\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\_status=dismiss` — tracks dismissal of the welcome banner + +### Security Headers (from `Invoke-WebRequest` response headers) + +``` +Access-Control-Allow-Origin : \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* +X-Content-Type-Options : nosniff +X-Frame-Options : SAMEORIGIN +Feature-Policy : payment 'self' +X-Recruiting : /#/jobs +Content-Type : text/html; charset=UTF-8 +Cache-Control : public, max-age=0 +``` + +Headers that are **MISSING** (cross-referenced with OWASP Top 10:2025 — A05: Security Misconfiguration): + +* \[x] `Content-Security-Policy` — **MISSING** +* \[x] `Strict-Transport-Security` — **MISSING** (app runs over plain HTTP) +* \[ ] `X-Content-Type-Options: nosniff` — present +* \[ ] `X-Frame-Options` — present (`SAMEORIGIN`) + +Additional concern: `Access-Control-Allow-Origin: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*` is a wildcard CORS policy, meaning any origin can make cross-origin requests to this API. + +### Top 3 Risks Observed + +1. **Missing Content-Security-Policy** — The application does not set a `Content-Security-Policy` header, meaning the browser applies no restrictions on which scripts, styles, or frames can be loaded. An attacker who injects malicious JavaScript (e.g., via a stored XSS payload in a product review) can execute arbitrary code in any victim's browser with no CSP barrier. Maps to **A05:2025 – Security Misconfiguration**. +2. **Unauthenticated Access to Reviews and User Endpoints** — The `/rest/products//reviews` endpoint returns full review data including user references with no authentication required, and `/rest/user/whoami` is queried on every page load without a token. This exposes internal user data and API structure to any unauthenticated visitor or automated scanner. Maps to **A01:2025 – Broken Access Control**. +3. **Wildcard CORS Policy** — Every API response includes Access-Control-Allow-Origin: \*, allowing any website to read API responses from a victim's browser via cross-origin requests. Combined with unauthenticated endpoints, a malicious page can silently enumerate products, read reviews, and probe the API structure on behalf of any visitor — no user interaction required beyond visiting the attacker's site. Maps to A05:2025 – Security Misconfiguration. + + +## 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 or large temp files committed + * \[ ] Submission file at `submissions/labN.md` exists +* Auto-fill verified: \[x] Yes — PR description showed the template automatically when opening the PR from `feature/lab1` + + +### GitHub Community + +Starring a repository bookmarks it for later and signals community trust — star count is a common indicator of a project's popularity and adoption. Following professors, TAs, and classmates surfaces their activity in your feed, making it easy to discover new tools and stay updated on each other's work. + +## Bonus: CI Smoke Test + +* Workflow file: `.github/workflows/lab1-smoke.yml` +* Trigger: `pull\_request` on main +* Run URL (must be green): *https://github.com/Meliman1000-7/DevSecOps-Intro/actions/runs/27373488077* +* Workflow run duration: *21s* +* Curl response excerpt: +``` +HTTP status: 200 +``` diff --git a/submissions/lab2.md b/submissions/lab2.md new file mode 100644 index 000000000..b6b4887fd --- /dev/null +++ b/submissions/lab2.md @@ -0,0 +1,108 @@ +# Lab 2 — Submission + +## 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. **unencrypted-communication** — Proxy→App over HTTP; severity Elevated; affecting `reverse-proxy` +2. **unencrypted-communication** — Browser→App direct over HTTP, transfers auth data; severity Elevated; affecting `user-browser` +3. **cross-site-scripting** — XSS risk at Juice Shop Application; severity Elevated; affecting `juice-shop` +4. **missing-authentication** — no auth on Proxy→App link; severity Elevated; affecting `juice-shop` +5. **unnecessary-technical-asset** — Persistent Storage flagged as unnecessary; severity Low; affecting `persistent-storage` + +### STRIDE mapping + +- Risk 1 (`unencrypted-communication`, Proxy→App): **T** — plaintext internal traffic allows interception and modification of data in transit. +- Risk 2 (`unencrypted-communication`, Browser→App): **I** — session tokens sent over HTTP are exposed to passive eavesdropping. +- Risk 3 (`cross-site-scripting`): **T** — injected scripts tamper with page content and can steal session tokens from other users. +- Risk 4 (`missing-authentication`): **S** — any process with network access to the internal link can impersonate the reverse proxy. +- Risk 5 (`unnecessary-technical-asset`): **E** — unencrypted storage holding user accounts and orders increases blast radius on host compromise. + +### Trust boundary observation + +The **"Direct to App (no proxy)"** arrow in `data-flow-diagram.png` crosses from the **Internet** boundary (User Browser) straight into the **Container Network** (Juice Shop), bypassing the Host boundary. It carries session tokens over plain HTTP with no TLS, WAF, or rate limiting — making it the most attractive target for credential interception and session hijacking. + +--- + +## Task 2: Secure Variant & Diff + +### Changes made to `threagile-model-secure.yaml` + +| # | Change | Location | Old value | New value | +|---|--------|----------|-----------|-----------| +| 1 | Force HTTPS on direct browser→app link | `User Browser` → `Direct to App (no proxy)` | `protocol: http` | `protocol: https` | +| 2 | Force HTTPS on proxy→app internal link | `Reverse Proxy` → `To App` | `protocol: http` | `protocol: https` | +| 3 | Add authentication on proxy→app link | `Reverse Proxy` → `To App` | `authentication: none` | `authentication: token` | +| 4 | Add authorization on proxy→app link | `Reverse Proxy` → `To App` | `authorization: none` | `authorization: technical-user` | +| 5 | Encrypt Juice Shop application asset | `Juice Shop Application` | `encryption: none` | `encryption: data-with-symmetric-shared-key` | +| 6 | Encrypt persistent storage asset | `Persistent Storage` | `encryption: none` | `encryption: data-with-symmetric-shared-key` | + +### Risk count comparison + +| Severity | Baseline | Secure | Δ | +|----------|---------:|-------:|--:| +| Critical | 0 | 0 | 0 | +| High | 0 | 0 | 0 | +| Elevated | 4 | 1 | -3 | +| Medium | 14 | 12 | -2 | +| Low | 5 | 5 | 0 | +| **Total** | **23** | **18** | **-5** | + +### Which rules are GONE in the secure variant? + +1. `unencrypted-communication` (Proxy→App) — fixed by `protocol: https` on the internal link +2. `unencrypted-communication` (Browser→App) — fixed by `protocol: https` on the direct link +3. `missing-authentication` (Proxy→App) — fixed by `authentication: token` + `authorization: technical-user` +4. `unencrypted-asset` (Juice Shop) — fixed by `encryption: data-with-symmetric-shared-key` +5. `unencrypted-asset` (Persistent Storage) — fixed by `encryption: data-with-symmetric-shared-key` + +### Which rules are STILL THERE in the secure variant? + +1. **`cross-site-scripting`** — XSS is a code-level issue in how the app handles user input. Transport encryption and authentication headers have no effect on it; fixing it requires input sanitization and a Content Security Policy. + +2. **`missing-authentication-second-factor`** — The model still describes single-factor auth. MFA requires application-level changes that cannot be expressed in Threagile YAML fields. + +### Honesty check + +Total dropped by 5 risks (≈22%), well under 50%. The fixed risks were all cheap infrastructure wins (TLS, encryption at rest). The remaining 18 are application-layer issues — XSS, CSRF, missing MFA, SSRF, container hardening — that require code changes and additional controls. Easy wins are worth doing first, but they leave the majority of the attack surface untouched. + +--- + +## Bonus Task: Auth Flow Threat Model + +### Risk count + +| Severity | Count | +|----------|------:| +| Critical | 0 | +| High | 1 | +| Elevated | 12 | +| Medium | 22 | +| Low | 6 | +| **Total** | **41** | + +### Three auth-specific risks NOT in the baseline model's top 5 + +1. **`sql-nosql-injection`** — STRIDE: **T** (Tampering) + High-severity SQL injection on the `Credential Lookup` link (Auth API → Credential Store). The baseline never models this link explicitly, so the rule never fires. Mitigation: parameterized queries for all credential lookups. + +2. **`unguarded-access-from-internet`** — STRIDE: **E** (Elevation of Privilege) + Auth API and Admin Endpoint are directly reachable from the Internet with no WAF or rate limiting. The baseline hides this behind an optional proxy. Mitigation: WAF or API gateway in front of auth endpoints; per-IP rate limiting on login. + +3. **`missing-vault`** — STRIDE: **I** (Information Disclosure) + The Token Signer stores the JWT signing key with no secrets manager declared. This fires because the auth model explicitly declares `jwt-signing-key` as a `strictly-confidential` asset — detail absent from the baseline. Mitigation: store the signing key in a secrets manager; inject at runtime via environment variable. + +### Reflection + +The focused auth model surfaced SQL injection, direct internet exposure, and missing secrets management — none of which appear in the baseline top 5. The baseline treats all of Juice Shop as one "web-server" process, so Threagile cannot reason about internal auth data flows. Modeling each link explicitly (Auth API → Credential Store, Auth API → Token Signer) is where the highest-severity auth risks emerge. diff --git a/submissions/lab3.md b/submissions/lab3.md new file mode 100644 index 000000000..1a376ebd7 --- /dev/null +++ b/submissions/lab3.md @@ -0,0 +1,125 @@ +# Lab 3 — Submission + +## Task 1: SSH Commit Signing + +### Local configuration +- `git config --global gpg.format` → `ssh` +- `git config --global user.signingkey` → `~/.ssh/id_ed25519.pub` +- `git config --global commit.gpgsign` → `true` + +### Local verification +Output of `git log --show-signature -1`: +``` +commit 667d59cb9d1455466d29a35dde8481ec3e873a18 (HEAD -> feature/lab3) +Good "git" signature for egor.neyalov@mail.ru with ED25519 key SHA256:VjXsGms5VaarmJ6Mk3UML8JHF/TCOUcF90EFeq9HRyE +Author: Meliman1000-7 +Date: Fri Jun 12 19:42:50 2026 +0300 + + test: first signed commit +``` + +### GitHub verification +- Direct link to commit: https://github.com/Meliman1000-7/DevSecOps-Intro/commit/667d59cb9d1455466d29a35dde8481ec3e873a18 +- Verified badge: ✅ Green **Verified** badge confirmed on GitHub (see screenshot in PR) + +### One-paragraph reflection +A forged-author commit — where an attacker sets `git config user.email` to a trusted colleague's address — would allow them to introduce malicious code (a backdoor, a dependency swap, a secrets exfiltration snippet) while attribution points to an innocent team member. In a real codebase this could pass code review because reviewers trust the name they see, and audit logs would blame the wrong person. The green **Verified** badge breaks this attack: because the badge requires the committer to hold the private key registered under that email as a GitHub Signing Key, a forged author address without the matching key produces an **Unverified** commit that stands out immediately in the commit history. + +--- + +## Task 2: Pre-commit + gitleaks + +### `.pre-commit-config.yaml` (full content) +```yaml +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.21.2 + hooks: + - id: gitleaks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: detect-private-key + - id: check-added-large-files +``` + +### `pre-commit install` output +``` +pre-commit installed at .git/hooks/pre-commit +``` + +### The blocked commit +Output of `git commit -m "test: should be blocked by gitleaks"`: +``` +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 + +8:18PM INF 1 commits scanned. +8:18PM INF scan completed in 7.57ms +8:18PM WRN leaks found: 1 + +detect private key.......................................................Passed +check for added large files..............................................Passed +``` + +Gitleaks fired on the `github-pat` rule, flagged the finding in `submissions/leak-attempt.txt:2`, and aborted the commit with exit code 1. The file was unstaged and deleted after the test — it never entered the repository. + +### Tune-out exercise + +**1. Inline allowlist — `[allowlist]` block in `.gitleaks.toml`** + +This approach adds an exception directly to the gitleaks configuration by specifying a finding fingerprint or a regex pattern to ignore. It is appropriate when the string is a well-known documentation example (e.g. `AKIAIOSFODNN7EXAMPLE` from official AWS docs) whose value is fixed and will never be a real secret. The risk is that developers may abuse the allowlist by adding real secrets under the pretext of "this is just a test value", effectively disabling detection for a live credential. + +**2. Path exclusion — `paths: [docs/]` in `.gitleaks.toml`** + +This approach excludes an entire directory from scanning. It is convenient when `docs/` contains many key-shaped strings in different formats and maintaining a per-finding allowlist is impractical. The danger is that if a developer accidentally places a real secret in `docs/` (for example, copying a working config as a usage example), gitleaks will silently skip it — meaning the control fails precisely where it is least expected to be bypassed. + +--- + +## Bonus: History Rewrite + +### Before +``` +2b564af (HEAD -> main) docs: add usage notes +657ee91 feat: empty log +9cd940d feat: add config +b786272 init +``` +Output of `git log -p | grep -c 'ghp_AAAA'`: **2** +Output of `git log -p | grep -c 'REDACTED'`: **0** + +### After +``` +86cc1b3 (HEAD -> main) docs: add usage notes +3398aca feat: empty log +5e6a9b2 feat: add config +ceea4f8 init +``` +Output of `git log -p | grep -c 'ghp_AAAA'`: **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 history locally and force-push to all remotes +2. **Secret rotation** — the mandatory second step in a real incident. Rewriting history removes the secret from the repository, but does not revoke it: if the key or token was ever visible, it may have been copied, cached by a CI/CD system, or saved in logs. Until the old secret is invalidated and replaced with a new one, the cleanup is incomplete and the exposure window remains open. + +### Two real-world gotchas + +1. **`filter-repo` refused to run without `--force`** — the tool checks that the repository is a fresh clone (at most one reflog entry for HEAD) and raises the error `Refusing to destructively overwrite repo history`. In the sandbox this was resolved with the `--force` flag; in a real incident the correct approach is to work on a fresh `git clone` to avoid accidentally losing local changes during the rewrite. + +2. **All commit hashes changed after the rewrite** — even the `feat: empty log` commit, which contained no secret, received a new SHA (`657ee91` → `3398aca`). In a real project this means all open PRs, commit links in issues, and references in external systems (Jira, Slack) immediately become invalid, and every team member must run `git fetch --force` and rebase their branches — otherwise their next push could silently restore the old history containing the leaked secret.