Chapter 30: Application Security¶
Overview¶
Application security (AppSec) addresses vulnerabilities introduced during software design, development, deployment, and maintenance. With 75% of attacks now targeting the application layer (Gartner), organizations must embed security throughout the software development lifecycle rather than applying it as an afterthought. This chapter covers the OWASP Top 10, secure coding principles, threat modeling, SAST/DAST/IAST, API security, secure DevOps (DevSecOps), and web application firewall deployment.
Learning Objectives¶
By the end of this chapter, students SHALL be able to:
- Apply OWASP Top 10 knowledge to code review and threat assessment
- Conduct threat modeling using STRIDE for new application designs
- Integrate SAST, DAST, and SCA into software development pipelines
- Assess and secure RESTful and GraphQL API attack surfaces
- Design and operate a Web Application Firewall (WAF) deployment
- Build an AppSec program using OWASP SAMM or BSIMM benchmarks
Prerequisites¶
- Basic programming knowledge (any language)
- Understanding of HTTP, REST APIs, and web architecture
- Chapter 29 (Vulnerability Management) for scanning tools context
Why This Matters
The Log4Shell vulnerability (CVE-2021-44228) was in a logging library used by millions of Java applications — a single insecure code pattern (JNDI lookup in log messages) enabled remote code execution across an estimated 3 billion devices. SQL injection, still #3 in OWASP Top 10 2021, has existed since 1998 and continues to cause major breaches because developers still don't use parameterized queries. Equifax's 2017 breach of 147 million Americans came from an unpatched Apache Struts vulnerability in a consumer-facing app. Application security is both the largest attack surface and the most preventable category of vulnerability.
30.1 OWASP Top 10 (2021)¶
30.1.1 A01 — Broken Access Control¶
The most common web application vulnerability (94% of applications tested).
# VULNERABLE: Direct Object Reference
@app.route("/invoice/<invoice_id>")
def get_invoice(invoice_id):
invoice = db.query("SELECT * FROM invoices WHERE id = ?", invoice_id)
return jsonify(invoice) # Anyone can access any invoice by changing the ID!
# SECURE: Enforce ownership check
@app.route("/invoice/<invoice_id>")
@login_required
def get_invoice(invoice_id):
invoice = db.query(
"SELECT * FROM invoices WHERE id = ? AND user_id = ?",
invoice_id, current_user.id # Ensure user owns this invoice
)
if not invoice:
abort(403) # Forbidden — not found or not owned
return jsonify(invoice)
30.1.2 A02 — Cryptographic Failures¶
# VULNERABLE: Weak hashing, no salt
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest() # Crackable in seconds
# VULNERABLE: Broken encryption
from Crypto.Cipher import DES # DES — 56-bit key, broken
cipher = DES.new(key, DES.MODE_ECB) # ECB mode reveals patterns
# SECURE: Modern password hashing
import bcrypt
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# SECURE: Authenticated encryption
from cryptography.fernet import Fernet
key = Fernet.generate_key() # Store securely!
f = Fernet(key)
encrypted = f.encrypt(sensitive_data.encode())
# SECURE: For databases — use AES-256-GCM with unique IV per record
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
nonce = os.urandom(12) # MUST be unique per encryption operation
ciphertext = aesgcm.encrypt(nonce, plaintext, additional_data)
30.1.3 A03 — Injection¶
# VULNERABLE: SQL Injection
username = request.args.get('username')
query = f"SELECT * FROM users WHERE username = '{username}'"
# Payload: ' OR '1'='1 → dumps entire table
# Payload: '; DROP TABLE users; -- → destroys data
# SECURE: Parameterized queries (prepared statements)
query = "SELECT * FROM users WHERE username = %s"
cursor.execute(query, (username,)) # Database handles escaping
# SECURE: ORM (SQLAlchemy)
user = session.query(User).filter(User.username == username).first()
# COMMAND INJECTION
# VULNERABLE:
result = os.system(f"ping {user_input}") # User can inject: '; rm -rf /'
# SECURE: Use subprocess with argument list (no shell=True)
import subprocess
result = subprocess.run(
["ping", "-c", "4", user_input], # Arguments as list — no shell interpolation
capture_output=True, text=True, timeout=30
)
30.1.4 A04 — Insecure Design¶
Threat modeling identifies insecure design before code is written. STRIDE is the most widely used methodology:
| Threat | Description | Example |
|---|---|---|
| Spoofing | Impersonating another user/system | Weak session tokens |
| Tampering | Modifying data in transit or at rest | No message integrity checks |
| Repudiation | Denying an action was taken | Insufficient logging |
| Information Disclosure | Exposing confidential data | Verbose error messages |
| Denial of Service | Making service unavailable | No rate limiting |
| Elevation of Privilege | Gaining unauthorized access | IDOR, privilege escalation |
30.1.5 A05 — Security Misconfiguration¶
# VULNERABLE: Django default settings
DEBUG = True # Never in production — exposes stack traces
SECRET_KEY = "insecure-default-key"
ALLOWED_HOSTS = ["*"] # Allows all hosts
# SECURE: Production Django settings
DEBUG = False
SECRET_KEY = os.environ.get("SECRET_KEY") # From secrets manager
ALLOWED_HOSTS = ["app.example.com"]
SECURE_HSTS_SECONDS = 31536000 # HSTS for 1 year
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
X_FRAME_OPTIONS = "DENY"
SECURE_CONTENT_TYPE_NOSNIFF = True
30.1.6 A07 — Identification and Authentication Failures¶
# SECURE: Authentication implementation checklist
class SecureAuth:
def login(self, username: str, password: str) -> bool:
# 1. Use timing-safe comparison (prevent timing attacks)
user = self.get_user(username)
if not user:
# Compare anyway — prevent username enumeration via timing
bcrypt.checkpw(password.encode(), b"$2b$12$dummy_hash_for_timing")
return False
# 2. Use bcrypt with cost factor ≥ 10
if not bcrypt.checkpw(password.encode(), user.password_hash):
self.log_failed_login(username, request.remote_addr)
self.increment_lockout_counter(username)
return False
# 3. Account lockout (5 attempts)
if user.failed_logins >= 5:
raise AccountLockedException("Account locked. Use password reset.")
# 4. MFA required for all accounts
if not self.verify_mfa(user, request.form.get("totp_code")):
return False
# 5. Regenerate session on login (prevent session fixation)
session.regenerate()
session["user_id"] = user.id
return True
30.1.7 A08 — Software and Data Integrity Failures¶
See Chapter 24 (Supply Chain Attacks) for full treatment of dependency integrity and CI/CD security.
# Verify package integrity before use
pip install --require-hashes -r requirements.txt
# requirements.txt with hashes:
requests==2.31.0 \
--hash=sha256:58cd2187423839d5cec5c8c50a66b0571b4f19ca238e75399b680c58cffb84a7
30.1.8 A10 — Server-Side Request Forgery (SSRF)¶
# VULNERABLE: Fetch user-provided URL
import requests
url = request.args.get("url")
response = requests.get(url) # Attacker can access: http://169.254.169.254/latest/meta-data/
# SECURE: Validate and allowlist URLs
from urllib.parse import urlparse
import ipaddress
ALLOWED_SCHEMES = {"https"}
ALLOWED_DOMAINS = {"api.partner.com", "cdn.example.com"}
def safe_fetch(url: str) -> requests.Response:
parsed = urlparse(url)
if parsed.scheme not in ALLOWED_SCHEMES:
raise ValueError(f"Disallowed scheme: {parsed.scheme}")
# Resolve hostname to IP and check for private ranges
import socket
ip = socket.gethostbyname(parsed.netloc)
addr = ipaddress.ip_address(ip)
if addr.is_private or addr.is_loopback or addr.is_link_local:
raise ValueError(f"Internal IP not allowed: {ip}")
if parsed.netloc not in ALLOWED_DOMAINS:
raise ValueError(f"Domain not in allowlist: {parsed.netloc}")
return requests.get(url, timeout=5)
30.2 API Security¶
30.2.1 OWASP API Security Top 10¶
| Rank | Vulnerability | Example |
|---|---|---|
| API1 | Broken Object Level Authorization | Access other users' resources by changing ID |
| API2 | Broken Authentication | Weak JWT secrets, no token expiry |
| API3 | Broken Object Property Level Auth | Mass assignment vulnerability |
| API4 | Unrestricted Resource Consumption | No rate limiting → DoS |
| API5 | Broken Function Level Authorization | Non-admin accessing admin endpoints |
| API6 | Unrestricted Access to Sensitive Business Flows | Account takeover via API chain |
| API7 | Server Side Request Forgery | Webhook URL points to internal services |
| API8 | Security Misconfiguration | CORS misconfiguration, verbose errors |
| API9 | Improper Inventory Management | Shadow APIs, undocumented endpoints |
| API10 | Unsafe Consumption of APIs | Trusting 3rd-party API responses without validation |
30.2.2 JWT Security¶
# VULNERABLE: JWT verification
import jwt
token = request.headers["Authorization"].split(" ")[1]
payload = jwt.decode(token, options={"verify_signature": False}) # NEVER DO THIS
# VULNERABLE: Algorithm confusion attack
# Attacker changes alg to "none" — accept token without signature
payload = jwt.decode(token, algorithms=["HS256", "none"]) # Don't allow "none"!
# SECURE: Strict JWT validation
import jwt
from functools import wraps
JWT_SECRET = os.environ["JWT_SECRET"] # 256-bit random from secrets manager
JWT_ALGORITHM = "RS256" # Prefer asymmetric (RS256/ES256) over HS256
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
return jsonify({"error": "Unauthorized"}), 401
token = auth_header.split(" ")[1]
try:
payload = jwt.decode(
token,
JWT_PUBLIC_KEY,
algorithms=["RS256"], # Strict algorithm list — no "none", no HS256 if using RS256
options={
"verify_exp": True, # Verify expiry
"verify_nbf": True, # Verify not-before
"require": ["exp", "iat", "sub"] # Required claims
}
)
except jwt.ExpiredSignatureError:
return jsonify({"error": "Token expired"}), 401
except jwt.InvalidTokenError as e:
return jsonify({"error": "Invalid token"}), 401
g.user_id = payload["sub"]
return f(*args, **kwargs)
return decorated
30.3 Secure Development Lifecycle¶
30.3.1 Microsoft SDL Activities by Phase¶
flowchart LR
A[Requirements] --> B[Design] --> C[Implementation] --> D[Verification] --> E[Release] --> F[Response]
A --> A1[Security requirements\nAbuse cases]
B --> B1[Threat modeling\nSTRIDE/PASTA\nSecurity architecture review]
C --> C1[Secure coding standards\nSAST in IDE\nPeer code review]
D --> D1[SAST + DAST\nPentest\nSCA dependency scan]
E --> E1[Penetration test\nFinal security review\nIncident response plan]
F --> F1[Bug bounty\nVulnerability disclosure\nPatch management] 30.3.2 OWASP SAMM — Software Assurance Maturity Model¶
SAMM provides a measurable framework for assessing and improving AppSec programs across 15 security practices in 5 business functions:
OWASP SAMM v2 Structure:
├── Governance
│ ├── Strategy and Metrics
│ ├── Policy and Compliance
│ └── Education and Guidance
├── Design
│ ├── Threat Assessment
│ ├── Security Requirements
│ └── Security Architecture
├── Implementation
│ ├── Secure Build
│ ├── Secure Deployment
│ └── Defect Management
├── Verification
│ ├── Architecture Assessment
│ ├── Requirements-Driven Testing
│ └── Security Testing
└── Operations
├── Incident Management
├── Environment Management
└── Operational Management
Each practice measured at Level 1, 2, or 3.
Target: Defined by org risk appetite and regulatory requirements.
30.4 Web Application Firewall¶
30.4.1 WAF Deployment Modes¶
graph LR
subgraph "Client"
USER[User Browser]
end
subgraph "WAF Layer"
WAF[Web Application Firewall\nModSecurity / Cloudflare / AWS WAF]
end
subgraph "Application"
APP[Web Application\nAPI / Service]
end
USER --> WAF --> APP
WAF -.->|Block| BL[Blocked Requests:\n- SQLi patterns\n- XSS patterns\n- Path traversal\n- Scanner signatures\n- Bot traffic] 30.4.2 ModSecurity / OWASP CRS¶
# Nginx + ModSecurity 3 + OWASP CRS configuration
# /etc/nginx/nginx.conf
load_module modules/ngx_http_modsecurity_module.so;
server {
listen 443 ssl;
server_name app.example.com;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity/modsecurity.conf;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-{RANDOM}'; style-src 'self' 'unsafe-inline'";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()";
}
30.5 Security Headers¶
# REQUIRED Security Response Headers
# HSTS — force HTTPS for 1 year
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
# Prevent clickjacking
X-Frame-Options: DENY
# Prevent MIME sniffing
X-Content-Type-Options: nosniff
# CSP — restrict resource loading
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'; base-uri 'self'
# Referrer policy
Referrer-Policy: strict-origin-when-cross-origin
# Permissions policy — disable browser features
Permissions-Policy: geolocation=(), microphone=(), payment=()
# Remove server information
Server: (remove this header entirely)
X-Powered-By: (remove this header entirely)
30.6 Benchmark Controls¶
| Control ID | Title | Requirement |
|---|---|---|
| Nexus SecOps-AS-01 | Secure SDLC | Documented secure development lifecycle; threat modeling for new features |
| Nexus SecOps-AS-02 | SAST/DAST in CI/CD | Automated SAST and DAST in pipeline; critical findings block deployment |
| Nexus SecOps-AS-03 | Penetration Testing | Annual pentest for all externally-facing applications |
| Nexus SecOps-AS-04 | WAF Deployment | WAF deployed in front of all internet-facing applications |
| Nexus SecOps-AS-05 | Security Headers | All OWASP recommended security headers deployed |
| Nexus SecOps-AS-06 | API Security | Authentication, rate limiting, and input validation on all APIs |
Exam Prep & Certifications¶
Relevant Certifications
The topics in this chapter align with the following certifications:
- CSSLP — Domains: Secure Software Lifecycle, Secure Design, Security Testing
- GIAC GWEB — Domains: Web Application Security, Secure Development
- GIAC GWAPT — Domains: Web Application Penetration Testing, OWASP Top 10
Key Terms¶
CSP (Content Security Policy) — An HTTP response header that instructs browsers to only load resources from specified, trusted sources — preventing XSS and data injection.
DAST (Dynamic Application Security Testing) — Testing a running application from the outside by sending inputs and observing responses, simulating attacker behavior.
OWASP — Open Web Application Security Project — a non-profit foundation producing free, open-source methodologies, tools, and documentation for application security.
SAST (Static Application Security Testing) — Analyzing source code or compiled binaries without execution to identify security vulnerabilities.
STRIDE — Microsoft's threat modeling framework categorizing threats as: Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege.
WAF (Web Application Firewall) — A security control that monitors, filters, and blocks HTTP/HTTPS traffic to and from a web application, protecting against common attacks.