Security Advanced

The Web Application Threat Landscape — Real Attackers, Real Targets, Real Money

Who actually attacks web applications, what they're after, and the threat models that matter for SaaS, e-commerce, and B2B platforms in 2026.

KC Ramo · DjangoZen Team May 10, 2026 17 min read 391 views

Why "web app" is a special case

Web applications carry more business value, store more customer data, and accept more attacker-controlled input than any other class of software you run. Every day on the internet, your login form is touched by credential-stuffing botnets. Your /api/ endpoints are probed by automated scanners. Your password reset flow is tested for enumeration.

This series is about understanding those threats at the technical depth required to defend production web apps — not generic "awareness training" content. We're starting with the threat landscape because everything else depends on knowing who's actually attacking and why.

The four attacker categories that hit web apps daily

1. Automated scanners and botnets

The largest volume of attack traffic against any internet-facing web app. Operators rent or build botnets to:

  • Credential stuff — try usernames/passwords from one breach against every other login form
  • Web shell scan — probe for known file paths (/wp-admin/, /.env, /.git/HEAD, common admin URLs)
  • Vulnerability scan — exploit known CVEs in popular frameworks within hours of public disclosure
  • Cryptominer drop — once they get RCE, install miners pointed at attacker-controlled pools
  • DDoS infrastructure — your compromised app becomes part of the next attack

You will see this traffic on day one of going live. The defenses are basic but mandatory: WAF + rate limiting + patched dependencies + MFA. Get them in place before launch.

2. Initial Access Brokers (IABs)

A specialized criminal economy: attackers whose only job is to break in and sell access to others. They don't deploy ransomware themselves; they sell credentials and shell access to the gangs that do.

What they target in web apps:

  • Admin panels behind weak passwords with no MFA
  • CI/CD platforms with leaked credentials in commit history
  • Cloud consoles (AWS, GCP, Azure) accessed via web
  • CMS backends (WordPress, Drupal, custom admins) running outdated code

Access to a compromised SaaS admin account is traded as a commodity, and its price rises with the victim organisation's apparent revenue and the sensitivity of the data behind it.

3. Ransomware affiliates

The crime groups that pay IABs. Their web app angle is access-to-pivot — once they get into your admin panel or developer accounts, they pivot to internal infrastructure where the real damage happens (file servers, domain controllers, backup systems).

Web apps are increasingly the initial vector because:

  • VPN gateways are now better-patched and locked down
  • RDP is mostly off the internet
  • But web app admin panels, dev tools, and SaaS integrations have grown the attack surface

Specifically dangerous: any app where compromising an admin account also compromises connected systems (SSO providers, Git repositories, cloud infrastructure).

4. Targeted attackers and APTs

Most web apps aren't worth this level of attention. The exceptions: defense contractors, journalists' tools, dissident communications, government suppliers, financial services with high-value transactions. If you fall into one of these, your threat model includes professional, well-resourced adversaries with custom tooling.

Indicators you're a target:

  • Specific industry alerts from your sector ISAC
  • Spear-phishing attempts tailored to your business
  • Long-dwell incidents found in audits (months of undetected access)
  • Anomalous activity on developer or admin accounts

If this is your context, you need a different conversation than this tutorial series alone.

What attackers actually want from your web app

Mapped to monetization paths covered in tutorial 2:

  1. Credentials — your users' login credentials, especially admin or staff. Resold or used for downstream attacks.
  2. Payment data — credit card numbers (PCI breach), stored payment methods, Stripe API keys for fraudulent charges.
  3. Customer PII — emails, names, addresses, identification documents. Used for fraud, sold in bulk, leveraged for further phishing.
  4. Business data — customer lists, contracts, internal communications. Used for blackmail or competitive espionage.
  5. Computing power — your servers become cryptominers, DDoS nodes, proxy servers.
  6. Access for pivots — your app is the entry point to higher-value targets.
  7. Brand abuse — your domain hijacked to host phishing pages targeting your own customers.

The defense priorities depend on which of these matters most for your specific app. A small content site mainly defends against #5 and #7. A SaaS with payment data defends primarily against #1, #2, and #6.

The web app attack chain

Most successful breaches against web apps follow a recognizable pattern:

1. Reconnaissance     — find the target, map the attack surface
2. Initial access     — credential reuse, phishing, exploit a vuln
3. Persistence        — get a foothold that survives password changes
4. Privilege escalation — go from low-priv user to admin
5. Lateral movement   — pivot to other systems/accounts
6. Action on objective — exfiltrate data, deploy ransomware, fraudulent transfers

