Skip to content

Application Security -- OWASP Top 10 & Secure Coding

DodaTech Updated 2026-06-22 6 min read

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: &lt;script&gt;alert('xss')&lt;/script&gt;

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

  1. Trusting client-side validation -- JavaScript validation can be bypassed by disabling JS or sending raw HTTP requests. Always validate on the server.

  2. Hardcoding secrets in source code -- API keys, database passwords, and JWT secrets committed to git are immediately exposed. Use environment variables or a vault.

  3. Ignoring dependency vulnerabilities -- Third-party libraries account for 70% of application code on average. Regularly run npm audit or pip-audit and update vulnerable dependencies.

  4. 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.

  5. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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