OWASP Interview Questions (Free Preview)
Free sample of 15 from 73 questions available
A02:2021 - Cryptographic Failures
What is the difference between encryption at rest and encryption in transit?
The 30-Second Answer: Encryption at rest protects data stored on physical media (databases, hard drives, backups) using algorithms like AES-256, while encryption in transit protects data moving across networks (between client and server, between services) using protocols like TLS/SSL. Both are essential - data at rest prevents unauthorized access if storage is compromised, while data in transit prevents interception during transmission.
The 2-Minute Answer (If They Want More): Encryption at rest focuses on protecting stored data from unauthorized access when the system is breached or physical media is stolen. This includes database encryption, full-disk encryption, file-level encryption, and encrypted backups. Common implementations use AES (Advanced Encryption Standard) with 256-bit keys, often with envelope encryption where data is encrypted with a data encryption key (DEK), which is itself encrypted with a key encryption key (KEK) managed by a KMS (Key Management Service).
Encryption in transit protects data while it moves between locations - from user browsers to web servers, between microservices, to external APIs, or across database connections. The primary protocol is TLS (Transport Layer Security), which provides confidentiality through encryption, integrity through message authentication codes, and authenticity through certificates. Modern applications should enforce TLS 1.2 as minimum (preferably TLS 1.3) and use strong cipher suites.
A critical mistake is protecting only one aspect. For example, using HTTPS for transit but storing passwords in plaintext means attackers accessing the database get immediate access. Similarly, encrypting the database but using HTTP means credentials can be intercepted in transit. Both layers are required for comprehensive security.
Cloud providers typically offer encryption at rest (like AWS EBS encryption, Azure Storage Service Encryption) and encryption in transit (TLS endpoints, VPN connections). However, these need to be explicitly enabled and configured - they're not always on by default.
Code Example:
// Encryption in Transit - HTTPS server with strong TLS configuration
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem'),
// Enforce TLS 1.2+ and strong cipher suites
minVersion: 'TLSv1.2',
ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384',
honorCipherOrder: true
};
https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('Secure connection established');
}).listen(443);
// Encryption at Rest - Database connection with encryption enabled
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'db.example.com',
user: 'app_user',
password: process.env.DB_PASSWORD,
database: 'myapp',
ssl: {
ca: fs.readFileSync('ca-cert.pem'),
rejectUnauthorized: true
},
// Enable MySQL transparent data encryption (TDE)
// Note: This requires server-side configuration as well
});
// Application-level encryption before storing
const crypto = require('crypto');
function encryptSensitiveData(plaintext, encryptionKey) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(
'aes-256-gcm',
Buffer.from(encryptionKey, 'hex'),
iv
);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
// Return IV, encrypted data, and auth tag for storage
return {
iv: iv.toString('hex'),
encryptedData: encrypted,
authTag: authTag.toString('hex')
};
}
function decryptSensitiveData(encryptedObj, encryptionKey) {
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
Buffer.from(encryptionKey, 'hex'),
Buffer.from(encryptedObj.iv, 'hex')
);
decipher.setAuthTag(Buffer.from(encryptedObj.authTag, 'hex'));
let decrypted = decipher.update(encryptedObj.encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Usage example
const masterKey = process.env.MASTER_ENCRYPTION_KEY; // 64 hex chars for 256 bits
const ssn = '123-45-6789';
const encrypted = encryptSensitiveData(ssn, masterKey);
// Store encrypted.iv, encrypted.encryptedData, encrypted.authTag in database
// Later, retrieve and decrypt
const original = decryptSensitiveData(encrypted, masterKey);
References:
↑ Back to topCross-Site Request Forgery (CSRF)
What is the difference between SameSite=Strict, Lax, and None?
The 30-Second Answer: Strict never sends cookies on cross-site requests (maximum protection), Lax sends cookies only on top-level navigations with safe HTTP methods like GET (balanced protection), and None sends cookies on all cross-site requests when explicitly configured (no CSRF protection, used for legitimate third-party integrations).
The 2-Minute Answer (If They Want More): The three SameSite values offer different levels of CSRF protection with varying impacts on functionality. Understanding these differences is crucial for properly securing your application while maintaining necessary cross-site features.
SameSite=Strict provides the strongest CSRF protection by never sending cookies with cross-site requests, even when users click links from external sites. This means if a user clicks a link to your-bank.com from an email or search results, they'll appear logged out initially and need to log in again. While this maximizes security, it can hurt user experience for legitimate use cases. Strict is ideal for highly sensitive cookies like session identifiers and authentication tokens where security outweighs convenience.
SameSite=Lax offers a balanced approach and is the default in modern browsers. It sends cookies with top-level navigation (like clicking a link) using safe HTTP methods (GET, HEAD, OPTIONS) but blocks cookies on cross-site subrequests (like images, iframes, AJAX calls) and unsafe methods (POST, PUT, DELETE). This prevents most CSRF attacks while allowing users to remain logged in when following links from external sites. Lax is suitable for most session cookies where user experience matters.
SameSite=None disables SameSite protection entirely, allowing cookies to be sent with all cross-site requests. This is necessary for legitimate third-party contexts like embedded widgets, payment processors, or single sign-on systems that operate across different domains. When using SameSite=None, you must also set the Secure attribute (HTTPS-only) and implement other CSRF protections like tokens, as the cookie provides no CSRF defense.
SameSite Comparison Table:
| Attribute | Cross-Site GET (link click) | Cross-Site POST (form) | Cross-Site AJAX | Iframe Embed | CSRF Protection | Use Case |
|---|---|---|---|---|---|---|
| Strict | ❌ Not sent | ❌ Not sent | ❌ Not sent | ❌ Not sent | ✅ Maximum | Highly sensitive operations |
| Lax (default) | ✅ Sent | ❌ Not sent | ❌ Not sent | ❌ Not sent | ✅ Good | Most session cookies |
| None | ✅ Sent | ✅ Sent | ✅ Sent | ✅ Sent | ❌ None | Third-party integrations |
Code Example:
// Demonstrating all three SameSite values in practice
const express = require('express');
const app = express();
// Strict: Maximum security for authentication
app.post('/login', (req, res) => {
// Authenticate user
const sessionToken = generateSecureToken();
res.cookie('sessionId', sessionToken, {
httpOnly: true,
secure: true,
sameSite: 'strict', // Never sent cross-site
maxAge: 3600000 // 1 hour
});
res.json({ success: true });
});
// Lax: Balance security and usability for general sessions
app.post('/create-account', (req, res) => {
const userId = createUser(req.body);
res.cookie('userId', userId, {
httpOnly: true,
secure: true,
sameSite: 'lax', // Sent with top-level navigation
maxAge: 86400000 // 24 hours
});
res.json({ success: true });
});
// None: Required for cross-origin scenarios
app.get('/third-party-widget', (req, res) => {
const widgetToken = generateWidgetToken();
res.cookie('widgetAuth', widgetToken, {
httpOnly: true,
secure: true, // REQUIRED with SameSite=None
sameSite: 'none', // Sent with all cross-site requests
maxAge: 3600000
});
res.send('<script>initializeWidget();</script>');
});
// Mixed approach: Different cookies for different purposes
app.post('/complete-setup', (req, res) => {
const sessionId = generateSecureToken();
const preferences = req.body.preferences;
// Strict for auth token
res.cookie('authToken', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
// Lax for user preferences (non-sensitive)
res.cookie('userPrefs', JSON.stringify(preferences), {
secure: true,
sameSite: 'lax'
});
// None for analytics (third-party)
res.cookie('analyticsId', generateId(), {
secure: true,
sameSite: 'none'
});
res.json({ success: true });
});
Request Scenarios Comparison:
// Scenario 1: User clicks link from email to bank.com/dashboard
// evil.com: <a href="https://bank.com/dashboard">View Account</a>
// SameSite=Strict: ❌ Cookie NOT sent (user appears logged out)
// SameSite=Lax: âś… Cookie sent (user stays logged in)
// SameSite=None: âś… Cookie sent (user stays logged in)
// Scenario 2: Malicious form on evil.com posts to bank.com/transfer
// evil.com: <form action="https://bank.com/transfer" method="POST">
// SameSite=Strict: ❌ Cookie NOT sent (CSRF blocked)
// SameSite=Lax: ❌ Cookie NOT sent (CSRF blocked)
// SameSite=None: âś… Cookie sent (CSRF possible!)
// Scenario 3: AJAX request from evil.com to bank.com/api
// evil.com: fetch('https://bank.com/api/data', {credentials: 'include'})
// SameSite=Strict: ❌ Cookie NOT sent (blocked)
// SameSite=Lax: ❌ Cookie NOT sent (blocked)
// SameSite=None: âś… Cookie sent (allowed)
// Scenario 4: Embedded iframe from bank.com on trusted-partner.com
// trusted-partner.com: <iframe src="https://bank.com/widget">
// SameSite=Strict: ❌ Cookie NOT sent (widget broken)
// SameSite=Lax: ❌ Cookie NOT sent (widget broken)
// SameSite=None: âś… Cookie sent (widget works)
Decision Tree for Choosing SameSite Value:
flowchart TD
Start[Choose SameSite Value] --> Q1{Does cookie need to work<br/>in third-party context?}
Q1 -->|Yes| Q2{Is this a legitimate<br/>cross-origin use case?}
Q2 -->|Yes| None[Use SameSite=None]
None --> Secure[MUST set Secure=true<br/>+ Implement CSRF tokens]
Q2 -->|No| Stop1[🛑 Don't use None<br/>Redesign the feature]
Q1 -->|No| Q3{Is this an auth/session<br/>cookie for sensitive ops?}
Q3 -->|Yes| Q4{Can users tolerate<br/>appearing logged out<br/>from external links?}
Q4 -->|Yes| Strict[Use SameSite=Strict]
Strict --> Best[âś… Best CSRF protection]
Q4 -->|No| Lax1[Use SameSite=Lax]
Q3 -->|No| Lax2[Use SameSite=Lax]
Lax1 --> Good[âś… Good CSRF protection<br/>+ Better UX]
Lax2 --> Good
style Best fill:#90EE90
style Good fill:#90EE90
style Stop1 fill:#ffcccc
style Secure fill:#FFE4B5
Browser Behavior Examples:
<!-- Test page to demonstrate SameSite behavior -->
<!DOCTYPE html>
<html>
<head>
<title>SameSite Test - evil.com</title>
</head>
<body>
<h1>SameSite Cookie Test Scenarios</h1>
<!-- Scenario 1: Top-level navigation (GET) -->
<h2>1. Click this link (top-level GET navigation)</h2>
<a href="https://bank.com/dashboard">Go to Bank Dashboard</a>
<!-- Strict: ❌ | Lax: ✅ | None: ✅ -->
<!-- Scenario 2: Form submission (POST) -->
<h2>2. Submit this form (cross-site POST)</h2>
<form action="https://bank.com/transfer" method="POST">
<input name="amount" value="1000" />
<button type="submit">Transfer Money</button>
</form>
<!-- Strict: ❌ | Lax: ❌ | None: ✅ -->
<!-- Scenario 3: Image request (GET subresource) -->
<h2>3. Image loaded (cross-site GET subresource)</h2>
<img src="https://bank.com/api/balance.png" />
<!-- Strict: ❌ | Lax: ❌ | None: ✅ -->
<!-- Scenario 4: AJAX request -->
<h2>4. Click to make AJAX request</h2>
<button onclick="makeAjaxRequest()">Fetch Data</button>
<script>
function makeAjaxRequest() {
fetch('https://bank.com/api/data', {
credentials: 'include'
});
}
</script>
<!-- Strict: ❌ | Lax: ❌ | None: ✅ -->
<!-- Scenario 5: Iframe embed -->
<h2>5. Embedded iframe</h2>
<iframe src="https://bank.com/widget"></iframe>
<!-- Strict: ❌ | Lax: ❌ | None: ✅ -->
</body>
</html>
Real-World Configuration Examples:
// E-commerce site with multiple cookie types
const cookieConfig = {
// Shopping cart - needs to persist across external links
cart: {
sameSite: 'lax',
secure: true,
maxAge: 7 * 24 * 3600 // 7 days
},
// Authentication - maximum security
session: {
sameSite: 'strict',
secure: true,
httpOnly: true,
maxAge: 3600 // 1 hour
},
// Analytics - third-party service
analytics: {
sameSite: 'none',
secure: true,
maxAge: 365 * 24 * 3600 // 1 year
},
// CSRF token cookie
csrfToken: {
sameSite: 'strict',
secure: true,
maxAge: 3600
}
};
// Apply configurations
app.use((req, res, next) => {
res.setCookie = (name, value) => {
const config = cookieConfig[name];
if (config) {
res.cookie(name, value, config);
}
};
next();
});
References:
↑ Back to topAPI Security
What is the difference between API authentication methods (API keys, OAuth, JWT)?
The 30-Second Answer: API keys are simple static credentials for service-to-service authentication, OAuth is a delegation protocol for third-party authorization without sharing passwords, and JWT (JSON Web Tokens) are self-contained tokens for stateless authentication. Each suits different use cases: API keys for server-to-server, OAuth for user authorization with third parties, and JWT for scalable session management.
The 2-Minute Answer (If They Want More): API keys are the simplest form of authentication—essentially passwords for applications. They're typically long random strings passed in headers or query parameters. API keys are ideal for server-to-server communication, internal microservices, or public APIs with minimal security requirements. However, they have significant limitations: they don't identify users (only applications), are long-lived and difficult to rotate, provide no granular permissions, and if compromised, give full access until revoked. Examples include Google Maps API keys and Stripe API keys.
OAuth 2.0 is an authorization framework, not strictly an authentication protocol (though often used for both). It enables users to grant third-party applications limited access to their resources without sharing passwords. The user authenticates with the resource owner (like Google or Facebook), which then issues an access token to the third-party app. OAuth is perfect for scenarios like "Login with Google" or allowing a scheduling app to access your calendar. It supports multiple flows (authorization code, client credentials, implicit, password) for different scenarios, provides scoped permissions, and includes refresh tokens for long-term access. OAuth is complex to implement but extremely powerful for user-delegated access.
JWT (JSON Web Tokens) are self-contained, digitally signed tokens that carry claims about a user or session. Unlike session IDs that require server-side storage, JWTs contain all necessary information (user ID, roles, expiration) and can be verified using cryptographic signatures. They're excellent for stateless authentication in distributed systems, mobile apps, and microservices. JWTs enable horizontal scaling without shared session storage, reduce database lookups, and work well across different domains. However, they're harder to revoke (since they're self-contained), can become large if they contain too much data, and require careful handling to prevent common vulnerabilities like algorithm confusion or missing expiration checks.
The choice depends on your use case: use API keys for simple service authentication, OAuth when you need user authorization with third-party apps, and JWT for scalable, stateless authentication in modern distributed applications. Many systems combine these—for example, using OAuth to authenticate users and issuing JWTs as the access tokens.
Code Example:
// 1. API KEY AUTHENTICATION
// Server-side implementation
const API_KEYS = new Map([
['pk_test_123abc', { client: 'MobileApp', permissions: ['read'] }],
['sk_live_456def', { client: 'WebApp', permissions: ['read', 'write'] }]
]);
app.use('/api', (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || !API_KEYS.has(apiKey)) {
return res.status(401).json({ error: 'Invalid API key' });
}
req.client = API_KEYS.get(apiKey);
next();
});
// Client usage
fetch('https://api.example.com/data', {
headers: {
'X-API-Key': 'pk_test_123abc'
}
});
// 2. OAUTH 2.0 AUTHENTICATION
// Authorization Code Flow (most secure for web apps)
const express = require('express');
const axios = require('axios');
// Step 1: Redirect user to OAuth provider
app.get('/auth/google', (req, res) => {
const authUrl = 'https://accounts.google.com/o/oauth2/v2/auth?' +
'client_id=YOUR_CLIENT_ID&' +
'redirect_uri=http://localhost:3000/auth/callback&' +
'response_type=code&' +
'scope=openid email profile&' +
'state=random_state_string';
res.redirect(authUrl);
});
// Step 2: Handle callback with authorization code
app.get('/auth/callback', async (req, res) => {
const { code, state } = req.query;
// Exchange code for access token
const tokenResponse = await axios.post(
'https://oauth2.googleapis.com/token',
{
code,
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
redirect_uri: 'http://localhost:3000/auth/callback',
grant_type: 'authorization_code'
}
);
const { access_token, refresh_token, expires_in } = tokenResponse.data;
// Use access token to get user info
const userResponse = await axios.get(
'https://www.googleapis.com/oauth2/v2/userinfo',
{
headers: { Authorization: `Bearer ${access_token}` }
}
);
// Store tokens and user info in session
req.session.user = userResponse.data;
req.session.accessToken = access_token;
req.session.refreshToken = refresh_token;
res.redirect('/dashboard');
});
// Use access token for API calls
app.get('/api/calendar', async (req, res) => {
const response = await axios.get(
'https://www.googleapis.com/calendar/v3/calendars/primary/events',
{
headers: {
Authorization: `Bearer ${req.session.accessToken}`
}
}
);
res.json(response.data);
});
// 3. JWT AUTHENTICATION
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// Login endpoint - issue JWT
app.post('/auth/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Create access token (short-lived)
const accessToken = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
// Create refresh token (long-lived)
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
// Store refresh token in database
await user.update({ refreshToken });
res.json({
accessToken,
refreshToken,
expiresIn: 900
});
});
// JWT middleware
function authenticateJWT(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(403).json({ error: 'Invalid token' });
}
}
// Protected route using JWT
app.get('/api/profile', authenticateJWT, async (req, res) => {
const user = await User.findById(req.user.userId);
res.json(user);
});
// Refresh token endpoint
app.post('/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: 'Refresh token required' });
}
try {
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
const user = await User.findById(decoded.userId);
if (!user || user.refreshToken !== refreshToken) {
return res.status(403).json({ error: 'Invalid refresh token' });
}
// Issue new access token
const accessToken = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
res.json({ accessToken, expiresIn: 900 });
} catch (error) {
res.status(403).json({ error: 'Invalid refresh token' });
}
});
Comparison Table:
| Feature | API Keys | OAuth 2.0 | JWT |
|---|---|---|---|
| Complexity | Simple | Complex | Moderate |
| Use Case | Service-to-service | Third-party authorization | Stateless sessions |
| User Identity | No (app only) | Yes | Yes |
| Granular Permissions | Limited | Yes (scopes) | Yes (claims) |
| Expiration | Manual rotation | Yes (access + refresh) | Yes (built-in) |
| Revocation | Easy | Moderate | Difficult |
| Scalability | Good | Good | Excellent |
| Server State | Optional | Required (tokens) | None (stateless) |
| Security | Moderate | High | High (if implemented correctly) |
| Standards | None | RFC 6749 | RFC 7519 |
| Best For | Internal APIs, public APIs | User authorization, SSO | Microservices, mobile apps |
Mermaid Diagram (OAuth 2.0 Authorization Code Flow):
sequenceDiagram
participant User
participant Client as Client App
participant AuthServer as Authorization Server
participant ResourceServer as Resource Server
User->>Client: 1. Access protected resource
Client->>AuthServer: 2. Redirect to authorization endpoint
AuthServer->>User: 3. Show login/consent page
User->>AuthServer: 4. Authenticate & grant permission
AuthServer->>Client: 5. Redirect with authorization code
Client->>AuthServer: 6. Exchange code for access token<br/>(+ client credentials)
AuthServer->>Client: 7. Return access token + refresh token
Client->>ResourceServer: 8. Request resource with access token
ResourceServer->>ResourceServer: 9. Validate token
ResourceServer->>Client: 10. Return protected resource
Client->>User: 11. Display resource
Note over Client,AuthServer: Token expires
Client->>AuthServer: 12. Refresh access token
AuthServer->>Client: 13. New access token
References:
↑ Back to topSecurity Testing and Tools
What is the difference between SAST, DAST, and IAST?
The 30-Second Answer: SAST (Static Application Security Testing) analyzes source code without execution, DAST (Dynamic Application Security Testing) tests running applications from the outside, and IAST (Interactive Application Security Testing) combines both by analyzing code behavior during runtime. SAST finds vulnerabilities early in development, DAST simulates real attacks, and IAST provides real-time feedback during testing.
The 2-Minute Answer (If They Want More): Static Application Security Testing (SAST) is a white-box testing approach that examines source code, bytecode, or binaries to identify security vulnerabilities without executing the program. SAST tools scan code during development to catch issues like SQL injection points, hardcoded credentials, and buffer overflows early in the SDLC. The main advantage is early detection, but SAST can produce false positives and cannot detect runtime or configuration issues.
Dynamic Application Security Testing (DAST) is a black-box testing method that analyzes running applications by sending requests and examining responses, simulating how an attacker would interact with the system. DAST tools test for vulnerabilities like XSS, CSRF, and authentication flaws in deployed environments. While DAST produces fewer false positives and tests real-world configurations, it requires a running application and cannot identify the exact location of vulnerabilities in source code.
Interactive Application Security Testing (IAST) instruments the application during runtime, combining elements of both SAST and DAST. IAST agents monitor application behavior during functional or security testing, tracking data flow and identifying vulnerabilities with precise line-of-code accuracy. This approach provides comprehensive coverage with low false positives but requires application modification and can impact performance.
Comparison Table:
| Feature | SAST | DAST | IAST |
|---|---|---|---|
| Testing Method | White-box (code analysis) | Black-box (external testing) | Gray-box (instrumented runtime) |
| When to Test | During development | After deployment/staging | During QA/testing phase |
| Code Access | Requires source code | No code access needed | Requires instrumentation |
| Accuracy | High false positives | Low false positives | Very low false positives |
| Coverage | All code paths | Only accessible paths | Executed code paths |
| Performance Impact | None (offline) | Minimal | Moderate (instrumentation) |
| Vulnerability Location | Exact line of code | General area/endpoint | Exact line of code |
| Best For | Early detection | Production-like testing | Comprehensive analysis |
Code Example:
# CI/CD Pipeline with SAST, DAST, and IAST
name: Security Testing Pipeline
on: [push, pull_request]
jobs:
sast-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run SAST with Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
- name: Run SonarQube SAST
run: |
sonar-scanner \
-Dsonar.projectKey=${{ github.repository }} \
-Dsonar.sources=src \
-Dsonar.host.url=${{ secrets.SONAR_HOST_URL }} \
-Dsonar.login=${{ secrets.SONAR_TOKEN }}
build-and-test:
needs: sast-scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build application
run: |
npm install
npm run build
- name: Run tests with IAST agent
run: |
# Contrast Security IAST agent
java -javaagent:contrast.jar \
-Dcontrast.api.key=${{ secrets.CONTRAST_API_KEY }} \
-jar target/app.jar
npm test
dast-scan:
needs: build-and-test
runs-on: ubuntu-latest
steps:
- name: Deploy to staging
run: |
kubectl apply -f k8s/staging/
kubectl wait --for=condition=ready pod -l app=myapp
- name: Run OWASP ZAP DAST
uses: zaproxy/action-baseline@v0.7.0
with:
target: 'https://staging.example.com'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a'
- name: Run Burp Suite DAST
run: |
docker run --rm \
-e BURP_API_KEY=${{ secrets.BURP_API_KEY }} \
portswigger/burp-scanner \
--scan-url https://staging.example.com
Mermaid Diagram:
flowchart LR
subgraph Development
A[Write Code] --> B[SAST Scan]
B --> C{Vulnerabilities?}
C -->|Yes| A
C -->|No| D[Commit Code]
end
subgraph Testing
D --> E[Build & Deploy to Test]
E --> F[IAST Agent Instrumentation]
F --> G[Run Functional Tests]
G --> H[IAST Analysis]
H --> I{Vulnerabilities?}
I -->|Yes| A
I -->|No| J[Deploy to Staging]
end
subgraph Staging
J --> K[DAST Scan]
K --> L{Vulnerabilities?}
L -->|Yes| A
L -->|No| M[Deploy to Production]
end
style B fill:#ff9999
style F fill:#99ccff
style K fill:#99ff99
References:
↑ Back to topSecure Development Practices
What is DevSecOps and how does it integrate security into DevOps?
The 30-Second Answer: DevSecOps integrates security practices into every phase of the DevOps lifecycle, making security a shared responsibility across development, security, and operations teams. It automates security testing and embeds security controls directly into CI/CD pipelines, enabling teams to identify and fix vulnerabilities early rather than treating security as a final gate before deployment.
The 2-Minute Answer (If They Want More): DevSecOps represents a cultural and technical shift where security is "shifted left" - integrated from the earliest stages of software development rather than added as an afterthought. This approach combines automated security testing tools, continuous monitoring, and collaborative practices to ensure that security considerations are embedded throughout the software development lifecycle.
In practice, DevSecOps involves integrating security tools directly into CI/CD pipelines, including static application security testing (SAST), dynamic application security testing (DAST), software composition analysis (SCA) for dependency vulnerabilities, container security scanning, and infrastructure-as-code security validation. These automated checks run alongside traditional unit and integration tests, providing immediate feedback to developers when security issues are introduced.
The cultural aspect is equally important - DevSecOps breaks down traditional silos between development, security, and operations teams. Security teams provide tools, guidance, and guardrails rather than acting as gatekeepers. Developers are empowered with security training and automated tools to find and fix issues themselves. Operations teams ensure secure configurations and monitor for security events in production.
This approach significantly reduces the cost and time to remediate security vulnerabilities. Finding a security flaw during development costs far less than discovering it in production. DevSecOps also improves compliance posture by providing continuous evidence of security controls and making it easier to demonstrate that security requirements are consistently met throughout the development process.
DevSecOps Pipeline Diagram:
flowchart LR
A[Plan] --> B[Code]
B --> C[Build]
C --> D[Test]
D --> E[Release]
E --> F[Deploy]
F --> G[Operate]
G --> H[Monitor]
H --> A
B -.->|SAST<br/>Secret Scanning<br/>Linting| B1[Security Tools]
C -.->|SCA<br/>License Check<br/>Container Scan| C1[Security Tools]
D -.->|DAST<br/>Penetration Test<br/>Compliance| D1[Security Tools]
F -.->|IaC Security<br/>Config Validation<br/>Policy Check| F1[Security Tools]
G -.->|Runtime Security<br/>WAF<br/>Monitoring| G1[Security Tools]
H -.->|SIEM<br/>Threat Detection<br/>Incident Response| H1[Security Tools]
style B1 fill:#ff6b6b
style C1 fill:#ff6b6b
style D1 fill:#ff6b6b
style F1 fill:#ff6b6b
style G1 fill:#ff6b6b
style H1 fill:#ff6b6b
References:
↑ Back to topOWASP Fundamentals
What is the OWASP Application Security Verification Standard (ASVS)?
The 30-Second Answer: The OWASP ASVS is a comprehensive framework that defines three levels of security requirements for web applications, providing specific, testable security controls that go far beyond the OWASP Top 10. It serves as a blueprint for developers to build secure applications and for security professionals to verify application security through architecture reviews, code reviews, and penetration testing.
The 2-Minute Answer (If They Want More): The OWASP Application Security Verification Standard is a structured catalog of security requirements organized into 14 chapters covering different aspects of application security, including architecture, authentication, session management, access control, cryptography, error handling, data protection, communications, malicious code prevention, business logic, file handling, API security, configuration, and WebSocket security. Unlike the OWASP Top 10, which focuses on awareness of common risks, ASVS provides actionable requirements that can be directly implemented and verified.
ASVS defines three progressive security verification levels. Level 1 represents basic security suitable for all applications and can be verified through automated testing and basic manual verification. Level 2 contains requirements for applications handling sensitive data and requires more thorough testing including architecture and code review. Level 3 represents the most critical applications, such as those performing high-value transactions or containing sensitive medical data, requiring comprehensive security review including design documentation, threat modeling, and extensive testing.
The standard is designed to be used by multiple stakeholders in the software development lifecycle. Developers use ASVS as a checklist of security requirements to implement. Architects use it during design phase to ensure security is baked into the application architecture. Security testers use it as a comprehensive verification checklist. Organizations can specify ASVS compliance levels in procurement contracts to ensure vendors deliver secure software. For example, a contract might require "ASVS Level 2 compliance for all authentication and session management requirements."
Each requirement in ASVS is tagged with specific identifiers that map to other security standards like NIST 800-53, PCI DSS, and various compliance frameworks. This mapping makes ASVS particularly valuable for organizations that need to demonstrate compliance with multiple standards simultaneously. The requirements are written to be technology-agnostic where possible, but include specific guidance for common technologies and frameworks.
ASVS has become the foundation for several other security initiatives and tools. The Mobile Application Security Verification Standard (MASVS) was modeled after ASVS, and numerous security testing tools reference ASVS requirements in their reporting. Organizations can customize ASVS by selecting specific chapters or requirements relevant to their applications while maintaining a standardized approach to security verification.
Mermaid Diagram:
flowchart TD
A[OWASP ASVS] --> B[Level 1: Opportunistic]
A --> C[Level 2: Standard]
A --> D[Level 3: Advanced]
B --> E[Automated Testing]
B --> F[Basic Manual Verification]
C --> G[Architecture Review]
C --> H[Code Review]
C --> I[Comprehensive Testing]
D --> J[Threat Modeling]
D --> K[Design Documentation]
D --> L[Extensive Manual Testing]
A --> M[14 Security Chapters]
M --> N[Architecture]
M --> O[Authentication]
M --> P[Session Management]
M --> Q[Access Control]
M --> R[And 10 more...]
A --> S[Used By]
S --> T[Developers: Requirements]
S --> U[Architects: Design]
S --> V[Testers: Verification]
S --> W[Organizations: Contracts]
References:
↑ Back to topA04:2021 - Insecure Design
What is security by design and defense in depth?
The 30-Second Answer: Security by Design means building security into every stage of development from the start, not adding it as an afterthought. Defense in Depth is a layered security strategy where multiple independent security controls protect a system, so if one fails, others still provide protection.
The 2-Minute Answer (If They Want More): Security by Design is a philosophy that treats security as a core requirement throughout the entire software development lifecycle. Instead of bolting security features onto a completed product, security considerations inform architectural decisions, design patterns, technology choices, and coding practices from day one. This includes practices like secure defaults, fail-safe mechanisms, least privilege, and complete mediation.
Defense in Depth, also known as layered security, recognizes that no single security control is perfect. Instead, multiple overlapping layers of security controls create redundancy. If an attacker breaches one layer, they still face additional barriers. This approach is borrowed from military strategy where multiple defensive positions protect against breakthrough.
A practical example combines both concepts: Consider a web application protecting user data. Security by Design means: choosing a framework with built-in XSS protection, designing an authentication system from the start, and planning for secure session management. Defense in Depth adds layers: (1) Network firewall filtering malicious traffic; (2) Web Application Firewall (WAF) blocking common attacks; (3) Application-level input validation; (4) Parameterized queries preventing SQL injection; (5) Encrypted data at rest; (6) Access control lists limiting data exposure; (7) Monitoring and intrusion detection systems.
Together, these principles ensure security is foundational (Security by Design) and redundant (Defense in Depth), significantly reducing the attack surface and blast radius of potential breaches.
Mermaid Diagram:
flowchart TD
subgraph "Defense in Depth Layers"
A[Perimeter - Firewall/IDS]
B[Network - Segmentation/VPN]
C[Host - Antivirus/Hardening]
D[Application - WAF/Input Validation]
E[Data - Encryption/Access Control]
F[Physical - Datacenter Security]
G[Policies - Training/Procedures]
end
H[Attacker] --> A
A -->|Layer 1 Bypassed| B
B -->|Layer 2 Bypassed| C
C -->|Layer 3 Bypassed| D
D -->|Layer 4 Bypassed| E
E -->|Multiple Layers Protect| I[Protected Asset]
style I fill:#90EE90
style H fill:#FFB6C6
References:
↑ Back to topA05:2021 - Security Misconfiguration
What is Security Misconfiguration?
The 30-Second Answer: Security Misconfiguration occurs when security settings are not defined, implemented, or maintained properly. This includes missing security patches, unnecessary features enabled, default accounts unchanged, overly verbose error messages, or misconfigured HTTP headers. It's the most common vulnerability because it can happen at any level of the application stack.
The 2-Minute Answer (If They Want More): Security Misconfiguration represents a broad category of vulnerabilities that arise from improper or incomplete security settings across the entire application stack - from the network and platform to the application and database layers. Unlike other vulnerabilities that exploit specific coding flaws, misconfigurations exploit gaps in security hardening and maintenance.
Common examples include leaving default credentials in place, enabling unnecessary features or services, failing to apply security patches, displaying overly detailed error messages that reveal system information, and not configuring security headers properly. These issues are particularly dangerous because they're often easy to exploit and can affect multiple components simultaneously.
The prevalence of security misconfiguration has increased with the complexity of modern application architectures. Cloud services, containers, microservices, and infrastructure-as-code introduce new configuration surfaces that require careful management. A single misconfigured S3 bucket or improperly secured API endpoint can expose sensitive data to attackers.
Prevention requires a repeatable hardening process, automated security configuration verification, minimal platform installations (removing unused features), regular security updates, and a segmented architecture with proper access controls between components. DevSecOps practices that integrate security into the deployment pipeline are essential for maintaining consistent configurations across environments.
Code Example:
// Example: Secure Express.js configuration
const express = require('express');
const helmet = require('helmet');
const app = express();
// Apply security headers using Helmet
app.use(helmet());
// Disable unnecessary features
app.disable('x-powered-by');
// Environment-specific error handling
if (process.env.NODE_ENV === 'production') {
// Generic error handler for production
app.use((err, req, res, next) => {
console.error(err.stack); // Log internally
res.status(500).json({ error: 'Internal Server Error' });
});
} else {
// Detailed errors for development only
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message, stack: err.stack });
});
}
References:
- OWASP Top 10 2021 - A05 Security Misconfiguration
- CIS Benchmarks - Security Configuration Guidelines
A09:2021 - Security Logging and Monitoring Failures
What is a Security Information and Event Management (SIEM) system?
The 30-Second Answer: A SIEM (Security Information and Event Management) system is a centralized platform that collects, aggregates, analyzes, and correlates security logs and events from across your infrastructure in real-time. It provides threat detection, incident response capabilities, compliance reporting, and forensic analysis by identifying patterns and anomalies that indicate security incidents.
The 2-Minute Answer (If They Want More): SIEM systems combine two core functions: Security Information Management (SIM) for log collection and long-term storage, and Security Event Management (SEM) for real-time monitoring and analysis. Modern SIEM solutions aggregate data from firewalls, intrusion detection systems, applications, databases, servers, and endpoints into a unified view.
The real power of SIEM lies in its correlation engine, which applies rules and machine learning algorithms to identify suspicious patterns across disparate data sources. For example, a SIEM might correlate multiple failed login attempts from different IPs with a successful login from an unusual location, triggering an alert for potential credential compromise. This correlation capability enables detection of sophisticated multi-stage attacks that would be invisible when viewing individual log sources in isolation.
SIEM systems provide critical capabilities including real-time alerting for security events, automated incident response workflows, compliance reporting for regulations like PCI DSS and GDPR, threat intelligence integration, and forensic investigation tools. They typically offer customizable dashboards, visualization tools, and reporting features that help security teams understand their security posture and respond effectively to threats.
Popular SIEM solutions include Splunk, IBM QRadar, Elastic Security (ELK Stack), Microsoft Sentinel, and open-source options like Wazuh. Implementation requires careful planning for log source integration, retention policies, correlation rule development, and tuning to minimize false positives while ensuring critical threats are detected.
Code Example:
// Example SIEM integration using Splunk HTTP Event Collector (HEC)
const axios = require('axios');
const winston = require('winston');
const SplunkStreamEvent = require('winston-splunk-httplogger');
// Configure Splunk transport for Winston
const splunkSettings = {
token: process.env.SPLUNK_HEC_TOKEN,
host: process.env.SPLUNK_HOST || 'splunk.example.com',
port: 8088,
protocol: 'https',
path: '/services/collector/event',
maxRetries: 3,
source: 'application-security-events',
sourcetype: 'json',
index: 'security'
};
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new SplunkStreamEvent({ splunk: splunkSettings })
]
});
// Security event sender to SIEM
class SIEMEventSender {
constructor(siemConfig) {
this.config = siemConfig;
this.endpoint = `${siemConfig.protocol}://${siemConfig.host}:${siemConfig.port}${siemConfig.path}`;
}
async sendEvent(eventData) {
const payload = {
time: Math.floor(Date.now() / 1000),
source: this.config.source,
sourcetype: this.config.sourcetype,
index: this.config.index,
event: eventData
};
try {
await axios.post(this.endpoint, payload, {
headers: {
'Authorization': `Splunk ${this.config.token}`,
'Content-Type': 'application/json'
},
timeout: 5000
});
} catch (error) {
console.error('Failed to send event to SIEM:', error.message);
// Fallback to local logging
logger.error('SIEM_SEND_FAILURE', { originalEvent: eventData, error: error.message });
}
}
// Send structured security event
async logSecurityEvent(eventType, severity, details) {
const event = {
eventType,
severity,
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV || 'production',
application: 'web-application',
...details
};
await this.sendEvent(event);
}
}
const siemSender = new SIEMEventSender(splunkSettings);
// Example: Authentication monitoring
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
const sourceIP = req.ip;
const userAgent = req.get('user-agent');
try {
const user = await authenticateUser(username, password);
if (user) {
// Send successful authentication to SIEM
await siemSender.logSecurityEvent('AUTHENTICATION', 'INFO', {
action: 'LOGIN_SUCCESS',
username,
userId: user.id,
sourceIP,
userAgent,
sessionId: req.sessionID
});
res.json({ success: true, token: generateToken(user) });
} else {
// Send failed authentication to SIEM
await siemSender.logSecurityEvent('AUTHENTICATION', 'WARNING', {
action: 'LOGIN_FAILURE',
username,
sourceIP,
userAgent,
reason: 'INVALID_CREDENTIALS'
});
res.status(401).json({ error: 'Invalid credentials' });
}
} catch (error) {
await siemSender.logSecurityEvent('AUTHENTICATION', 'ERROR', {
action: 'LOGIN_ERROR',
username,
sourceIP,
error: error.message
});
res.status(500).json({ error: 'Authentication error' });
}
});
// Example: Suspicious activity detection
app.use(async (req, res, next) => {
const suspiciousPatterns = {
sqlInjection: /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION)\b.*\b(FROM|WHERE)\b)/i,
xss: /<script[^>]*>.*?<\/script>/i,
pathTraversal: /\.\.[\/\\]/,
commandInjection: /[;&|`$()]/
};
const checkInput = (value) => {
for (const [type, pattern] of Object.entries(suspiciousPatterns)) {
if (pattern.test(value)) {
return type;
}
}
return null;
};
// Check query parameters and body
const allInputs = { ...req.query, ...req.body };
for (const [key, value] of Object.entries(allInputs)) {
if (typeof value === 'string') {
const threat = checkInput(value);
if (threat) {
await siemSender.logSecurityEvent('THREAT_DETECTION', 'CRITICAL', {
action: 'ATTACK_ATTEMPT',
threatType: threat.toUpperCase(),
parameter: key,
value: value.substring(0, 200), // Truncate for logging
sourceIP: req.ip,
userAgent: req.get('user-agent'),
url: req.originalUrl,
userId: req.user?.id || 'anonymous'
});
// Block the request
return res.status(400).json({ error: 'Invalid request' });
}
}
}
next();
});
// Example: Correlation-worthy event (privilege escalation attempt)
app.post('/api/user/:id/role', authenticate, async (req, res) => {
const targetUserId = req.params.id;
const { newRole } = req.body;
const currentUser = req.user;
// Log privilege change attempt for SIEM correlation
await siemSender.logSecurityEvent('AUTHORIZATION', 'HIGH', {
action: 'PRIVILEGE_CHANGE_ATTEMPT',
actor: {
userId: currentUser.id,
username: currentUser.username,
currentRole: currentUser.role
},
target: {
userId: targetUserId,
requestedRole: newRole
},
sourceIP: req.ip,
sessionId: req.sessionID,
// Add context for SIEM correlation
correlationId: `priv-change-${Date.now()}`,
requiresReview: true
});
// Implement role change logic with additional authorization checks
});
Mermaid Diagram:
flowchart TD
A[Log Sources] --> B[SIEM Platform]
A1[Firewalls] --> A
A2[IDS/IPS] --> A
A3[Applications] --> A
A4[Databases] --> A
A5[Servers] --> A
A6[Endpoints] --> A
A7[Cloud Services] --> A
B --> C[Data Collection Layer]
C --> D[Normalization & Parsing]
D --> E[Correlation Engine]
E --> F[Rule-Based Detection]
E --> G[ML/Behavioral Analysis]
E --> H[Threat Intelligence]
F --> I[Alert Generation]
G --> I
H --> I
I --> J{Severity Assessment}
J -->|Critical/High| K[Automated Response]
J -->|Critical/High| L[SOC Notification]
J -->|Medium/Low| M[Alert Queue]
K --> K1[Block IP]
K --> K2[Quarantine Account]
K --> K3[Isolate System]
B --> N[Storage & Retention]
N --> O[Compliance Reporting]
N --> P[Forensic Analysis]
I --> Q[Dashboard & Visualization]
style B fill:#f9f,stroke:#333,stroke-width:4px
style E fill:#bbf,stroke:#333,stroke-width:2px
style I fill:#fbb,stroke:#333,stroke-width:2px
References:
↑ Back to topA06:2021 - Vulnerable and Outdated Components
What is Software Composition Analysis (SCA)?
The 30-Second Answer: Software Composition Analysis (SCA) is the automated process of identifying and analyzing open-source and third-party components in your codebase to detect security vulnerabilities, license compliance issues, and code quality risks. SCA tools create a Software Bill of Materials (SBOM) and continuously monitor components against vulnerability databases.
The 2-Minute Answer (If They Want More): SCA is a critical security practice that addresses the reality that modern applications are typically 70-90% composed of third-party code through dependencies, libraries, and frameworks. SCA tools perform deep analysis of your codebase to discover not just direct dependencies listed in manifest files, but also transitive dependencies (dependencies of your dependencies) that can introduce hidden vulnerabilities.
Modern SCA solutions go beyond simple vulnerability detection. They analyze license obligations to ensure compliance with open-source licenses (preventing use of GPL code in proprietary software, for example), detect outdated components even without known CVEs, identify components that are no longer maintained, and assess code quality metrics of dependencies. Advanced SCA tools can also detect malicious packages, typosquatting attempts, and supply chain attacks.
The analysis typically integrates at multiple points: IDE plugins that alert developers in real-time, Git hooks that prevent commits with critical issues, CI/CD pipeline stages that fail builds based on policy violations, and runtime monitoring that detects vulnerabilities in production. SCA tools maintain their own vulnerability databases, often combining data from NVD, GitHub Security Advisories, proprietary research, and community contributions.
A key output of SCA is the Software Bill of Materials (SBOM), a comprehensive inventory of all components used in an application. SBOMs are increasingly required by regulations like the US Executive Order on Cybersecurity and are critical for incident response—when a new vulnerability like Log4Shell is disclosed, teams can quickly determine if they're affected by consulting their SBOM.
Key SCA Capabilities:
### Core SCA Functions:
1. **Dependency Discovery**
- Direct and transitive dependency mapping
- Detection of shadow dependencies
- Component fingerprinting
2. **Vulnerability Detection**
- CVE database matching
- Proprietary vulnerability research
- Zero-day threat intelligence
- Exploit maturity assessment
3. **License Compliance**
- License identification and classification
- Conflict detection (incompatible licenses)
- Policy enforcement (approved/denied licenses)
- Attribution report generation
4. **Risk Assessment**
- Component health scoring
- Maintenance status tracking
- Community activity metrics
- Supply chain risk analysis
5. **Remediation Guidance**
- Upgrade recommendations
- Patch availability
- Alternative component suggestions
- Fix PR generation
SCA Integration Example:
# GitHub Actions workflow with SCA
name: Software Composition Analysis
on: [push, pull_request]
jobs:
sca-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Snyk SCA
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high --fail-on=all
- name: Generate SBOM
run: |
npx @cyclonedx/cyclonedx-npm --output-file sbom.json
- name: Upload SBOM
uses: actions/upload-artifact@v3
with:
name: sbom
path: sbom.json
References:
- NIST Software Bill of Materials (SBOM)
- OWASP Software Component Verification Standard
- Synopsys - What is Software Composition Analysis
A07:2021 - Identification and Authentication Failures
What is multi-factor authentication (MFA) and why is it important?
The 30-Second Answer: Multi-factor authentication (MFA) is a security mechanism that requires users to provide two or more independent verification factors to access a resource, combining something they know (password), something they have (phone/token), or something they are (biometrics). It's critical because even if passwords are compromised, attackers still cannot access accounts without the additional factors.
The 2-Minute Answer (If They Want More): MFA significantly strengthens security by requiring multiple independent credentials from different categories. The three main factor categories are: knowledge factors (passwords, PINs, security questions), possession factors (smartphones, hardware tokens, smart cards), and inherence factors (fingerprints, facial recognition, voice patterns). Combining factors from different categories creates layers of defense that are exponentially harder to breach.
The importance of MFA has grown dramatically as password-based attacks have become more sophisticated. Data breaches expose billions of credentials, credential stuffing attacks are automated and widespread, phishing attacks successfully steal passwords daily, and keyloggers can capture typed credentials. Even strong passwords are vulnerable to these threats, but MFA adds a barrier that's significantly harder to overcome.
Common MFA implementations include time-based one-time passwords (TOTP) using apps like Google Authenticator or Authy, SMS-based codes (though less secure due to SIM swapping attacks), push notifications to mobile devices, hardware security keys like YubiKey or Titan, and biometric authentication combined with passwords. Each method has different security and usability trade-offs.
Industry statistics consistently show MFA effectiveness: Microsoft reports that MFA blocks 99.9% of automated attacks, making it one of the most effective security controls available. For web applications, implementing MFA should be mandatory for administrative accounts, highly recommended for all user accounts, and essential for applications handling sensitive data or financial transactions. Modern frameworks and identity providers make MFA implementation straightforward, removing previous barriers to adoption.
Code Example:
// Implementing TOTP-based MFA with speakeasy and qrcode
const speakeasy = require('speakeasy');
const qrcode = require('qrcode');
// Generate MFA secret for user during setup
async function setupMFA(userId) {
// Generate secret
const secret = speakeasy.generateSecret({
name: `MyApp (${userId})`,
issuer: 'MyApp'
});
// Store secret.base32 in database for user
await saveUserMFASecret(userId, secret.base32);
// Generate QR code for user to scan with authenticator app
const qrCodeUrl = await qrcode.toDataURL(secret.otpauth_url);
return {
secret: secret.base32,
qrCode: qrCodeUrl
};
}
// Verify MFA token during login
function verifyMFAToken(userSecret, token) {
return speakeasy.totp.verify({
secret: userSecret,
encoding: 'base32',
token: token,
window: 2 // Allow 2 time steps before/after for clock drift
});
}
// Login flow with MFA
async function loginWithMFA(username, password, mfaToken) {
// Step 1: Verify password
const user = await authenticateUser(username, password);
if (!user) {
throw new Error('Invalid credentials');
}
// Step 2: Check if MFA is enabled
if (user.mfaEnabled) {
// Verify MFA token
const isValidToken = verifyMFAToken(user.mfaSecret, mfaToken);
if (!isValidToken) {
throw new Error('Invalid MFA token');
}
}
// Step 3: Create session after successful authentication
const session = await createSession(user.id);
return session;
}
// Express.js middleware to require MFA for sensitive operations
function requireMFA(req, res, next) {
if (!req.session.mfaVerified) {
return res.status(403).json({
error: 'MFA verification required',
requiresMFA: true
});
}
next();
}
Mermaid Diagram:
sequenceDiagram
participant U as User
participant A as Application
participant DB as Database
participant MFA as MFA Provider
Note over U,MFA: MFA Setup Phase
U->>A: Request MFA Setup
A->>MFA: Generate Secret
MFA-->>A: Return Secret & QR Code
A->>DB: Store Secret
A-->>U: Display QR Code
U->>U: Scan with Authenticator App
Note over U,MFA: Login Phase
U->>A: Username + Password
A->>DB: Verify Credentials
DB-->>A: Valid User
A-->>U: Request MFA Token
U->>U: Generate Token (TOTP)
U->>A: Submit Token
A->>DB: Retrieve User Secret
A->>A: Verify Token with Secret
A-->>U: Grant Access + Session
References:
- NIST Special Publication 800-63B - Digital Identity Guidelines
- OWASP Multi-Factor Authentication Cheat Sheet
A08:2021 - Software and Data Integrity Failures
What is a supply chain attack and how do you prevent it?
The 30-Second Answer: A supply chain attack compromises software by inserting malicious code into trusted third-party components, libraries, or dependencies. Prevention requires dependency scanning, using lock files, verifying package integrity with checksums, implementing Software Bill of Materials (SBOM), and monitoring for known vulnerabilities in your dependency tree.
The 2-Minute Answer (If They Want More): Supply chain attacks target the software development and distribution chain rather than attacking the application directly. Attackers compromise trusted packages, libraries, or build tools that developers incorporate into their applications. Notable examples include the 2020 SolarWinds Orion attack (affecting 18,000+ organizations), the 2021 Codecov breach, and numerous npm package compromises like the event-stream and ua-parser-js incidents.
These attacks are particularly dangerous because: (1) they exploit trust relationships between developers and package ecosystems, (2) a single compromised package can affect thousands of downstream applications, (3) malicious code often remains undetected for months, and (4) they can bypass traditional security controls since the malicious code comes from "trusted" sources.
Prevention strategies involve multiple layers: First, use dependency lock files (package-lock.json, yarn.lock, Gemfile.lock) to ensure reproducible builds and prevent unexpected updates. Second, implement automated dependency scanning tools like Snyk, Dependabot, or npm audit to identify known vulnerabilities. Third, verify package integrity using checksums and signatures (npm uses SHA-512 hashes). Fourth, maintain a Software Bill of Materials (SBOM) documenting all components and their versions.
Additional measures include: minimizing dependencies (fewer packages = smaller attack surface), reviewing dependency updates before applying them, using private package registries for internal code, implementing Subresource Integrity for CDN-loaded resources, and monitoring for typosquatting attacks. For critical applications, consider vendoring dependencies or using tools like Socket.dev for real-time supply chain protection.
Code Example:
// package.json - Use exact versions, not ranges
{
"dependencies": {
"express": "4.18.2", // âś“ Exact version
"lodash": "^4.17.21" // âś— Allows minor/patch updates
}
}
// .npmrc - Configure package integrity checking
audit=true
audit-level=moderate
package-lock=true
// Verify package integrity before installation
npm audit
npm audit fix
// Generate SBOM (Software Bill of Materials)
npm sbom --sbom-format=cyclonedx
// Check for vulnerabilities with Snyk
npx snyk test
// Verify package checksums
npm install --integrity
// Use npm ci instead of npm install in CI/CD
npm ci // Installs from lock file, fails if mismatch
Mermaid Diagram:
flowchart LR
A[Developer] -->|Installs| B[Package Manager]
B -->|Downloads| C[Public Registry]
D[Attacker] -.->|Compromises| C
D -.->|Publishes malicious| E[Typosquatted Package]
C -->|Contains| F[Legitimate Package]
C -->|Contains| E
B -->|Without verification| G[Application Build]
G -->|Deployed to| H[Production]
I[Prevention Layer] -->|Lock files| B
I -->|Integrity checks| B
I -->|Vulnerability scanning| B
I -->|SBOM tracking| G
style D fill:#ff6b6b
style E fill:#ff6b6b
style I fill:#6bcf7f
style H fill:#ffd93d
References:
- OWASP Software Component Verification Standard (SCVS)
- CISA Software Bill of Materials (SBOM)
- npm Security Best Practices
A01:2021 - Broken Access Control
What is Broken Access Control and why is it ranked #1 in OWASP Top 10 2021?
The 30-Second Answer: Broken Access Control occurs when users can access resources or perform actions beyond their intended permissions. It's ranked #1 because 94% of applications tested had some form of access control issue, with an average incidence rate of 3.81%, affecting critical functions like data modification, deletion, and unauthorized access to sensitive information.
The 2-Minute Answer (If They Want More): Broken Access Control represents failures in enforcing policies that prevent users from acting outside their intended permissions. This includes accessing other users' data, modifying data they shouldn't have access to, elevating privileges, or bypassing authentication checks entirely.
The ranking shift from #5 in 2017 to #1 in 2021 reflects the increasing complexity of modern applications, widespread adoption of APIs, and the prevalence of microservices architectures where access control is often inconsistently implemented. Common vulnerabilities include bypassing access control checks by modifying URLs, internal application state, or HTML pages, allowing primary key manipulation to access others' records (IDOR), and elevation of privilege without proper authorization.
The impact is severe because broken access control can lead to unauthorized information disclosure, modification or destruction of data, and performing business functions outside user limits. Real-world examples include viewing others' bank accounts, editing another user's profile, accessing admin functions as a regular user, or manipulating API requests to access unauthorized data.
Organizations struggle with this because access control must be consistently enforced across the entire application stack - UI, API endpoints, and backend services. A single oversight in any layer can compromise the entire system's security posture.
Code Example:
// VULNERABLE: Direct object reference without authorization
app.get('/api/user/:userId/profile', async (req, res) => {
// Only checks authentication, not authorization
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
// Directly uses user-supplied ID without checking ownership
const profile = await db.getUserProfile(req.params.userId);
res.json(profile);
});
// SECURE: Proper authorization check
app.get('/api/user/:userId/profile', async (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
// Verify the requesting user owns this profile or has admin role
if (req.user.id !== req.params.userId && !req.user.roles.includes('admin')) {
return res.status(403).json({ error: 'Forbidden: Access denied' });
}
const profile = await db.getUserProfile(req.params.userId);
res.json(profile);
});
// BETTER: Use session context instead of trusting client input
app.get('/api/my/profile', async (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
// Always use authenticated user's ID from session
const profile = await db.getUserProfile(req.user.id);
res.json(profile);
});
Mermaid Diagram:
flowchart TD
A[User Request] --> B{Authenticated?}
B -->|No| C[401 Unauthorized]
B -->|Yes| D{Authorized for Resource?}
D -->|No| E[403 Forbidden]
D -->|Yes| F{Rate Limited?}
F -->|Exceeded| G[429 Too Many Requests]
F -->|OK| H[Process Request]
H --> I{Input Validated?}
I -->|No| J[400 Bad Request]
I -->|Yes| K[Execute Business Logic]
K --> L[Return Response]
style C fill:#f88
style E fill:#f88
style G fill:#fa8
style J fill:#fa8
style L fill:#8f8
References:
↑ Back to topA03:2021 - Injection
What is injection and what types of injection attacks exist?
The 30-Second Answer: Injection attacks occur when untrusted data is sent to an interpreter as part of a command or query, tricking the interpreter into executing unintended commands or accessing unauthorized data. Common types include SQL injection, NoSQL injection, OS command injection, LDAP injection, XPath injection, and expression language injection.
The 2-Minute Answer (If They Want More): Injection flaws are among the most dangerous vulnerabilities in web applications because they allow attackers to execute arbitrary code, access sensitive data, or completely compromise systems. The root cause is the failure to properly validate, sanitize, or escape user input before passing it to an interpreter.
The impact varies by injection type but can include data theft, data loss, denial of service, or complete system takeover. SQL injection remains one of the most prevalent, allowing attackers to manipulate database queries. Command injection enables OS-level command execution. NoSQL injection targets document databases like MongoDB. LDAP injection compromises directory services, while XPath injection attacks XML data stores.
Modern applications may also be vulnerable to newer injection types like Server-Side Template Injection (SSTI), ORM injection, and expression language injection in frameworks like Spring or Struts. Each type exploits the same fundamental weakness: mixing code and data without proper separation.
Prevention requires a defense-in-depth approach: use parameterized queries, employ input validation with allowlists, apply the principle of least privilege for database accounts, and implement proper output encoding based on the context where data will be used.
Mermaid Diagram:
flowchart TD
A[User Input] --> B{Input Validation?}
B -->|No| C[Directly Concatenated to Query]
B -->|Yes| D{Parameterized/Escaped?}
C --> E[Injection Vulnerability]
D -->|No| E
D -->|Yes| F[Safe Execution]
E --> G[Attacker Can Execute Arbitrary Commands]
F --> H[Only Intended Operations Execute]
References:
↑ Back to topA10:2021 - Server-Side Request Forgery (SSRF)
What is Server-Side Request Forgery (SSRF)?
The 30-Second Answer: Server-Side Request Forgery (SSRF) is a vulnerability where an attacker can abuse server-side functionality to make HTTP requests to arbitrary destinations, including internal resources that are normally inaccessible from the internet. The attacker tricks the server into acting as a proxy, potentially accessing internal APIs, metadata services, or private network resources.
The 2-Minute Answer (If They Want More): SSRF occurs when a web application fetches a remote resource without properly validating the user-supplied URL. Attackers exploit this by providing malicious URLs that target internal systems, cloud metadata endpoints, or services behind firewalls. Since the request originates from the trusted server rather than the attacker's machine, it bypasses network-level protections.
Common attack vectors include URL parameters for fetching images, webhooks, PDF generators, document parsers, and import features. For example, an attacker might submit http://localhost:8080/admin to access an internal admin panel, or http://169.254.169.254/latest/meta-data/ to retrieve AWS credentials from EC2 metadata service.
The impact ranges from information disclosure (reading internal files, accessing admin interfaces) to remote code execution (exploiting internal services), denial of service, and credential theft. SSRF is particularly dangerous in cloud environments where metadata services expose sensitive credentials and configuration data.
Modern applications using microservices, containers, and cloud infrastructure are especially vulnerable because they often have numerous internal services that trust requests from within their network perimeter.
Code Example:
// VULNERABLE: No URL validation
app.get('/fetch-image', async (req, res) => {
const imageUrl = req.query.url;
const response = await fetch(imageUrl); // Dangerous!
const buffer = await response.buffer();
res.type('image/png').send(buffer);
});
// Attacker can submit: ?url=http://localhost:8080/admin
// or: ?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
// SECURE: Whitelist and validation
const ALLOWED_DOMAINS = ['cdn.example.com', 's3.amazonaws.com'];
app.get('/fetch-image', async (req, res) => {
try {
const imageUrl = req.query.url;
// Parse and validate URL
const parsedUrl = new URL(imageUrl);
// Block private/internal IPs and metadata endpoints
if (isPrivateIP(parsedUrl.hostname) ||
parsedUrl.hostname === '169.254.169.254' ||
parsedUrl.hostname === 'localhost' ||
parsedUrl.hostname === '127.0.0.1') {
return res.status(400).json({ error: 'Invalid URL' });
}
// Whitelist allowed domains
if (!ALLOWED_DOMAINS.includes(parsedUrl.hostname)) {
return res.status(400).json({ error: 'Domain not allowed' });
}
// Enforce HTTPS
if (parsedUrl.protocol !== 'https:') {
return res.status(400).json({ error: 'Only HTTPS allowed' });
}
// Use a timeout and size limit
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const response = await fetch(imageUrl, {
signal: controller.signal,
redirect: 'manual' // Don't follow redirects
});
clearTimeout(timeout);
// Check response size
const contentLength = response.headers.get('content-length');
if (contentLength && parseInt(contentLength) > 10 * 1024 * 1024) {
return res.status(400).json({ error: 'File too large' });
}
const buffer = await response.buffer();
res.type('image/png').send(buffer);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch image' });
}
});
// Helper function to check for private IP ranges
function isPrivateIP(hostname) {
const ip = hostname.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
if (!ip) return false;
const [_, a, b, c, d] = ip.map(Number);
return (
a === 10 || // 10.0.0.0/8
(a === 172 && b >= 16 && b <= 31) || // 172.16.0.0/12
(a === 192 && b === 168) || // 192.168.0.0/16
a === 127 || // 127.0.0.0/8 (loopback)
a === 0 // 0.0.0.0/8
);
}
Mermaid Diagram:
flowchart TD
A[Attacker] -->|1. Malicious URL| B[Web Application]
B -->|2. Unvalidated Request| C{Target}
C -->|Internal Service| D[http://localhost:8080/admin]
C -->|Cloud Metadata| E[http://169.254.169.254/meta-data]
C -->|Private Network| F[http://192.168.1.10/api]
D -->|3. Sensitive Data| B
E -->|AWS Credentials| B
F -->|Internal Info| B
B -->|4. Response| A
style A fill:#ff6b6b
style D fill:#ffd93d
style E fill:#ffd93d
style F fill:#ffd93d
References:
↑ Back to top