Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Sponsored Reserved space — layout preview until AdSense is connected
How-tos

How to Secure Your Web Application From Common Vulnerabilities

A practical guide to protecting your web app from SQL injection, XSS, CSRF, and other common threats. Covers authentication, input validation, HTTPS, rate limiting, and dependency updates with Python examples.

July 2026 12 min read 1 views 0 hearts

You’ve spent weeks building your web app. It works. Users like it. But one bad line of code can undo all of that. Security isn’t something you add at the end — it’s something you bake in from the start. Let’s talk about the most common vulnerabilities and how to protect your app without losing your mind.

The Big Three You’ll See Everywhere

Most web app attacks fall into a few categories. If you handle these, you’re already ahead of 90% of developers.

SQL Injection is still the king of old-school attacks. Someone types ' OR 1=1 -- into a login field, and suddenly they’re in. The fix? Never trust user input. Use parameterized queries or an ORM. In Python, that means using something like SQLAlchemy or Django’s ORM. Raw SQL with string formatting is a no-go.

Cross-Site Scripting (XSS) happens when you let users inject scripts into your pages. A comment box that doesn’t escape HTML can run JavaScript in someone else’s browser. The fix is simple: always escape output. In Flask, use |e in Jinja2 templates. In Django, templates auto-escape by default — don’t turn that off unless you really know what you’re doing.

Cross-Site Request Forgery (CSRF) tricks users into doing things they didn’t mean to. Like clicking a link that changes their password. Most frameworks have CSRF protection built in. Django has {% csrf_token %} in forms. Flask-WTF does the same. Use them.

Authentication Done Right

Passwords are still the weakest link. But you can make them stronger.

First, never store passwords in plain text. Use a hashing algorithm like bcrypt or Argon2. Python’s bcrypt library is straightforward. Here’s a quick example:

import bcrypt

password = b"super_secret"
hashed = bcrypt.hashpw(password, bcrypt.gensalt())

When someone logs in, compare the hash. Never decrypt — that’s not how hashing works.

Second, enforce password rules. Minimum 8 characters, mix of letters and numbers. But don’t go crazy with special character requirements — that just makes people write passwords on sticky notes.

Third, add rate limiting. If someone tries to log in 10 times in a minute, lock them out for a while. Flask-Limiter or Django Ratelimit can do this in a few lines.

Input Validation Is Your Best Friend

Every piece of data that comes into your app is a potential weapon. Treat it that way.

Validate everything on the server side. Client-side validation is nice for user experience, but it’s trivial to bypass. Someone can send a POST request with curl and skip your JavaScript entirely.

Use a library like marshmallow or pydantic to define schemas. Here’s a simple example with pydantic:

from pydantic import BaseModel, EmailStr

class UserSignup(BaseModel):
    username: str
    email: EmailStr
    password: str

This automatically checks that the email is valid and the fields are strings. No manual if statements needed.

Headers That Protect You

HTTP headers are like security guards for your web app. They tell the browser what to allow and what to block.

Content Security Policy (CSP) is the big one. It tells the browser which sources of content are trusted. If someone injects a script from a random domain, the browser blocks it. Here’s a basic CSP header:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com

X-Frame-Options prevents clickjacking. Set it to DENY or SAMEORIGIN. This stops other sites from embedding your pages in iframes.

Strict-Transport-Security forces HTTPS. Once a browser sees this header, it won’t connect over HTTP again. Set it with a max-age of at least a year.

Session Management That Doesn’t Leak

Sessions are how you remember who’s logged in. If they’re weak, attackers can hijack them.

Use secure cookies. Set the HttpOnly flag so JavaScript can’t read the cookie. Set Secure so it only goes over HTTPS. Set SameSite to Lax or Strict to prevent CSRF.

Here’s how you’d set a cookie in Flask:

response.set_cookie('session_id', value, httponly=True, secure=True, samesite='Lax')

Don’t use predictable session IDs. No sequential numbers or timestamps. Use a library that generates random tokens. Python’s secrets module is perfect for this.

File Uploads Are a Minefield

Letting users upload files is risky. Someone could upload a PHP file to your server and execute it. Or a huge file that fills your disk.

First, validate the file type. Don’t just check the extension — check the actual content. Use Python’s python-magic library to read the file’s MIME type.

