Attribuez en toute sécurité des entrées non fiables aux propriétés personnalisées CSS sans JavaScript : un guide
P粉356361722
P粉356361722 2023-09-06 22:32:52
0
1
721

Supposons que j'ai un objet de clés de chaîne et de valeurs de chaîne et que je souhaite les écrire en tant que propriétés personnalisées CSS dans du HTML généré par le serveur. Comment puis-je le faire en toute sécurité ?

Ce que j'entends par sécurité

  • Si possible, les déclarations de propriétés personnalisées ne doivent pas provoquer d'erreurs de syntaxe CSS, empêchant le navigateur d'analyser correctement d'autres déclarations de style ou parties du document HTML. Si, pour une raison quelconque, cela n'est pas possible, la paire clé-valeur doit être omise.
  • Plus important encore, cela devrait rendre impossible les scripts intersites.

Pour plus de simplicité, je limiterai les clés pour autoriser uniquement les caractères de la classe [a-zA-Z0-9_-].

En lisant la spécification CSS et en effectuant quelques tests personnels, je pense que vous pouvez faire beaucoup de progrès pour obtenir la valeur en suivant ces étapes :

  • Trouver une chaîne
  • Assurez-vous que chaque citation est suivie d'une autre citation (sans échappement) du même type (" ou '). Si ce n'est pas le cas, supprimez cette paire clé/valeur.
  • Assurez-vous que chaque accolade ouvrante {([字符串外部的 à l'extérieur de la chaîne a une accolade fermante correspondante. Sinon, supprimez cette paire clé-valeur.
  • Utilisez toutes les instances de 3C 转义 < 的所有实例,以及使用 3E 转义 >.
  • Utilisez toutes les instances de 3B; pour vous échapper.

J'ai proposé les étapes ci-dessus sur la base de cette spécification de syntaxe CSS

Pour le contexte, ces propriétés peuvent être utilisées par des styles définis par l'utilisateur que nous insérons ailleurs, mais le même objet est également utilisé comme données de modèle dans le modèle, il peut donc contenir un mélange de chaînes destinées au contenu et de chaînes attendues comme variables CSS. . J'ai l'impression que l'algorithme ci-dessus trouve un bon équilibre entre être très simple sans courir le risque de jeter trop de paires clé-valeur qui pourraient être utiles en CSS (même en permettant de futurs ajouts à CSS, mais je veux m'assurer de ne pas le faire). il ne manque rien


Voici un code JS montrant ce que je veux réaliser. obj 是有问题的对象,而 preprocessPairs est une fonction qui prend l'objet et le prétraite, en supprimant/reformatant les valeurs comme décrit dans les étapes ci-dessus.

function generateThemePropertiesTag(obj) {
  obj = preprocessPairs(obj);
  return `<style>
:root {
${Object.entries(obj).map(([key, value]) => {
  return `--theme-${key}: ${value};`
}).join("\n")}
}
</style>`
}

Alors quand on lui donne un objet comme celui-ci

{
  "color": "#D3A",
  "title": "The quick brown fox"
}

Je veux que le CSS ressemble à ceci :

:root {
--theme-color: #D3A;
--theme-title: The quick brown fox;
}

Bien que --theme-title soit une variable personnalisée assez inutile lorsqu'elle est utilisée en CSS, elle ne casse pas réellement la feuille de style car CSS ignore les propriétés qu'il ne comprend pas.

P粉356361722
P粉356361722

répondre à tous(1)
P粉898107874

Nous pourrions en fait simplement utiliser des expressions régulières et d'autres algorithmes sans avoir à nous appuyer sur un langage spécifique, j'espère que c'est ce dont vous avez besoin.

En déclarant que la clé de l'objet est à l'intérieur [a-zA-Z0-9_-], nous devons analyser la valeur d'une manière ou d'une autre.

Modèle de valeur

Nous pouvons donc le diviser en catégories et voir ce que nous rencontrons (elles peuvent être légèrement simplifiées pour plus de clarté) :

  1. '.*' (chaîne entourée d'apostrophes ; gourmand)
  2. ".*" (Chaîne entre guillemets doubles ; gourmande)
  3. [+-]?d+(.d+)?(%|[A-z]+)? (entier et décimal, pourcentage optionnel ou avec unité)
  4. #[0-9A-f]{3,6}(couleur)
  5. [A-z0-9_-]+ (Mots clés, dénomination des couleurs, "facilité d'entrée", etc.)
  6. ([w-]+)([^)]+) (类似 url()calc() fonction > etc.)

Premier filtre

J'imagine que vous pourriez effectuer un filtrage avant d'essayer d'identifier ces modèles. Peut-être que nous coupons d'abord la chaîne de valeur. Comme vous l'avez mentionné, > 可以在 preprocessPairs() est échappé au début de la fonction car il n'apparaîtra comme aucun des modèles que nous avons ci-dessus. Si vous ne souhaitez pas que les points-virgules non échappés apparaissent n'importe où, vous pouvez également les échapper.

Modèle de reconnaissance

Nous pouvons ensuite essayer d'identifier ces modèles dans values, et pour chaque modèle, nous devrons peut-être réexécuter le filtrage. Nous nous attendons à ce que ces modèles soient séparés par quelques (ou deux) caractères d'espacement.

Cela devrait être bien d'inclure la prise en charge des chaînes multilignes, qui sont une nouvelle ligne échappée.

Local

Nous devons réaliser que nous avons au moins deux contextes à filtrer : HTML et CSS. Quand on est dans l'attribut 元素中包含样式时,输入必须是安全的,同时它必须是有效的 CSS。幸运的是,您没有将 CSS 包含在元素的 style, c'est donc un peu plus facile.

Filtrage basé sur un modèle de valeur

  1. Chaînes entourées d'apostrophes - nous ne nous soucions de rien d'autre que des apostrophes et des points-virgules, nous devons donc trouver des instances sans échappement de ces caractères dans la chaîne et leur échapper
  2. Idem que ci-dessus, utilisez simplement des guillemets doubles
  3. Ça ne devrait poser aucun problème
  4. Ça ne devrait poser aucun problème
  5. En gros, pas de problème
  6. C'est la partie amusante

Les points 1 à 5 seront donc très simples et la plupart des valeurs seront couvertes par un simple filtrage et découpage depuis l'avant. Avec quelques ajouts (je ne sais pas quel impact sur les performances), il pourrait même effectuer des vérifications supplémentaires pour les unités, mots-clés, etc.

Mais par rapport à d’autres points, je pense que le défi relativement plus important est le point 6. Vous pourriez décider de simplement désactiver url() ,让您检查函数的输入,因此例如您可能想要转义分号,甚至可能通过微小的调整再次检查函数内的模式例如对于calc() dans ce style personnalisé.

Conclusion

Dans l’ensemble, c’est mon avis. Avec quelques ajustements à ces expressions régulières, cela devrait compléter ce que vous faites déjà et vous donner autant de flexibilité que possible lors de la saisie de CSS, tout en vous évitant d'avoir à modifier votre code à chaque fois que vous modifiez une fonctionnalité CSS.

Exemple

function preprocessPairs(obj) {
  // Catch-all regular expression
  // Explanation:
  // (                                   Start of alternatives
  //   \w+\(.+?\)|                       1st alternative - function
  //   ".+?(?<!\)"|                     2nd alternative - string with double quotes
  //   '.+?(?<!\)'|                     3rd alternative - string with apostrophes
  //   [+-]?\d+(?:\.\d+)?(?:%|[A-z]+)?|  4th alternative - integer/decimal number, optionally per cent or with a unit
  //   #[0-9A-f]{3,6}|                   5th alternative - colour
  //   [A-z0-9_-]+|                      6th alternative - keyword
  //   ''|                               7th alternative - empty string
  //   ""                                8th alternative - empty string
  // )
  // [\s,]*
  const regexA = /(\w+\(.+?\)|".+?(?<!\)"|'.+?(?<!\)'|[+-]?\d+(?:\.\d+)?(?:%|[A-z]+)?|#[0-9A-f]{3,6}|[A-z0-9_-]+|''|"")[\s,]*/g;

  // newObj contains filtered testObject
  const newObj = {};

  // Loop through all object properties
  Object.entries(obj).forEach(([key, value]) => {
    // Replace <>;
    value = value.trim().replace('<', '\00003C').replace('>', '\00003E').replace(';', '\00003B');

    // Use catch-all regex to split value into specific elements
    const matches = [...value.matchAll(regexA)];

    // Now try to build back the original value string from regex matches.
    // If these strings are equal, the value is what we expected.
    // Otherwise it contained some unexpected markup or elements and should
    // be therefore discarded.
    // We specifically set to ignore all occurences of url() and @import
    let buildBack = '';
    matches.forEach((match) => {
      if (Array.isArray(match) && match.length >= 2 && match[0].match(/url\(.+?\)/gi) === null && match[0].match(/@import/gi) === null) {
        buildBack += match[0];
      }
    });

    console.log('Compare\n');
    console.log(value);
    console.log(buildBack);
    console.log(value === buildBack);

    if (value === buildBack) {
      newObj[key] = value;
    }
  });

  return newObj;
}

Commentez, discutez, critiquez et faites-moi savoir si j'ai oublié d'aborder un sujet qui vous intéresse particulièrement.

Source

Avertissement : je ne suis pas l'auteur, le propriétaire, l'investisseur ou le contributeur des sources mentionnées ci-dessous. Il se trouve que je les utilise pour obtenir des informations.

Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal