KAHIBARO
Discord Login Register

OWASP Top 10

Why the OWASP Top 10 Matters

As a backend developer, you do not have to memorize every security attack that exists, but you must be familiar with the most common and dangerous ones. The OWASP Top 10 is a community maintained list of the most critical web application security risks.

Think of it as a “priority list” of things you must defend against first. Many real world breaches happen because of these exact issues, often in very simple ways.

In this chapter, you will learn what each OWASP Top 10 category means in practical backend terms, what a vulnerable backend might look like, and what a safer version looks like.

Overview of the OWASP Top 10

OWASP periodically updates the Top 10. The exact names and order can change, but the themes are stable. A practical way to think of them for backend work is:

Category (simplified)Typical backend mistake
Broken access controlNot checking “who can do what” on every request
Cryptographic failuresStoring or sending sensitive data without proper protection
InjectionPutting untrusted data directly into SQL, commands, etc.
Insecure designDesigning a feature in a way that is impossible to secure
Security misconfigurationBad or default server / framework settings
Vulnerable & outdated componentsUsing old libraries with known vulnerabilities
Identification & authentication failuresWeak login, broken session, flawed password handling
Software & data integrity failuresNo integrity checks for code, configs, or data
Security logging & monitoring failuresNo useful logs, so you never see attacks or cannot respond
Server-side request forgery (SSRF)Backend can be tricked to make dangerous HTTP requests

The rest of this chapter walks through these 10 from a backend perspective with examples.

A01: Broken Access Control

Access control is about “who can do what to which resource.” Broken access control happens when the backend does not correctly enforce this.

Common backend patterns that cause it:

Example: Missing authorization check

Imagine an API endpoint:

python
# /orders/{order_id}
@app.get("/orders/{order_id}")
def get_order(order_id: int, db: Session = Depends(get_db)):
    return db.query(Order).filter(Order.id == order_id).first()

If you do not check that the current user owns the order, any logged in user can fetch any order by trying different IDs.

A safer version:

python
@app.get("/orders/{order_id}")
def get_order(
    order_id: int,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db),
):
    order = (
        db.query(Order)
        .filter(Order.id == order_id, Order.user_id == current_user.id)
        .first()
    )
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    return order

Important patterns:

A02: Cryptographic Failures

Cryptographic failures usually mean sensitive data is not protected correctly. This is often more about configuration or misuse than about writing your own cryptography code.

Typical backend mistakes:

Example: Bad password storage

Bad:

python
# Storing raw password
user.password = form.password

Better:

python
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(plain_password: str) -> str:
    return pwd_context.hash(plain_password)
user.password_hash = hash_password(form.password)

Example: Sending sensitive data over HTTP

If an API that handles personal data is accessible over http://example.com/api, then anyone on the network can eavesdrop. You must:

Never store passwords in plain text or with a simple hash like MD5 or SHA1. Always use a password hashing algorithm designed for this purpose, for example bcrypt, Argon2, or PBKDF2.

You will work with HTTPS and TLS in more depth in the security chapters about HTTPS and TLS.

A03: Injection

Injection occurs when untrusted input is interpreted as code or a command. For backend developers, the most common form is SQL Injection.

Other types include:

SQL Injection example

Bad Python code:

python
def get_user(db, username: str):
    # Very unsafe
    query = f"SELECT * FROM users WHERE username = '{username}'"
    return db.execute(query).fetchone()

If the user sends username = "admin' OR '1'='1", the query becomes:

sql
SELECT * FROM users WHERE username = 'admin' OR '1'='1'

which matches all users.

Safe pattern, with parameterized queries:

python
def get_user(db, username: str):
    return db.execute(
        "SELECT * FROM users WHERE username = :username",
        {"username": username},
    ).fetchone()

Or with SQLAlchemy ORM:

python
db.query(User).filter(User.username == username).first()

Always use parameterized queries or ORM query builders. Never build SQL by concatenating user input.

This idea also applies to other interpreters. Avoid building shell commands like:

python
os.system("rm " + user_input)

A04: Insecure Design

Insecure design means the problem is in the concept of how the feature works, not only in the code.

Examples of insecure designs:

Example: Insecure password reset flow

Insecure design:

  1. User submits email.
  2. Backend shows security questions, then directly changes password.

If an attacker can guess the answers or intercept the flow, they own the account.

More secure design:

  1. User submits email.
  2. Backend generates a random, one time token and sends a link by email.
  3. The link has a short expiration, is tied to that account, and can be used only once.
  4. User sets a new password via that link.
  5. Token is invalidated after use.

Even if the code is written correctly, a design that reveals full passwords or uses permanent, guessable reset links will never be secure.

A05: Security Misconfiguration

Security misconfiguration is about bad or missing security settings. This includes:

Example: Debug mode in production

In some Python frameworks:

python
app.run(debug=True)

In production, this can show internal variables, environment values, and more if an error occurs. Attackers can use this information.

