Skip to main content

DOM-Clobbering-Angriffe in JavaScript eindämmen

Artikel von

Keshav Malik

7. August 2023

0 Min. Lesezeit

Das Document Object Model (DOM) dient als Schnittstelle zwischen HTML und JavaScript und überbrückt die Kluft zwischen statischen Inhalten und dynamischer Interaktivität. Dadurch ist das DOM für moderne Webentwickler unverzichtbar.

Das DOM birgt jedoch eine Tücke: DOM-Clobbering. DOM-Clobbering tritt auf, wenn HTML-Elemente mit globalen JavaScript-Variablen oder -Funktionen in Konflikt geraten. Das kann zu unerwartetem Verhalten und einer potenziellen Sicherheitslücke in Ihrer Webanwendung führen.

Um dieses Problem zu beheben, sind sorgfältige Programmierpraktiken, fundierte JavaScript-Kenntnisse und ein solides Verständnis der Sicherheit von Webanwendungen erforderlich. Dieser Artikel erläutert das Konzept des DOM-Clobberings und zeigt Strategien für sicherere und robustere Webanwendungen.

Wie DOM-Clobbering entsteht

Jedes HTML-Element kann ein eindeutiges ID- oder name-Attribut haben, damit sich bestimmte Elemente in JavaScript leicht referenzieren lassen. Die folgende HTML-Schaltfläche hat beispielsweise ein ID-Attribut mit dem Wert "myButton":

<button id="myButton">Click Me!</button>

Mit dieser ID können wir die Schaltfläche in unserem JavaScript-Code bearbeiten, indem wir beispielsweise ihren Text ändern, wenn darauf geklickt wird:

document.getElementById('myButton').onclick = function() {
    this.textContent = 'Clicked!';
};

Das DOM ermöglicht diese Interaktion zwischen HTML und JavaScript. Was passiert jedoch, wenn das ID- oder name-Attribut des HTML-Elements mit einer globalen JavaScript-Variablen oder -Funktion in Konflikt steht?

Wenn ein Browser eine HTML-Seite lädt, erstellt er automatisch globale JavaScript-Variablen für jedes ID- und name-Attribut im HTML-DOM. Gibt es ein HTML-Element mit dem Namen "myButton", erstellt der Browser die globale JavaScript-Variable myButton, die auf dieses HTML-Element verweist.

Sehen wir uns nun ein Szenario an, in dem wir eine JavaScript-Funktion namens myButton deklariert haben:

function myButton() {
    // some code
}

Zusätzlich gibt es ein HTML-Element mit der ID oder dem Namen "myButton". Beim Laden der Seite überschreibt der automatische Prozess des Browsers die JavaScript-Funktion myButton mit einer Referenz auf das HTML-Element:

<button id="myButton">Click Me!</button>

console.log(myButton); // This will log the HTML button element, not the function

Dieser Vorgang wird als DOM-Clobbering bezeichnet und kann unvorhersehbares Verhalten sowie Sicherheitslücken verursachen. Kann ein Angreifer die Attribute kontrollieren, kann er möglicherweise bösartigen Code in die Webseite einschleusen. Das kann zu Sicherheitsproblemen wie Cross-Site-Scripting (XSS) führen.

Zur Veranschaulichung betrachten wir das folgende Szenario:

Enter your name: <input id="username" type="text">
<button onclick="greet()">Greet</button>

function greet() {
    var username = document.getElementById('username').value;
    alert(`Hello ${username}`);
}

Ein Angreifer könnte etwa <img id='alert' src=x onerror='alert(1)'> eingeben und so eine neue HTML-Komponente mit der 'id' 'alert' erstellen. Diese Komponente ersetzt – oder überschreibt – die normale JavaScript-Funktion alert. Wenn die Website diese Funktion das nächste Mal aufruft, funktioniert sie nicht ordnungsgemäß und könnte sogar bösartigen Code ausführen.

DOM-Clobbering-Sicherheitslücken erkennen

Betrachten wir eine einfache Webanwendung mit einem Feedback-Formular. Nutzer geben ihren Namen und eine Feedback-Nachricht ein und senden das Formular ab. Die Seite zeigt das Feedback an:

HTML:

<h2>Feedback Form</h2>
<form>
  <label for="name">Name:</label><br>
  <input type="text" id="name" name="name"><br>
  <label for="feedback">Feedback:</label><br>
  <textarea id="feedback" name="feedback"></textarea><br>
  <input type="submit" value="Submit">
</form>

<div id="feedbackDisplay"></div>

JavaScript:

document.querySelector('form').onsubmit = function(event) {
    event.preventDefault();

    let name = document.getElementById('name').value;
    let feedback = document.getElementById('feedback').value;

    let feedbackElement = document.getElementById('feedbackDisplay');

    feedbackElement.innerHTML = `<p><b>${name}</b>: ${feedback}</p>`;
};

Dieser Code nimmt den Namen und das Feedback des Nutzers und zeigt beides in einem Absatzelement innerhalb des feedbackDisplay-Divs an.

Ein Angreifer könnte diesen Code ausnutzen, indem er HTML über das Feedback-Formular übermittelt. Gibt er beispielsweise den folgenden Code in das Feld Name ein und sendet das Formular ab, wird der Feedback-Anzeigebereich durch das script-Tag ersetzt:

<script id="feedbackDisplay">window.location.href='http://maliciouswebsite.com';</script>

Wenn das Formular versucht, das nächste Feedback anzuzeigen, wird stattdessen das Script ausgeführt und der Nutzer auf eine bösartige Website umgeleitet.

Dieses Szenario stellt eine Sicherheitslücke dar, weil die Anwendung vom Nutzer bereitgestellte Eingaben zur Erstellung von HTML verwendet und dabei ein script-Tag mit derselben ID wie ein vorhandenes Element erzeugt. Dies ist ein Beispiel für DOM-Clobbering mit schwerwiegenden Folgen: Der Angreifer kann die Kontrolle über den Browser des Nutzers erlangen und dadurch vertrauliche Daten stehlen oder Malware installieren.

Sichere Programmierpraktiken zur Vermeidung von DOM-Clobbering

Nachdem wir diese Sicherheitslücken genauer betrachtet haben, sehen wir uns einige Best Practices an, mit denen sich das Risiko von DOM-Clobbering verringern lässt.

Variablen und Funktionen korrekt im Gültigkeitsbereich einschränken

Eine der häufigsten Ursachen für DOM-Clobbering ist der unsachgemäße Umgang mit dem globalen Gültigkeitsbereich in JavaScript. Wenn wir Variablen und Funktionen innerhalb eines bestimmten Gültigkeitsbereichs definieren, können wir das Überschreiben auf diesen Bereich und darin verschachtelte Bereiche beschränken und so potenzielle Konflikte minimieren. 

Wenden wir die Gültigkeitsbereichsregeln von JavaScript an und strukturieren wir das vorherige Beispiel um, um zu zeigen, wie das funktioniert:

(function() {
    // All variables and functions are now in this function's scope
    const form = document.querySelector('form');
    const feedbackElement = document.getElementById('feedbackDisplay');

    form.onsubmit = function(event) {
        event.preventDefault();

        const name = document.getElementById('name').value;
        const feedback = document.getElementById('feedback').value;

        // Sanitize user input
        name = DOMPurify.sanitize(name);
        feedback = DOMPurify.sanitize(feedback);

        const newFeedback = document.createElement('p');
        newFeedback.textContent = `${name}: ${feedback}`;
        feedbackElement.appendChild(newFeedback);
    };
})();

Hinweis: Im obigen Codebeispiel haben wir DOMPurify verwendet, um das HTML zu bereinigen. Sie können es in Node.js mit npm install dompurify installieren. Binden Sie es mit <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.3.3/purify.min.js"></script> in Ihr HTML ein.

In dieser Codeversion haben wir alles in eine Immediately Invoked Function Expression (IIFE) eingeschlossen, die einen neuen Gültigkeitsbereich erstellt. Die Variablen form und feedbackElement sowie die dem Event-Handler onsubmit zugewiesene Funktion befinden sich nicht im globalen Gültigkeitsbereich und können daher nicht überschrieben werden.

Eindeutige Bezeichner verwenden

Wenn jedes Element auf unserer Webseite eine eindeutige ID hat, sinkt das Risiko, versehentlich eine wichtige Funktion oder Variable zu überschreiben. Vermeiden Sie außerdem gängige Namen oder Namen, die mit globalen JavaScript-Objekten oder -Funktionen in Konflikt geraten könnten.

Globale Namensraumverschmutzung vermeiden

Ein sauberer globaler Namensraum ist ein wichtiger Aspekt sicherer JavaScript-Programmierung. Je mehr Variablen und Funktionen sich im globalen Gültigkeitsbereich befinden, desto größer ist das Risiko für DOM-Clobbering. Nutzen Sie den Funktions-Gültigkeitsbereich von JavaScript oder den Block-Gültigkeitsbereich von ES6, um Variablen und Funktionen einzuschließen. Hier ein Beispiel mit Letzterem:

    // All variables and functions are now in this block's scope
    let form = document.querySelector('form');
    let feedbackElement = document.getElementById('feedbackDisplay');

    form.onsubmit = function(event) {
        event.preventDefault();

        let name = document.getElementById('name').value;
        let feedback = document.getElementById('feedback').value;

        // Sanitize user input
        name = DOMPurify.sanitize(name);
        feedback = DOMPurify.sanitize(feedback);

        let newFeedback = document.createElement('p');
        newFeedback.textContent = `${name}: ${feedback}`;
        feedbackElement.appendChild(newFeedback);
    };

In diesem Code haben wir mit einem Block (definiert durch {}) einen neuen Gültigkeitsbereich erstellt. Alle Variablen und Funktionen sind jetzt auf diesen Block beschränkt und befinden sich nicht im globalen Gültigkeitsbereich.

Hinweis: Die Codebeispiele in diesem Leitfaden sind zu Demonstrationszwecken vereinfacht und nicht für den Einsatz auf einer Produktionswebsite oder in einer Produktions-App gedacht.

JavaScript-Funktionen zur Eindämmung von DOM-Clobbering nutzen

Modernes JavaScript bietet mehrere Funktionen, mit denen sich das Risiko von DOM-Clobbering verringern lässt. Insbesondere die in ES6 eingeführten Schlüsselwörter let und const bieten mehr Kontrolle bei der Deklaration von Variablen.

Traditionell haben wir JavaScript-Variablen mit dem Schlüsselwort var deklariert. var hat jedoch einige Eigenheiten. Eine davon ist, dass es keinen Block-Gültigkeitsbereich gibt – nur einen Funktions- und einen globalen Gültigkeitsbereich. Das bedeutet, dass auf eine mit var deklarierte Variable auch außerhalb des Blocks zugegriffen werden kann, in dem sie deklariert wurde, und dass sie dort überschrieben werden kann.

let und const haben dagegen beide einen Block-Gültigkeitsbereich. Das bedeutet, dass auf sie nur innerhalb des Blocks zugegriffen werden kann, in dem sie deklariert wurden. Deshalb eignen sie sich häufig besser für Variablendeklarationen, da sie die Möglichkeit verringern, dass Variablen überschrieben werden.

Mit const können wir außerdem Konstanten deklarieren – Werte, die sich nach ihrer Zuweisung nicht mehr ändern lassen. Sie sind ein weiteres nützliches Mittel, um unbeabsichtigte Änderungen an wichtigen Variablen zu verhindern.

Sehen wir uns an, wie wir diese Funktionen in unserem Feedback-Formularcode implementieren können, um DOM-Clobbering zu verhindern:

    const form = document.querySelector('form');
    const feedbackElement = document.getElementById('feedbackDisplay');

    form.onsubmit = function(event) {
        event.preventDefault();

        const name = document.getElementById('name').value;
        const feedback = document.getElementById('feedback').value;

        // Sanitize user input
        name = DOMPurify.sanitize(name);
        feedback = DOMPurify.sanitize(feedback);

        const newFeedback = document.createElement('p');
        newFeedback.textContent = `${name}: ${feedback}`;
        feedbackElement.appendChild(newFeedback);
    };

In diesem Code haben wir alle Vorkommen von var durch const ersetzt. Wir haben alle Variablen auf den Block beschränkt, in dem wir sie deklariert haben, und die Konstanten können nicht überschrieben werden.

Denken Sie daran: Die Verwendung von let und const beseitigt das Risiko von DOM-Clobbering nicht, ist aber dennoch ein entscheidender Bestandteil sicherer Programmierung. Wenn Sie diese JavaScript-Funktionen verstehen und einsetzen, können Sie Ihre Webanwendungen sicherer machen.

DOM-Clobbering-Sicherheitslücken überwachen und erkennen

Sichere Programmierpraktiken können DOM-Clobbering verhindern. Ebenso wichtig ist es jedoch, Sicherheitslücken zu erkennen, sobald sie auftreten. Sehen wir uns einige hilfreiche Tools und Techniken an.

Statische Codeanalyse

Tools für statische Anwendungssicherheitstests (SAST) wie Snyk Code können potenzielle Sicherheitslücken, darunter auch DOM-Clobbering, wirksam erkennen. Diese Tools analysieren Ihren Quellcode, ohne ihn auszuführen, und identifizieren Muster, die zu Sicherheitsproblemen führen könnten. Dank der umfassenden JavaScript-Abdeckung von Snyk ist das Tool eine ausgezeichnete Wahl für Webentwickler.

Browser-Entwicklertools

Browser-Entwicklertools, wie sie in Chrome oder Firefox verfügbar sind, können bei der Erkennung von DOM-Clobbering-Sicherheitslücken eine wertvolle Unterstützung sein. 

Sehen wir uns kurz an, wie Sie mit diesen Tools ein potenzielles Problem erkennen können:

  1. Klicken Sie auf Ihrer Webseite mit der rechten Maustaste auf eine beliebige Stelle und wählen Sie Untersuchen, um die Entwicklertools zu öffnen. Unter Windows können Sie auch Strg+Umschalt+I oder unter macOS Befehlstaste+Optionstaste+I drücken.

  2. Wechseln Sie zum Tab Konsole, in dem JavaScript-Fehler und Protokollmeldungen angezeigt werden.

  3. Geben Sie in der Konsole „window“ ein und drücken Sie Enter, um den globalen Gültigkeitsbereich mit allen globalen Variablen und Funktionen zu untersuchen. Achten Sie auf Variablen, die fehl am Platz wirken, insbesondere auf solche, deren Name mit der ID oder dem Namen eines HTML-Elements übereinstimmt.

  4. Bearbeiten Sie im Tab Elemente das HTML der Seite, um das DOM zu verändern und auf potenzielle Sicherheitslücken zu testen. Fügen Sie beispielsweise ein Element mit einer ID hinzu, die mit einer globalen Variablen oder Funktion übereinstimmt, und prüfen Sie, ob diese überschrieben wird.

Fazit

Dieser Artikel hat erläutert, wie DOM-Clobbering entsteht, welche Gefahren damit verbunden sind und wie Sie sich davor schützen können. Die Variablen korrekt im Gültigkeitsbereich einzuschränken, eindeutige Bezeichner zu verwenden und die JavaScript-Funktionen let und const gezielt einzusetzen, ist entscheidend, um diese Sicherheitslücke einzudämmen. 

Für die Sicherheit des Codes müssen Präventions- und Erkennungsmaßnahmen ausgewogen eingesetzt werden. Mit empfohlenen Programmierpraktiken, Browser-Entwicklertools und einer SAST-Lösung wie Snyk können bestehende und potenzielle Probleme erkannt werden. 

Wenn Sie diese Techniken in Ihre JavaScript-Anwendungen integrieren und sich über aktuelle Trends bei der Sicherheit von Webanwendungen informieren, können Sie Ihre Programmierpraktiken proaktiv absichern.

Snyk für JavaScript-Sicherheit

Von Ihrer ersten Codezeile bis zu Ihrer letzten npm-Abhängigkeit schützt Snyk Ihre JavaScript-Anwendungen direkt in Ihrer IDE, CLI und Ihren Git-Workflows.

Gepostet in:

Weiterlesen

feature insights context
Blog

Autonome Angriffe sind bereits Realität. Die Verteidigung muss Schritt halten.

Autonome Angreifer verkürzen das Zeitfenster für die Verteidigung. Erfahren Sie, wie kontinuierliches Erkennen, Beheben, Validieren und Verhindern Sicherheitsteams helfen kann, Schritt zu halten.

illustration hero ai
Blog

Der KI-Hurrikan ist da

KI beschleunigt gleichermaßen die Softwareentwicklung und Cyberangriffe. Führungskräfte müssen Agents und Code von Anfang an absichern, zur Laufzeit Kontrollen durchsetzen und Abwehrmaßnahmen unabhängig validieren.

feature insights context
Blog

Ist Prävention im Grunde ein gelöstes Problem?

Die Prävention in von Agenten generiertem Code ist architektonisch gelöst – die Herausforderung besteht jedoch weiterhin darin, Kontrollen zu wählen, die die Sicherheit schützen, ohne die Entwicklung zu verlangsamen.