Module 5 โ€” Basic Applications

๐ŸŽฏ Learning Objectives

  • Draw the complete DFD for a simple CRUD web app
  • Identify the most common application-level threats by category
  • Understand authentication and session management attack patterns
  • Map OWASP top vulnerabilities to STRIDE categories
  • Define mitigations for each identified threat

1The Example Application

We'll threat model "TaskFlow" โ€” a simple task management app. It's deliberately minimal to keep the focus on threat modeling mechanics. But it contains all the patterns you'll see in any production application.

๐Ÿ‘ค
Users
Register with email + password. Log in/out. Each user sees only their own tasks.
โœ…
Tasks
CRUD operations: create, read, update, delete tasks. Tasks belong to a single user.
๐Ÿ””
Notifications
Email reminders for due tasks. Uses a third-party email service (SendGrid).
๐Ÿ—„๏ธ
Storage
PostgreSQL database. Users table and Tasks table. Hosted on a cloud VPS.
๐Ÿ—๏ธ
Tech Stack (for context)
Node.js + Express API ยท React frontend ยท PostgreSQL ยท JWT sessions ยท SendGrid email ยท Nginx reverse proxy ยท Hosted on a single VPS. This is typical of a startup MVP.

2The TaskFlow DFD

Let's draw the architecture. The goal is to see every data flow and every trust boundary:

