// Engineering Log

HTTP, HTTPS and TLS: Part 12 — HTTP-level attacks: smuggling, Host header spoofing, CSRF, XSS, SSRF and cache poisoning

Published on 2026-10-21

// Fast route

This article belongs to the topic Servers and infrastructure.

HTTPS protects data in transit, but it knows nothing about what’s inside the request. Most attacks against websites happen over a fully encrypted connection and exploit HTTP itself: how proxies and applications parse the same request differently, how the browser automatically attaches cookies, how the application trusts headers. This part describes the main classes of such attacks — how they work and what to configure to protect against them. Test the described checks only on your own systems or with written permission from the owner.

Request smuggling: proxy and application desynchronization

There is almost always a proxy in front of the application — Nginx, HAProxy, CDN. If the proxy and the application determine differently where one request ends and the next begins, an attacker can “smuggle” the start of another request inside their request.

In HTTP/1.1 the body length is specified by two headers: Content-Length and Transfer-Encoding: chunked. The standard requires that if both are present, Transfer-Encoding is used, but implementations make mistakes:

POST / HTTP/1.1
Host: example.ru
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED

A proxy that looks at Content-Length will pass the entire body to the application. An application that looks at Transfer-Encoding sees the end of the request at 0, and SMUGGLED is considered the start of the next request on the same connection. That “next” request can be concatenated with another user’s request that arrived over the same connection between proxy and application. The result — someone else’s response, cookie theft, bypassing access rules on the proxy.

Variations: Transfer-Encoding: xchunked or a space before the colon that one side ignores and the other does not; discrepancies when translating HTTP/2 to HTTP/1.1 on the proxy (H2.CL, H2.TE); Upgrade: h2c which the proxy passes to the application.

Protection:

  • keep proxies and application servers up to date — these bugs are fixed in each release;
  • the proxy should reject ambiguous requests with both headers or at least remove Content-Length and not forward it; check how your entire stack behaves, not just the proxy;
  • use HTTP/2 between proxy and application where possible — in HTTP/2 length is set by frames, so ambiguities are gone;
  • do not forward Upgrade: h2c to the application.

Host header spoofing

Host is set by the client, and an application that builds links from it trusts user input. A classic example is password reset: the application sends an email with a link https://{Host}/reset?token=…. An attacker requests a victim’s password reset with Host: attacker.example, the victim gets a legitimate email with a link to the attacker’s site, goes there — and the reset token goes to the attacker.

Protection:

  • in Nginx configure a default_server that responds to unknown names with status 444 (close connection), and pass the application only requests with known server_name;
  • in the application set allowed hostnames (in Django — ALLOWED_HOSTS) and build absolute links from settings, not from the header;
  • the same applies to X-Forwarded-Host: do not accept it from clients.
nginx
server {
    listen 80 default_server;
    listen 443 ssl default_server;
    ssl_reject_handshake on;
    return 444;
}

Cache poisoning and cache deception

Cache poisoning. A CDN or proxy caches a response by key — usually scheme, host, and path. If the response depends on a header not included in the key (for example, X-Forwarded-Host ends up in script URLs on the page), an attacker sends one request with a forged header, the cache stores the poisoned page and serves it to all visitors.

Cache deception. The reverse situation: the cache decides that /account/profile.css is a static file based on extension. The application ignores the “extra” suffix and returns the user’s personal page. An attacker sends a victim a link, the victim’s personal page gets into the shared cache, and the attacker reads it from there.

Protection:

  • on personal pages use Cache-Control: private or no-store; the CDN should respect application headers and not cache by extension alone;
  • if the response depends on a header, add it to Vary or to the cache key;
  • the application should return 404 for non-existent paths like /account/profile.css, not serve the /account/profile page.

CSRF: requests on behalf of the user

The browser automatically attaches site cookies to requests regardless of where they originate. If a user is logged into bank.example.ru and then opens an attacker page with a form that auto-submits to https://bank.example.ru/transfer, the request will be sent with the user’s session cookie. This is cross-site request forgery (CSRF).

Protection:

  • SameSite on cookies. With SameSite=Lax (the default in modern browsers) cookies are not sent with POST requests from other sites. Do not set SameSite=None unless necessary;
  • CSRF token — a random value in the form that the server verifies against the session. Present in all popular frameworks;
  • check Origin on state-changing requests: it must match your site;
  • do not change data via GET: SameSite=Lax allows link navigation, and a GET request that changes state will go through.

CORS does not protect from CSRF: it forbids the page from reading the response, but the form request still goes and is executed.

XSS and Content-Security-Policy

Cross-site scripting (XSS) — injecting someone else’s JavaScript into your site’s page: via a comment, username, search parameter printed without escaping. The script runs with site privileges: it reads the page, sends requests with the user’s cookie, tampers with forms.

Defense is layered:

  • escape all user input on output; templating engines do this by default; dangerous spots are where escaping is disabled (|safe, v-html, dangerouslySetInnerHTML, innerHTML);
  • session cookies with HttpOnly — scripts cannot read them;
  • Content-Security-Policy — even if a script is injected, the browser will not execute it if the source is not allowed:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'

Inline scripts are allowed only with a one-time nonce value that the server generates per response and places in <script nonce="r4nd0m">. 'unsafe-inline' in script-src effectively cancels XSS protection. Deploy CSP via Content-Security-Policy-Report-Only, collect violation reports, and enable blocking once reports stop arriving.

SSRF: requests on behalf of the server

If your application fetches something by a user-provided address — an avatar for preview, a webhook, import by URL — an attacker can specify an internal address: http://127.0.0.1:8080/admin, http://10.0.0.5:9200/, the cloud metadata address http://169.254.169.254/. The request will originate from inside your network where there is often no firewall and often no authorization. Cloud metadata can reveal temporary credentials for the cloud account.

