Websicherheit: Die Grundlagen, die du kennen musst
Die häufigsten Sicherheitslücken in Webanwendungen – erklärt anhand echter Codebeispiele und konkreter Lösungen, die jeder Entwickler kennen sollte.

Sicherheit ist kein Sprint zum Schluss
Die meisten Sicherheitslücken sind keine raffinierten Angriffe. Es sind simple Fehler, die unter Zeitdruck entstehen – fehlende Eingabevalidierung, geleakte Secrets, vergessene Autorisierungsprüfungen. Dieser Leitfaden behandelt die praktischen Grundlagen, die sich jeder Entwickler verinnerlichen sollte.
Schwachstelle 1: SQL-Injection
Nach wie vor die häufigste kritische Schwachstelle in Webanwendungen – obwohl das Problem schon seit Jahrzehnten gelöst ist.
// ❌ Never concatenate user input into SQL
async function getUser(username: string) {
// If username = "admin' OR '1'='1" — returns all users
const query = `SELECT * FROM users WHERE username = '${username}'`;
return db.query(query);
}
// ✅ Always use parameterized queries
async function getUser(username: string) {
return db.query("SELECT * FROM users WHERE username = $1", [username]);
}
// ✅ Or use a query builder / ORM
async function getUser(username: string) {
return prisma.user.findUnique({ where: { username } });
}Parametrisierte Abfragen sind nicht verhandelbar. Es gibt keine sichere Möglichkeit, Benutzereingaben direkt in SQL zu verketten.
Schwachstelle 2: Cross-Site-Scripting (XSS)
Mit XSS können Angreifer Skripte in deine Seiten einschleusen, die im Browser anderer Nutzer ausgeführt werden – um Sitzungen zu stehlen, Daten abzugreifen oder Konten zu übernehmen.
// ❌ Never render raw HTML from untrusted sources
function Comment({ content }: { content: string }) {
return <div dangerouslySetInnerHTML={{ __html: content }} />;
}
// If content = "<script>document.cookie</script>" — you've been XSS'd
// ✅ React escapes by default — use it
function Comment({ content }: { content: string }) {
return <div>{content}</div>; // Safe — React escapes HTML entities
}
// ✅ If you must render HTML, sanitize first
import DOMPurify from "dompurify";
function RichContent({ html }: { html: string }) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ["p", "b", "i", "em", "strong", "a"],
ALLOWED_ATTR: ["href", "title"],
});
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}Setze zusätzlich eine strikte Content Security Policy, damit selbst bei einem erfolgreichen XSS nur eingeschränkt Skripte ausgeführt werden können.
Schwachstelle 3: fehlerhafte Zugriffskontrolle
Autorisierungsfehler sind tückisch, weil der Code in der Regel funktioniert – nur eben für die falschen Nutzer.
// ❌ Missing authorization check — any authenticated user can delete any comment
app.delete("/api/comments/:id", authenticate, async (req, res) => {
await db.comment.delete({ where: { id: req.params.id } });
res.json({ success: true });
});
// ✅ Verify ownership before mutation
app.delete("/api/comments/:id", authenticate, async (req, res) => {
const comment = await db.comment.findUnique({
where: { id: req.params.id },
});
if (!comment) {
return res.status(404).json({ error: "Not found" });
}
if (comment.authorId !== req.user.id) {
return res.status(403).json({ error: "Forbidden" });
}
await db.comment.delete({ where: { id: req.params.id } });
res.json({ success: true });
});Nutze bei komplexen Anwendungen eine dedizierte Autorisierungsbibliothek (Casbin, Permit.io oder eine selbst gebaute RBAC-Lösung) statt verstreuter, ad hoc eingefügter if-Prüfungen im gesamten Code.
Schwachstelle 4: unsicheres Secrets-Management
Secrets im Code werden früher oder später geleakt. Jeder API-Schlüssel, der jemals in ein Repository gecommittet wurde, sollte als kompromittiert gelten.
# ❌ Hard-coded secrets
DATABASE_URL="postgresql://admin:password123@prod-db/app"
STRIPE_SECRET_KEY="sk_live_..."
# ✅ Environment variables — never committed
echo ".env.local" >> .gitignore
echo ".env*.local" >> .gitignore// ✅ Validate secrets at startup, fail fast if missing
const requiredEnvVars = [
"DATABASE_URL",
"JWT_SECRET",
"STRIPE_SECRET_KEY",
] as const;
for (const key of requiredEnvVars) {
if (!process.env[key]) {
throw new Error(`Missing required environment variable: ${key}`);
}
}Verwende in der Produktion einen Secrets-Manager (AWS Secrets Manager, Doppler, HashiCorp Vault). Rotiere jeden Schlüssel, der jemals in ein Repository gecommittet wurde – selbst wenn der Commit später rückgängig gemacht wurde.
Schwachstelle 5: schwache Authentifizierung
JWT-Secrets mit Standardwerten, Sitzungen, die nie ablaufen, im Klartext gespeicherte Passwörter – das sind die Authentifizierungsmuster, die zu Sicherheitsvorfällen führen.
// ❌ Weak JWT configuration
const token = jwt.sign({ userId }, "secret"); // No expiry, weak secret
// ✅ Proper JWT configuration
const token = jwt.sign(
{ userId, iat: Math.floor(Date.now() / 1000) },
process.env.JWT_SECRET!, // Strong, random secret from env
{
expiresIn: "15m", // Short-lived access tokens
issuer: "myapp.com",
audience: "api.myapp.com",
},
);
// ✅ Password hashing — always use bcrypt or argon2
import { hash, verify } from "argon2";
async function hashPassword(password: string): Promise<string> {
return hash(password, {
type: argon2.argon2id,
memoryCost: 19456,
parallelism: 1,
timeCost: 2,
});
}
async function verifyPassword(
hash: string,
password: string,
): Promise<boolean> {
return verify(hash, password);
}Checkliste für Sicherheits-Header
// next.config.ts — minimum security headers for any web app
const securityHeaders = [
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=()",
},
{
key: "Content-Security-Policy",
value: [
"default-src 'self'",
"script-src 'self' 'nonce-{NONCE}'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self' https://api.myapp.com",
].join("; "),
},
];Prüfe deine Anwendung vor dem Launch mit securityheaders.com und OWASP ZAP. Nimm diese Prüfungen in deine CI-Pipeline auf.
Bei Sicherheit geht es nicht darum, paranoid zu sein. Es geht darum zu verstehen, wie deine Anwendung mit nicht vertrauenswürdigen Daten umgeht, und sicherzustellen, dass jeder Pfad bewusst gestaltet ist.