Safer pattern:

Example: Overly permissive CORS

CORS is covered in another chapter. A common misconfiguration is:

python
origins = ["*"]  # for all methods including credentials
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,  # with "*"
    allow_methods=["*"],
    allow_headers=["*"],
)

If combined with cookies or auth headers, this can expose user data to any site.

Better is to set specific trusted origins:

python
origins = ["https://example.com", "https://admin.example.com"]

Never leave default passwords, public debug modes, or open admin panels in production. Always review security related configuration before deployment.

A06: Vulnerable and Outdated Components

Modern backends depend on many third party libraries, frameworks, and services. If you use old versions with known vulnerabilities, your application can be compromised even if your own code is correct.

Common issues:

Example: Unmanaged dependencies

requirements.txt:

txt
fastapi
uvicorn
sqlalchemy

This means “latest available version at install time,” which can change across deployments.

Better:

txt
fastapi==0.115.0
uvicorn[standard]==0.30.0
sqlalchemy==2.0.35

Then regularly:

Patterns for safety:

A07: Identification and Authentication Failures

This category concerns login, session management, and related logic. Common failures:

Example: Weak login implementation

Bad:

python
def login(username, password):
    user = get_user_by_username(username)
    if not user:
        raise HTTPException(401, "Invalid credentials")
    # raw password comparison
    if user.password != password:
        raise HTTPException(401, "Invalid credentials")

Better:

python
def verify_password(plain_password, password_hash):
    return pwd_context.verify(plain_password, password_hash)
def login(username, password):
    user = get_user_by_username(username)
    if not user or not verify_password(password, user.password_hash):
        # do not reveal if username or password was wrong
        raise HTTPException(401, "Invalid credentials")

Also consider:

You will learn more about these in the authentication and authorization sections of the course.

Never store or compare raw passwords. Use proper password hashing and session or token security patterns.

A08: Software and Data Integrity Failures

This category covers situations where the application or infrastructure does not protect against:

Typical backend related problems:

Example: Trusting client side roles

Bad:

python
# Frontend sends role in JSON:
# { "username": "alice", "role": "admin" }
def create_user(data):
    user.role = data["role"]  # trust client!
    ...

Anyone who intercepts the request can change "role": "admin".

Safer:

For code and configuration integrity:

A09: Security Logging and Monitoring Failures

If you do not log important events or do not monitor those logs, you will not know that an attack is happening or has succeeded.

Common issues:

Example: Useful logging for a failed login

Instead of:

python
logger.info("Login failed")

Better:

python
logger.warning(
    "Login failed",
    extra={"username": username, "ip": client_ip, "reason": "invalid_credentials"},
)

Be careful not to log the password. You might log an anonymized user identifier, but not secrets.

Patterns:

Never log raw passwords, secret keys, or full payment data. Logs must be useful for detection but must not leak sensitive information.

A10: Server-Side Request Forgery (SSRF)

SSRF happens when your backend makes HTTP requests to URLs that the client controls, and you do not restrict where it can connect.

Attackers can use this to:

Example: Unrestricted URL fetcher

Bad:

python
import requests
@app.post("/fetch")
def fetch_url(body: dict):
    url = body["url"]
    resp = requests.get(url)
    return {"status": resp.status_code, "body": resp.text}

If the attacker sends {"url": "http://127.0.0.1:8000/admin"}, your server accesses localhost, which might be an internal admin interface.

Mitigations:

Example check (simplified, do not use as is in production):

python
from urllib.parse import urlparse
import ipaddress
import socket
def is_private_host(url: str) -> bool:
    parsed = urlparse(url)
    hostname = parsed.hostname
    if not hostname:
        return True
    ip = socket.gethostbyname(hostname)
    ip_obj = ipaddress.ip_address(ip)
    return ip_obj.is_private or ip_obj.is_loopback

Then:

python
if is_private_host(url):
    raise HTTPException(status_code=400, detail="URL not allowed")

Putting the OWASP Top 10 into Practice

You do not need to implement everything perfectly on day one, but you should:

  1. Recognize each category: When you design or review code, ask “Does this touch authentication, access control, injection, or logging?”
  2. Adopt safe defaults:
    • Use parameterized SQL and ORMs.
    • Use HTTPS in production.
    • Hash passwords with a proper algorithm.
    • Check access on every protected resource.
  3. Integrate checks into your workflow:
    • Code review with security in mind.
    • Use linters and security tools where possible.
    • Keep dependencies updated and monitored.

Most serious security incidents are caused by basic mistakes that the OWASP Top 10 describes. If you build the habit of considering these 10 areas when you design and code backend features, you will avoid many common and dangerous vulnerabilities.

In later chapters, you will dive deeper into specific topics like SQL Injection, XSS, CSRF, HTTPS, CORS, authentication, and secure configuration, where many of these OWASP Top 10 ideas appear again in more detail.

Views: 7

Comments

Please login to add a comment.

Don't have an account? Register now!