import magic

mime = magic.from_buffer(file.read(2048), mime=True)
if mime not in ['image/jpeg', 'image/png']:
    raise ValueError("Invalid file type")

Second, limit file size. In Flask, you can set MAX_CONTENT_LENGTH in config. In Django, use FILE_UPLOAD_MAX_MEMORY_SIZE.

Third, store uploaded files outside the web root. Never serve them directly from your app. Use a CDN or a separate storage service like S3.

HTTPS Is Non-Negotiable

If you’re not using HTTPS in 2025, you’re asking for trouble. Every request — including login forms and API calls — should be encrypted.

Let’s Encrypt gives free SSL certificates. Use them. Set up automatic renewal with Certbot. In production, redirect all HTTP traffic to HTTPS. In Flask, you can do this with middleware:

@app.before_request
def redirect_to_https():
    if not request.is_secure:
        return redirect(request.url.replace('http://', 'https://'), code=301)

Keep Dependencies Updated

This one is boring but critical. Old libraries have known vulnerabilities. Someone already found the hole and published the fix. If you don’t update, you’re leaving the door open.

Use pip-audit or safety to check your dependencies. Run it in your CI pipeline. When a new version of Flask or Django comes out, read the changelog. If it mentions a security fix, update immediately.

Error Messages That Don’t Give Away Secrets

When something breaks, don’t show the user a stack trace. That’s like handing them a map of your server.

In production, show a generic error page. Log the details to a file or a service like Sentry. In Django, set DEBUG = False in production. In Flask, set debug=False and configure proper error handlers.

@app.errorhandler(500)
def internal_error(error):
    return "Something went wrong. We’ve been notified.", 500

Rate Limiting and Brute Force Protection

Attackers will try to guess passwords. They’ll try to scrape your API. Rate limiting slows them down.

Use Flask-Limiter or Django Ratelimit. Set limits like 5 login attempts per minute per IP. After that, block for 15 minutes. This stops brute force without annoying real users.

from flask_limiter import Limiter

limiter = Limiter(app, key_func=lambda: request.remote_addr)

@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute")
def login():
    # your login logic

Keep Secrets Out of Code

Hardcoding API keys in your source code is a bad habit. If your repo goes public, those keys are exposed.

Use environment variables. In Python, os.getenv('SECRET_KEY') is your friend. Store secrets in a .env file that’s in .gitignore. For production, use a secrets manager like HashiCorp Vault or AWS Secrets Manager.

Logging Without Leaking

Logs are useful for debugging. But they can also leak sensitive data. Never log passwords, credit card numbers, or session tokens.

In Python, use the logging module with a custom filter. Here’s a simple one that masks passwords:

import logging

class PasswordFilter(logging.Filter):
    def filter(self, record):
        if 'password' in record.msg:
            record.msg = record.msg.replace(record.args[0], '***')
        return True

logger = logging.getLogger(__name__)
logger.addFilter(PasswordFilter())

Regular Updates Are Not Optional

Every month, new vulnerabilities are found in libraries you use. The fix is already out — you just need to apply it.

Set up a schedule. Once a month, run pip list --outdated and update what you can. Use a tool like Dependabot or Renovate to automate this. Yes, it’s boring. Yes, it’s necessary.

Real World Example: A Small E-Commerce Site

Let’s say you’re building a simple store with Flask. You have a product search feature. Without proper input validation, someone could type '; DROP TABLE products; -- into the search box. That’s SQL injection.

The fix is using parameterized queries. With SQLAlchemy, it looks like this:

results = session.query(Product).filter(Product.name.ilike(f'%{search_term}%')).all()

SQLAlchemy handles the escaping. You don’t have to think about it.

Now imagine your login form. Without rate limiting, an attacker can try thousands of passwords. With Flask-Limiter, you add one decorator and you’re safe.

The Bottom Line

Security isn’t about being paranoid. It’s about being prepared. The vulnerabilities I’ve covered here are the ones that get exploited every single day. Fix them, and you’ve closed the most common doors.

Start with input validation, use HTTPS, hash passwords, and keep your dependencies updated. That’s 80% of the work. The rest is staying informed and not cutting corners.

At PythonSkillset, we’ve seen too many apps that worked perfectly until someone found the one unvalidated input field. Don’t let that be you.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.