Protection:

  • validate the address after name resolution: forbid private, local, and link-local ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1, fc00::/7);
  • validate every redirect: an external address can redirect to an internal one;
  • defend against DNS rebinding: connect to the IP you validated;
  • in clouds enable the metadata protection mode (in AWS — IMDSv2, which requires a token in a header);
  • best is to delegate such requests to a separate service or proxy without access to the internal network.

CRLF injection and response splitting

HTTP/1.1 headers are separated by \r\n. If an application inserts user input into a header unchecked — for example, in Location for a redirect — an attacker can supply %0d%0a and append their own headers or even body: set a cookie, inject a script, poison the cache. Modern frameworks forbid newlines in headers, but custom code and old libraries are vulnerable.

Nginx has its own pitfall: $uri is decoded, and in return 302 https://$host$uri; %0d%0a can inject a header. Use $request_uri — it’s passed in original, encoded form.

Open redirects

An address like https://example.ru/login?next=https://attacker.example that redirects the user after login to a foreign site. This is not necessarily a break-in, but the link starts with your domain and inspires trust in phishing emails, and in OAuth such a redirect can steal a token.

Protection: allow only relative paths on your site in next or addresses from a whitelist. Checking “starts with /” is insufficient: //attacker.example is another site.

Clickjacking

An attacker embeds your site in a transparent frame on top of their page, and the user, thinking they are clicking a button on the attacker’s page, clicks “Delete account” or “Confirm payment” on yours. Protection — forbid embedding:

Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY

Ten-minute self-check

The commands below check the most common issues. Run them only against your own sites; example.ru — your domain, 192.0.2.10 — the IP address of your server.

Unknown Host. A request with a foreign name to your server IP should not reach the application:

bash
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: evil.example' http://192.0.2.10/
# good: 000 (Nginx closed the connection with return 444) or 4xx; bad: 200 with the site page

curl -sk -o /dev/null -w '%{http_code}\n' --resolve evil.example:443:192.0.2.10 https://evil.example/
# good: 000 — handshake rejected (ssl_reject_handshake)

X-Forwarded-Host reflection. A forged name should not appear in links on the page:

bash
curl -s -H 'X-Forwarded-Host: evil.example' https://example.ru/ | grep -c evil.example   # should be 0

Cookie flags. Session cookies should have Secure, HttpOnly and SameSite:

bash
curl -sI https://example.ru/login | grep -i '^set-cookie'

Security headers:

bash
for h in strict-transport-security content-security-policy x-content-type-options \
         referrer-policy x-frame-options permissions-policy; do
  printf '%-28s ' $h; curl -sI https://example.ru/ | grep -qi "^$h:" && echo present || echo MISSING
done

Open redirect. Substitute parameters your site uses for return-after-login (next, return, redirect, url):

bash
curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' 'https://example.ru/login?next=//evil.example'
# bad: redirect to evil.example

CRLF injection:

bash
curl -sI 'https://example.ru/%0d%0aX-Injected:%20yes' | grep -ci '^x-injected'   # should be 0

TRACE method should be disabled:

bash
curl -si -X TRACE https://example.ru/ | head -1   # 405 or 403

Personal pages and cache. Open a page that is available only after login twice with the session cookie and once without it:

bash
curl -sI -b 'session=YOUR_SESSION' https://example.ru/account | grep -iE '^(cache-control|x-cache|cf-cache-status|age)'

The response should include Cache-Control: private or no-store, and the CDN should miss or bypass the cache (MISS, BYPASS, DYNAMIC), without an Age header.

SSRF. If the site has features to fetch by URL (avatar by address, import, webhooks), try URLs http://127.0.0.1:22/ and http://169.254.169.254/latest/meta-data/. The application should refuse, not return content or a connection error with details.

Request smuggling is hard to check manually. There is the HTTP Request Smuggler extension for Burp Suite and the smuggler.py utility; run them on a test environment with the same proxy chain as production.

What to check

  • Proxies and application servers are updated; requests with simultaneous Content-Length and Transfer-Encoding do not reach the application ambiguously.
  • There is a default_server that closes requests with unknown Host.
  • Personal pages are not cached by the CDN.
  • Session cookies: Secure; HttpOnly; SameSite=Lax.
  • There is a CSP without 'unsafe-inline' for scripts and with frame-ancestors.
  • Server-side requests to user-provided addresses do not reach the internal network.
  • Redirect parameters are validated against a whitelist.

For automated testing use OWASP ZAP and Burp Suite: they include scanners for all the attack classes listed. Run them on a test environment — active scanning creates entries, submits forms and can damage data.

// Similar task

If you are dealing with something similar

This article belongs to one of the main working topics. You can keep reading on the topic, go to the homepage to understand what I do, or open the service pages directly.

Article topic

Servers and infrastructure

VPS, Linux, web stack, migrations, hosting, databases, and core operations.

Typical tasks behind this topic

  • Move a site or service to a new server
  • Set up Linux, Nginx, databases, and backups
  • Figure out why the system behaves unstably

// Next step

If you need help with this topic, not just another article, it is better to go straight to the service page. The homepage and topic collection stay available as secondary routes.

Open services

// Reviews

Related reviews

apande

apande

Configuring Nginx and OpenCart

2024-09-07 · ★ 5/5

A very powerful buyer
kireevk

kireevk

Diagnosing Nginx Proxy Manager in a Docker container and resolving the issue

2024-03-15 · ★ 5/5

Settled-in customer

// Contact

Need help?

Get in touch with me and I'll help solve the problem

I reply within one business day (03:00-13:00 GMT)

Или оставьте заявку здесь:

Confirm that you are not a bot.

Write and get a quick reply