Each step has tools and techniques (covered in tutorials 7-9). Each step has detection opportunities (covered in tutorial 10). The defenses you build should slow the attacker at multiple stages so detection has time to catch up.

Where each stage shows up in a Django app

The chain is only useful to a defender if each stage maps to something you can observe. For a Django app behind nginx and PostgreSQL, the evidence lives in a few places: the proxy access log, the application's security log, the admin LogEntry table, and your hosting provider's audit trail.

StageWhat it looks like from the insideControl that buys time
ReconnaissanceBursts of 404s on paths your app never servedWAF rules, rate limits, DEBUG = False
Initial accessFailed logins spread across accounts, then a success from a new networkMFA, breached-password checks, auth rate limiting
PersistenceNew staff user, API token or OAuth grant; changed email on an admin accountAlert on any credential or privilege change
Privilege escalationis_staff or is_superuser flipped, group changesFew superusers, change approval
Lateral movementApp-host secrets used elsewhere, unexpected outbound connectionsScoped credentials, egress filtering
Action on objectiveLarge exports, payout or bank-detail changesExport thresholds, dual approval

The goal is not to model the attacker's cleverness; it is to make sure at least two of these rows would produce a signal that a human sees within hours rather than months.

Real-world incident archetypes

These patterns repeat across countless web app breaches:

"Credential stuffing into staff account"

User reuses password from old breach. Attacker tries it on your admin login. Account has no MFA. Attacker now has full admin access. Exports customer database. Posts it for sale on dark forums.

Defense layers that would have stopped it: MFA, breach password blocking (HaveIBeenPwned check on login), rate limiting + anomaly detection on auth, audit logs of admin actions, alerts on bulk export operations.

"Phishing-to-OAuth"

Attacker sends spear-phishing email to a target with a link to a malicious OAuth app. Target clicks "Authorize." Attacker now has long-lived OAuth token, no need for password or MFA. Pivots through connected services (Slack, GitHub, Gmail).

Defense layers: OAuth app approval workflows, monitoring of token grants, principle-of-least-privilege on scopes, ability to revoke tokens centrally, user training on OAuth consent screens.

"Vulnerable dependency RCE"

Your app uses a popular library with a fresh CVE. Within hours of disclosure, internet-wide scanners are exploiting it. Your app gets a web shell installed. Attacker uses it to read secrets, then pivots to your cloud account via the IAM role.

Defense layers: automated dependency scanning + patching SLA, restricted server permissions (process can't read arbitrary files), egress filtering (web shells need C2 to be useful), runtime intrusion detection.

"BEC-to-wire-fraud via web admin"

Phishing compromises the CFO's email account. Attacker doesn't ransomware — they sit silently for a week, learn the wire transfer process, then send a properly-styled email to AP requesting a vendor payment to a controlled account. AP processes it. €240K gone.

Defense layers: MFA on email, DMARC enforcement, dual approval for transfers, out-of-band verification for changes to vendor banking, training that doesn't shame people for being targeted.

Building your threat model

A 20-minute exercise for any web app you maintain. Answer in order:

  1. What does the app do? (Plain English, two sentences.)
  2. What does it store that's valuable? (Data inventory.)
  3. Who would attack it? (Pick from the categories above.)
  4. How would they attack it? (Pick from the chain above; what's the easiest path?)
  5. What would the impact be? (Revenue, regulatory, reputational.)
  6. What controls exist today? (List them honestly.)
  7. Where's the biggest gap? (Where the easiest attack path meets the weakest control.)

That gap is what to fix this quarter. Repeat the exercise quarterly.

A worked threat model: a Django invoicing SaaS

Here is what a finished answer looks like, so you can calibrate the level of detail. The app is myproject at app.example.com: small businesses create invoices, store their customers' contact details, and connect Stripe so invoices can be paid by card. Staff use the Django admin for support.

Threat pathLikely attackerLikelihoodImpactCurrent gap
Credential stuffing against tenant loginsBotnetsHighMediumNo per-account throttling, MFA optional
Staff admin takeover via reused passwordIABMediumHighAdmin MFA not enforced
Tenant bank-detail change after account takeoverFraud crewsMediumHighNo re-authentication or notification on change
Dependency RCEScannersMediumHighAd hoc patching, unrestricted egress
Leaked Stripe key from CI logsOpportunisticLowHighRestricted keys not used

Coarse levels are enough; precise scores are false precision. The output is a short, ordered backlog with owners and dates: enforce staff MFA, require re-authentication plus an email to the account owner whenever bank details change, add per-account login throttling, and set a patching window for security releases. The bank-detail control is the one teams usually miss, because it is a business-logic weakness rather than a classic vulnerability, yet it is exactly where account takeover turns into money.

Two failure modes to avoid: modelling the architecture diagram instead of the real deployment (the forgotten staging server with production data, the cron job using a superuser token), and stopping at vulnerabilities when many costly incidents abuse features working as designed, such as exports, password resets and invitations. Re-run the model whenever you add a payment flow, a public API, or an integration with write access.

The minimum viable defense for any web app

If your app is on the internet today, these are non-negotiable:

  • TLS everywhere, HSTS enabled, modern cipher suites only
  • MFA on all admin and staff accounts — phishing-resistant if possible (WebAuthn)
  • Rate limiting on auth endpoints (for example, no more than 5 login attempts per minute per IP, plus a per-account limit)
  • Account lockout on repeated failures (with timing-safe responses to prevent enumeration)
  • Patched framework and dependencies with automated alerts on new CVEs
  • WAF in front (Cloudflare free tier, ModSecurity, AWS WAF — anything beats nothing)
  • Logging of all auth events, admin actions, 4xx/5xx errors to a centralized aggregator
  • Sentry or equivalent wired into the application
  • Encrypted backups with immutability and tested restore process
  • Documented incident response playbook even if it's one page

Beyond this floor, the rest of this tutorial series goes deep on advanced techniques and defenses. But start here. The fancy stuff doesn't help if these basics are missing.

Who actually attacks web applications

Effective defense starts with a realistic picture of who you are defending against, and it is rarely the lone hooded genius of stock imagery. The real landscape includes automated bots scanning indiscriminately for known vulnerabilities, opportunistic criminals monetizing whatever they can, organized groups running credential stuffing and fraud at scale, and occasionally targeted attackers with a specific goal. Most attacks are automated and opportunistic, which means basic hygiene defeats the majority of them. Understanding the spectrum of attackers — their motivations, capabilities, and methods — lets you size your defenses to the threats you actually face rather than to an imagined worst case.

Why money drives almost everything

The overwhelming motivation behind web attacks is financial, and seeing this clarifies what attackers want and how they operate. Stolen data is sold, access is ransomed, fraud is committed, and resources are hijacked for profit. Even attacks that look like vandalism often have a monetary angle. This economic reality means your highest-value assets — payment data, personal information, account access, anything that converts to money — are what attackers most want, and therefore what you must protect most. Following the money predicts attacker behavior remarkably well, and aligning your defenses with where the financial incentive points is one of the most practical ways to prioritize security effort.

Automated versus targeted threats

A crucial distinction shapes your defensive strategy: most threats are automated and indiscriminate, while a few are targeted and persistent. Automated attacks scan the entire internet for known weaknesses and exploit whatever they find, so keeping software patched and avoiding common vulnerabilities defeats the vast majority of them with basic diligence. Targeted attacks, where someone specifically wants into your system, are rarer but more determined and require deeper defenses. Knowing which you are likely to face — most organizations face mostly automated threats — tells you where to invest, and for most, getting the fundamentals right blocks the bulk of real-world attacks.

Telling the two apart in your own logs

Automated traffic tends to be wide and shallow: many source addresses, generic paths that do not exist in your app, no session cookies, and a constant round-the-clock rate. Targeted activity tends to be narrow and deep: few sources, requests that follow your real routes, valid sessions, human-looking pauses, and interest in features specific to your business such as invitations, exports, or payout settings. Neither pattern is proof on its own, but the second should always raise the priority of an investigation, because it means someone has spent effort learning how your application works.

Building a realistic defense posture

A sound defensive posture is proportionate to your actual threat landscape rather than driven by fear or fashion. For most applications that means nailing the fundamentals — patching, strong authentication, the OWASP basics, monitoring — which collectively defeat the automated majority, then layering additional defenses according to the value of what you protect and the likelihood of being targeted. Security is about managing risk sensibly, not achieving an impossible perfection. Grounding your effort in a realistic understanding of who attacks web applications and why lets you build defenses that are strong where it matters and not wastefully elaborate where it does not.

Hardening the Django surface

Most of the minimum viable defense maps to settings you can verify in code review. Run python manage.py check --deploy against your production settings, then make the choices explicit so nobody relies on remembering defaults.

# myproject/settings/production.py
DEBUG = False
ALLOWED_HOSTS = ["app.example.com"]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 60 * 60 * 24 * 365
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_AGE = 60 * 60 * 8  # stolen cookies expire within a working day
AUTH_PASSWORD_VALIDATORS = [
    {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
     "OPTIONS": {"min_length": 12}},
    {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
]

SECURE_PROXY_SSL_HEADER is only safe if nginx always overwrites that header and Gunicorn is reachable only through nginx (bind it to a Unix socket or 127.0.0.1). Below the application, connect as a database role that does not own the schema, and run the service under a systemd unit with ProtectSystem=strict and NoNewPrivileges=true, so a compromised process cannot drop tables or rewrite its own code.

Detection signals and alerting

Every ranked threat path needs at least one structured event that is shipped off the host. Django already emits the key authentication events as signals in django.contrib.auth.signals; route them to a dedicated security logger.

# myproject/security/signals.py (imported from AppConfig.ready)
import logging

from django.contrib.auth.signals import user_logged_in, user_login_failed
from django.dispatch import receiver

security_log = logging.getLogger("security")


@receiver(user_login_failed)
def on_login_failed(sender, credentials, request=None, **kwargs):
    # Django masks the password in credentials; log the username only
    security_log.warning("auth.login_failed", extra={
        "event": "auth.login_failed",
        "username": credentials.get("username", ""),
        "ip": request.META.get("REMOTE_ADDR") if request else None,
    })


@receiver(user_logged_in)
def on_logged_in(sender, request, user, **kwargs):
    security_log.log(logging.WARNING if user.is_staff else logging.INFO,
                     "auth.login_success",
                     extra={"event": "auth.login_success", "user_id": user.pk,
                            "ip": request.META.get("REMOTE_ADDR")})

Common failure modes: trusting the left-most X-Forwarded-For value (the client chooses it; only trust addresses your own proxy wrote), logging session IDs or reset tokens, and keeping logs only on the host an intruder may control. Remember too that the admin LogEntry table records admin additions, changes and deletions, but not reads, exports, or changes made through your own views or the shell. Protect the tests as well: an assertLogs("security") test around a failed self.client.login() catches the refactor that silently renames a logger.

Keep alerts few and tied to threat paths; an alert nobody can act on gets muted, and a muted channel creates false confidence.

AlertThreat pathFirst response
Staff login from a network not seen for that userAdmin takeoverConfirm with the staff member out of band
is_staff or is_superuser grantedPersistence, escalationVerify a matching change request
Bank or payout details changedTakeover to fraudHold payouts, contact owner via known channel
Export volume above a per-user hourly thresholdData theftIdentify the session, decide whether to revoke

Set export thresholds from a month of real usage, above what legitimate heavy users do, and back the hourly tripwire with a daily query so slow exfiltration spread across hours is still caught.

Incident-response preparation

A one-page playbook is only useful if the capabilities it names already exist and have been tried. Build these before you need them:

  • Revoke one user's sessions: setting a new password changes Django's session authentication hash and invalidates that user's other sessions; also check that the account's email and MFA devices were not changed.
  • Revoke every session: rotating SECRET_KEY without listing the old key in SECRET_KEY_FALLBACKS logs everyone out and invalidates outstanding password reset tokens.
  • Rotate third-party secrets: keep a list of where each Stripe, email and OAuth secret lives and who can rotate it.
  • Freeze money movement with a feature flag that pauses payouts or bank-detail changes.
  • Preserve evidence first: snapshot disk and database and export log ranges before cleaning anything, because containment can destroy the record you need for notification decisions.
  • Reach people through an out-of-band contact list that does not depend on possibly compromised systems.

Rehearse quarterly with a tabletop exercise: take one archetype from this tutorial and ask, step by step, what alert fires, who sees it, and what they do. The gaps are usually mundane, such as nobody knowing who can rotate the Stripe key, and each becomes a backlog item beside the threat-model findings.

What's coming in this series

Tutorial 2 — How attackers monetize web app compromise specifically Tutorial 3 — The dark side of web app data after a breach Tutorial 4 — HTTP, TLS, cookies, headers — the attack surface in depth Tutorial 5 — Beyond OWASP Top 10 Tutorial 6 — WAF bypass and application-layer defenses Tutorial 7 — Advanced web app reconnaissance Tutorial 8 — Authentication attacks (sessions, JWT, OAuth, SSO) Tutorial 9 — Red team tactics specific to web apps Tutorial 10 — Investigating a live web app compromise

The whole series assumes you're already comfortable with Django, HTTP, and the basics of web security. The point isn't to introduce concepts — it's to go deep enough to build defenses that actually hold under realistic attack.