TaskFlow โ€” Full Data Flow Diagram
๐ŸŒ Internet (Untrusted) ๐Ÿ‘ค Browser React SPA โœ… App Server (VPS) ๐Ÿ”€ Nginx Reverse proxy / TLS 1. Auth Service JWT issue/verify 2. Tasks API CRUD + authorization 3. Notify Email scheduler ๐Ÿ—„๏ธ DB Zone Users email, pw_hash, id Tasks id, user_id, title, due ๐Ÿ“ฆ External ๐Ÿ“ง SendGrid HTTPS requests /auth/* /tasks/* JWT token lookup/create CRUD schedule send email verify JWT
TaskFlow's complete DFD โ€” three trust zones, three processes, two data stores, and one external entity

3Authentication Threats (Process 1)

๐ŸŽญ
T-01 โ€” Credential Stuffing [STRIDE: Spoofing]
Attacker uses credential dumps from other breaches to try username/password pairs at scale. The auth service will happily accept valid stolen credentials.
CriticalSpoofing
๐Ÿ›ก๏ธ
Mitigations
TOTP-based MFA ยท bcrypt/Argon2 password hashing (slow hash โ†’ defeats stuffing rate) ยท Bot detection (CAPTCHA after 3 fails) ยท Notify user of new login location ยท Check credentials against HaveIBeenPwned API on login
๐Ÿ”‘
T-02 โ€” Weak JWT Secret [STRIDE: Tampering + Elevation of Privilege]
If the JWT signing secret is too short or predictable, an attacker can brute-force it offline, then forge tokens with arbitrary claims (e.g., admin role, different user_id).
CriticalTamperingElevation
๐Ÿ›ก๏ธ
Mitigations
256-bit cryptographically random JWT secret stored in a secrets manager (never in code/env file) ยท Prefer RS256 (asymmetric) over HS256 ยท Short token expiry (15 min access + refresh token rotation)
๐Ÿช
T-03 โ€” JWT Stored in localStorage [STRIDE: Information Disclosure]
React SPA stores JWT in localStorage โ€” accessible to any JavaScript on the page. An XSS vulnerability in any dependency can steal the token and give the attacker a persistent session.
HighInfo Disclosure
๐Ÿ›ก๏ธ
Mitigations
Store JWT in HttpOnly + Secure + SameSite=Strict cookie instead of localStorage ยท Add Content-Security-Policy headers ยท Subresource Integrity (SRI) for all third-party scripts

4Authorization Threats (Process 2 โ€” Tasks API)

๐Ÿšจ
The #1 App-Level Vulnerability: IDOR
Insecure Direct Object Reference (IDOR) is the most common authorization failure in CRUD apps. It happens when the API trusts user-supplied IDs without verifying ownership. Example: GET /api/tasks/42 โ€” does the server check that task 42 belongs to the authenticated user? If not, any logged-in user can read anyone's tasks.
๐Ÿ‘€
T-04 โ€” IDOR: Read Other Users' Tasks [STRIDE: Info Disclosure + Elevation]
Authenticated attacker changes the task ID in the URL from their own (e.g., 42) to another user's (e.g., 43). If the API doesn't check task.user_id === jwt.user_id, they get full read access to another user's data.
CriticalInfo DisclosureElevation
// โŒ Vulnerable
app.get('/api/tasks/:id', auth, async (req, res) => {
  const task = await db.query('SELECT * FROM tasks WHERE id = $1', [req.params.id]);
  res.json(task);  // Missing: check task.user_id === req.user.id
});

// โœ… Fixed
app.get('/api/tasks/:id', auth, async (req, res) => {
  const task = await db.query(
    'SELECT * FROM tasks WHERE id = $1 AND user_id = $2',
    [req.params.id, req.user.id]  // Ownership enforced in query
  );
  if (!task) return res.status(404).json({ error: 'Not found' });
  res.json(task);
});
๐Ÿ’‰
T-05 โ€” SQL Injection via Task Title [STRIDE: Tampering + Info Disclosure]
If the task title field is inserted directly into a SQL query string, an attacker submits '; DROP TABLE tasks; -- or ' OR '1'='1 to extract or destroy data.
CriticalTampering
// โŒ Vulnerable
const query = `INSERT INTO tasks (title) VALUES ('${req.body.title}')`;

// โœ… Fixed โ€” parameterized query
const query = await db.query('INSERT INTO tasks (title, user_id) VALUES ($1, $2)',
  [req.body.title, req.user.id]);

5Data Store Threats

๐Ÿ”“
T-06 โ€” Plaintext Passwords in DB [STRIDE: Information Disclosure]
If the database is breached and passwords are stored in plaintext (or with weak MD5 hashing), every user account is immediately compromised. Attackers sell these on dark markets within hours.
CriticalInfo Disclosure
๐Ÿ›ก๏ธ
Mitigations
Always hash passwords with bcrypt (cost factor 12+) or Argon2id. Never MD5, SHA-1, or SHA-256 alone โ€” these are too fast. Salt is automatically included in bcrypt. Store only the hash, never the plaintext.
๐ŸŒ
T-07 โ€” DB Exposed to Internet [STRIDE: Denial of Service + Info Disclosure]
PostgreSQL running on default port 5432 exposed to the internet. Attackers scan for exposed databases continuously. Direct database attacks or brute-force connection attempts.
HighInfo DisclosureDoS
๐Ÿ›ก๏ธ
Mitigations
Bind PostgreSQL to localhost only (listen_addresses = 'localhost' in postgresql.conf). Firewall rules: no inbound access on port 5432. App server connects via Unix socket or localhost only.

6External Service Threats (Process 3 + SendGrid)

๐Ÿ—๏ธ
T-08 โ€” SendGrid API Key Exposure [STRIDE: Spoofing]
The SendGrid API key is hardcoded in the source code or committed to a public GitHub repo. Attacker finds it, uses it to send phishing emails from the app's domain โ€” destroying email reputation.
HighSpoofing
๐Ÿ›ก๏ธ
Mitigations
Store API keys in environment variables or secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.). Never commit secrets. Add .env to .gitignore. Use git-secrets or similar pre-commit hooks to catch accidental commits.

7Full Threat Table โ€” TaskFlow

IDComponentSTRIDEThreatRiskDecision
T-01Auth ServiceSCredential StuffingCriticalMitigate: MFA + bot detection
T-02JWT SigningT+EWeak JWT secret โ†’ token forgeryCriticalMitigate: 256-bit secret, RS256
T-03Frontend (SPA)IJWT in localStorage โ†’ XSS theftHighMitigate: HttpOnly cookie
T-04Tasks APII+EIDOR: access other users' tasksCriticalMitigate: ownership check in query
T-05Tasks APITSQL Injection via task titleCriticalMitigate: parameterized queries
T-06Users DBIPlaintext passwordsCriticalMitigate: bcrypt/Argon2id
T-07DatabaseD+IDB exposed to internetHighMitigate: localhost-only binding
T-08Notify โ†’ SendGridSAPI key hardcoded in codeHighMitigate: secrets manager

8Your Turn โ€” Spot the Threat

โœ๏ธ Exercise 5.1
Identify this vulnerability
A logged-in user sends GET /api/tasks/99. Task #99 belongs to a different user. The API returns it without error. What STRIDE category does this represent?

9Module Summary

โœ…
Key Takeaways
  • Authentication: hash passwords (bcrypt), short JWTs, HttpOnly cookies, MFA
  • Authorization: always verify ownership server-side โ€” IDOR is the #1 app-level failure
  • SQL injection: parameterized queries โ€” always, no exceptions
  • Secrets: never in source code โ€” use environment variables or secrets managers
  • Network: databases should never be directly reachable from the internet
1 / 9