Application Security -- OWASP Top 10 & Secure Coding
In this tutorial, you'll learn about Application Security. We cover key concepts, practical examples, and best practices.
Application security (AppSec) is the practice of building security into software from the first line of code -- covering the OWASP Top 10 vulnerabilities, secure coding standards, automated security testing, and developer training.
What You'll Learn
You will learn to identify and prevent each OWASP Top 10 vulnerability, integrate SAST and DAST tools into CI/CD pipelines, apply secure coding patterns, and build a security champions program.
Why It Matters
92% of applications have at least one security vulnerability on initial scan. A single SQL injection in a production app costs an average of $4.6 million in breach costs. Shifting security left reduces both cost and risk.
Real-World Use
A payment processing startup integrates Semgrep SAST rules into every pull request. A developer introduces a Server-Side Request Forgery (SSRF) vulnerability in a new feature. The CI pipeline blocks the merge and alerts the security team before the code reaches production.
OWASP Top 10 Quick Reference
| Rank | Category | Core Prevention |
|---|---|---|
| A01 | Broken Access Control | Deny by default, server-side enforcement |
| A02 | Cryptographic Failures | Use modern algorithms, never roll your own |
| A03 | Injection | Parameterized queries, input validation |
| A04 | Insecure Design | Threat modeling, secure design patterns |
| A05 | Security Misconfiguration | Automated hardening, disable defaults |
| A06 | Vulnerable Components | Dependency scanning, SBOM management |
| A07 | Auth Failures | MFA, rate limiting, secure sessions |
| A08 | Integrity Failures | Sign artifacts, verify checksums |
| A09 | Logging Failures | Centralized logging, alerting |
| A10 | SSRF | URL whitelisting, network segmentation |
Threat Modeling with STRIDE
Before writing code, model the threats your application faces using the STRIDE methodology.
| Category | Definition | Example |
|---|---|---|
| Spoofing | Impersonating someone else | Fake JWT token |
| Tampering | Modifying data in transit | Man-in-the-middle API call |
| Repudiation | Denying an action | No audit log for admin changes |
| Information Disclosure | Exposing sensitive data | Stack trace in error response |
| Denial of Service | Crashing the service | Unbounded file upload |
| Elevation of Privilege | Gaining higher permissions | IDOR to access admin panel |
Secure Coding: Input Validation
Always validate input on the server side -- client-side validation is convenience, not security.
import re
from flask import request, jsonify
def process_user_input():
email = request.form.get('email', '')
# Validate email format
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
return jsonify({'error': 'Invalid email format'}), 400
# Sanitize: strip control characters
sanitized = ''.join(c for c in email if c.isprintable())
# Parameterized database query
cursor.execute("SELECT * FROM users WHERE email = %s", (sanitized,))
user = cursor.fetchone()
return jsonify({'user': user})
Expected behavior: Invalid emails are rejected with a 400 error. Valid emails are sanitized and used in a parameterized query, preventing injection.
Secure Coding: Authentication
import bcrypt
from flask import session
def register_user(username, password):
# Enforce password strength
if len(password) < 12:
raise ValueError('Password must be at least 12 characters')
# Hash with bcrypt
hashed = bcrypt.hashpw(
password.encode('utf-8'),
bcrypt.gensalt(rounds=12)
)
# Store hash, never plaintext
cursor.execute(
"INSERT INTO users (username, password_hash) VALUES (%s, %s)",
(username, hashed.decode('utf-8'))
)
def login_user(username, password):
cursor.execute(
"SELECT password_hash FROM users WHERE username = %s",
(username,)
)
row = cursor.fetchone()
if not row:
return False
stored_hash = row[0].encode('utf-8')
if bcrypt.checkpw(password.encode('utf-8'), stored_hash):
session['user'] = username
session['login_time'] = time.time()
return True
return False
Expected behavior: Passwords are hashed with bcrypt (not SHA or MD5) before storage. Login compares against the stored hash. Sessions are created server-side with a login timestamp for expiry validation.
Output Encoding to Prevent XSS
When rendering user-supplied data in web pages, always encode for the correct context.
// Safe HTML encoding for user-generated content
function encodeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// Safe URL encoding
function encodeUrl(str) {
return encodeURIComponent(str).replace(/'/g, '%27');
}
const userComment = "<script>alert('xss')</script>";
console.log(encodeHtml(userComment));
// Output: <script>alert('xss')</script>
Expected behavior: The script tag is rendered as harmless text entities. The browser interprets it as text, not executable code.
SAST Integration in CI/CD
# .github/workflows/sast.yml
name: SAST Scan
on: [pull_request]
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: semgrep/semgrep-action@v1
with:
config: p/owasp-top-ten
publishToken: ${{ secrets.SEMGREP_TOKEN }}
Expected behavior: On every pull request, Semgrep scans the codebase against OWASP Top 10 rules. If critical or high findings exist, the workflow fails and blocks the merge.
DAST with OWASP ZAP
# Automated DAST scan
docker run -t owasp/zap2docker-stable \
zap-full-scan.py \
-t https://staging.example.com \
-r zap-report.html \
-w zap-report.md
Expected output: ZAP spiders the staging site, crawls all pages, and tests for XSS, SQL injection, CSRF, and other runtime vulnerabilities. Results are generated as HTML and Markdown reports.
Dependency Scanning
# Python dependency audit
pip-audit --requirement requirements.txt
# Node.js dependency audit
npm audit --audit-level=high
# Trivy container scan
trivy image --severity CRITICAL,HIGH myapp:latest
Expected output: Lists of known vulnerabilities with CVE IDs, affected versions, and recommended fixes.
Common Errors
Trusting client-side validation -- JavaScript validation can be bypassed by disabling JS or sending raw HTTP requests. Always validate on the server.
Hardcoding secrets in source code -- API keys, database passwords, and JWT secrets committed to git are immediately exposed. Use environment variables or a vault.
Ignoring dependency vulnerabilities -- Third-party libraries account for 70% of application code on average. Regularly run
npm auditorpip-auditand update vulnerable dependencies.No rate limiting on login -- Attackers can brute force passwords at thousands of attempts per second. Implement account lockout and progressive delays after failed attempts.
Exposing stack traces in production -- Verbose error messages reveal framework versions, file paths, and database structure. Return generic error messages and log details server-side.
Practice Questions
What is the difference between SAST and DAST? SAST (Static Application Security Testing) analyzes source code without executing it. DAST (Dynamic Application Security Testing) tests the running application. SAST finds issues early; DAST finds runtime issues.
Why use bcrypt instead of SHA-256 for passwords? bcrypt includes a work factor (cost) that makes each hash computationally expensive. SHA-256 is fast and can be brute-forced at billions of attempts per second with GPU hardware.
What does parameterized query prevent that input sanitization alone might miss? Parameterized queries separate SQL code from data completely. Even if sanitization misses an edge case, the database treats the input as data, not executable SQL.
How does rate limiting prevent credential stuffing? Credential stuffing uses lists of breached passwords against many accounts. Rate limiting slows login attempts, making it impractical to test thousands of credentials per minute.
Challenge: Deploy OWASP Juice Shop (a deliberately vulnerable Node.js app), run a ZAP DAST scan against it, fix three identified vulnerabilities in the source, and verify the fixes pass a re-scan.
Mini Project
Integrate application security into a complete CI/CD pipeline for a Flask web app. Add Semgrep SAST scanning on pull requests, ZAP DAST scanning on staging deploys, and pip-audit dependency scanning on every commit. Configure the pipeline to fail on critical and high findings only.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro