Weisen Sie benutzerdefinierten CSS-Eigenschaften ohne JavaScript sicher nicht vertrauenswürdige Eingaben zu: Eine Anleitung
P粉356361722
P粉356361722 2023-09-06 22:32:52
0
1
715

Angenommen, ich habe ein Objekt mit Zeichenfolgenschlüsseln und Zeichenfolgenwerten und möchte diese als benutzerdefinierte CSS-Eigenschaften in einen vom Server generierten HTML-Code schreiben. Wie kann ich das sicher machen?

Was ich mit Sicherheit meine

  • Wenn möglich, sollten benutzerdefinierte Eigenschaftsdeklarationen keine CSS-Syntaxfehler verursachen und den Browser daran hindern, andere Stildeklarationen oder Teile des HTML-Dokuments korrekt zu analysieren. Wenn dies aus irgendeinem Grund nicht möglich ist, sollte das Schlüssel-Wert-Paar weggelassen werden.
  • Noch wichtiger ist, dass dies Cross-Site-Scripting unmöglich machen sollte.

Der Einfachheit halber beschränke ich die Tasten darauf, nur Zeichen der Klasse [a-zA-Z0-9_-] zuzulassen.

Nachdem ich die CSS-Spezifikation gelesen und einige persönliche Tests durchgeführt habe, denke ich, dass Sie große Fortschritte machen können, indem Sie die folgenden Schritte befolgen:

  • Suchen Sie eine Zeichenfolge
  • Stellen Sie sicher, dass auf jedes Anführungszeichen ein weiteres (ohne Escapezeichen versehenes) Anführungszeichen desselben Typs (" oder ') folgt. Wenn dies nicht der Fall ist, verwerfen Sie dieses Schlüssel/Wert-Paar.
  • Stellen Sie sicher, dass jede öffnende Klammer {([字符串外部的 außerhalb der Zeichenfolge eine passende schließende Klammer hat. Wenn nicht, verwerfen Sie dieses Schlüssel-Wert-Paar.
  • Verwenden Sie alle Instanzen von 3C 转义 < 的所有实例,以及使用 3E 转义 >.
  • Verwenden Sie alle Vorkommen von 3B;, um zu entkommen.

Ich habe mir die oben genannten Schritte basierend auf dieser CSS-Syntaxspezifikation ausgedacht

Für den Kontext können diese Eigenschaften von benutzerdefinierten Stilen verwendet werden, die wir an anderer Stelle einfügen. Das gleiche Objekt wird jedoch auch als Vorlagendaten in der Vorlage verwendet, sodass es eine Mischung aus Zeichenfolgen enthalten kann, die als Inhalt gedacht sind, und Zeichenfolgen, die als CSS-Variablen erwartet werden . Ich habe das Gefühl, dass der obige Algorithmus eine gute Balance zwischen Einfachheit schafft, ohne das Risiko einzugehen, zu viele Schlüssel-Wert-Paare wegzuwerfen, die in CSS nützlich sein könnten (und sogar zukünftige Ergänzungen zu CSS zu ermöglichen, aber ich möchte sicherstellen, dass ich das nicht tue). Es fehlt nichts


Hier ist ein JS-Code, der zeigt, was ich erreichen möchte. obj 是有问题的对象,而 preprocessPairs ist eine Funktion, die das Objekt nimmt und es vorverarbeitet, wobei die Werte wie in den obigen Schritten beschrieben entfernt/neu formatiert werden.

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

Wenn man also ein Objekt wie dieses erhält

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

Ich möchte, dass das CSS so aussieht:

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

Obwohl --theme-title eine ziemlich nutzlose benutzerdefinierte Variable ist, wenn sie in CSS verwendet wird, beschädigt sie das Stylesheet nicht wirklich, da CSS Eigenschaften ignoriert, die es nicht versteht.

P粉356361722
P粉356361722

Antworte allen(1)
P粉898107874

我们实际上可能只使用正则表达式和一些其他算法,而不必依赖于一种特定的语言,希望这是您所需要的。

通过声明对象键位于 [a-zA-Z0-9_-] 内,我们需要以某种方式解析值。

价值模式

因此,我们可以将其分为几类,然后看看我们会遇到什么(为了清楚起见,它们可能会稍微简化):

  1. '.*'(用撇号包围的字符串;贪婪)
  2. ".*"(用双引号括起来的字符串;贪婪)
  3. [+-]?\d+(\.\d+)?(%|[A-z]+)?(整数和小数,可选百分比或带单位)
  4. #[0-9A-f]{3,6}(颜色)
  5. [A-z0-9_-]+(关键字、命名颜色、“缓入”等内容)
  6. ([\w-]+)\([^)]+\) (类似 url()calc() 的函数> 等等)

第一次过滤

我可以想象在尝试识别这些模式之前您可以进行一些过滤。也许我们首先修剪值字符串。正如您所提到的, > 可以在 preprocessPairs() 函数的开头进行转义,因为它不会出现为我们上面有的任何模式。如果您不希望在任何地方出现未转义的分号,您也可以转义它们。

识别模式

然后我们可以尝试识别中的这些模式,对于每个模式,我们可能需要再次运行过滤。我们期望这些模式将由一些(或两个)空白字符分隔。

包括对多行字符串的支持应该没问题,这是一个转义的换行符。

语言环境

我们需要认识到我们至少要过滤两个上下文 - HTML 和 CSS。当我们在 元素中包含样式时,输入必须是安全的,同时它必须是有效的 CSS。幸运的是,您没有将 CSS 包含在元素的 style 属性中,因此这会稍微容易一些。

基于值模式的过滤

  1. 由撇号包围的字符串 - 除了撇号和分号之外我们不关心任何东西,因此我们需要在字符串中找到这些字符的未转义实例并对它们进行转义
  2. 同上,只是用双引号
  3. 应该没问题
  4. 应该没问题
  5. 基本没问题
  6. 这是有趣的部分

因此第 1-5 点将非常简单,通过前面的简单过滤和修剪将覆盖大部分值。通过一些添加(不知道对性能有什么影响),它甚至可能会对正确的单位、关键字等进行额外的检查。

但与其他点相比,我认为相对更大的挑战是第 6 点。您可能决定简单地禁止此自定义样式中的 url() ,让您检查函数的输入,因此例如您可能想要转义分号,甚至可能通过微小的调整再次检查函数内的模式例如对于calc()

结论

总的来说,这是我的观点。通过对这些正则表达式进行一些调整,它应该能够补充您已经所做的工作,并为输入 CSS 提供尽可能多的灵活性,同时使您不必在每次调整 CSS 功能时都调整代码。

示例

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;
}

请评论、讨论、批评,如果我忘记触及您特别感兴趣的某个话题,请告诉我。

来源

免责声明:我不是以下提到的来源的作者、所有者、投资者或贡献者。我只是碰巧用它们来获取一些信息。

Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage