提问人:bigblind 提问时间:5/10/2023 最后编辑:bigblind 更新时间:5/11/2023 访问量:65
如何在没有 JavaScript 的情况下安全地将不受信任的输入分配给 CSS 自定义属性?
How to safely assign untrusted input to CSS custom properties without JavaScript?
问:
假设我有一个字符串键和字符串值的对象,我想将它们作为 CSS 自定义属性写入服务器生成的一些 HTML。我怎样才能安全地这样做?
安全地说,我的意思是
- 如果可能的话,自定义属性声明不应导致 CSS 语法错误,从而阻止浏览器正确解析其他样式声明或 HTML 文档的某些部分。如果由于某种原因无法做到这一点,则应省略键值对。
- 更强烈的是,应该不可能使用它进行跨站点脚本。
为了简单起见,我将限制键以仅允许类中的字符。[a-zA-Z0-9_-]
通过阅读CSS规范和一些个人测试,我认为我可以通过以下步骤获取价值来走得很远:
- 查找字符串
- 确保每个引号后面都跟着另一个相同类型(“或')的(未转义)引号。如果不是这种情况,请放弃此键/值对。
- 确保字符串外的每个左大括号都有一个匹配的右大括号。如果没有,请丢弃此键值对。
{([
- 转义 with 的所有实例和 with 的所有实例。
<
\3C
>
3E
- 转义 with 的所有实例。
;
\3B
我根据这个CSS语法规范想出了上面的步骤
对于上下文,这些属性可以由我们在其他地方插入的用户自定义样式使用,但同一对象也用作模板中的模板数据,因此它可能包含用作内容的字符串和用作 CSS 变量的字符串的混合。我觉得上面的算法在非常简单之间取得了很好的平衡,但也不会冒险丢弃太多可能在 CSS 中有用的键值对(即使考虑将来对 CSS 的添加,但我想确保我没有遗漏一些东西。
这里有一些JS代码,显示了我想要实现的目标。 是有问题的对象,并且是一个函数,它接受对象并对其进行预处理,删除/重新格式化值,如上述步骤所述。obj
preprocessPairs
function generateThemePropertiesTag(obj) {
obj = preprocessPairs(obj);
return `<style>
:root {
${Object.entries(obj).map(([key, value]) => {
return `--theme-${key}: ${value};`
}).join("\n")}
}
</style>`
}
所以当给定这样的对象时
{
"color": "#D3A",
"title": "The quick brown fox"
}
我希望CSS看起来像这样:
:root {
--theme-color: #D3A;
--theme-title: The quick brown fox;
}
虽然在CSS中使用是一个非常无用的自定义变量,但它实际上并没有破坏样式表,因为CSS忽略了它不理解的属性。--theme-title
答:
我们实际上可能只使用正则表达式和其他一些算法,而不必依赖一种特定的语言,希望这是您在这里需要的。
通过声明对象键在 in 中,我们需要以某种方式解析值。[a-zA-Z0-9_-]
值模式
因此,我们可以将其分为几类,看看我们会遇到什么(为了清楚起见,它们可能会稍微简化):
'.*'
(被撇号包围的字符串;贪婪)".*"
(用双引号括起来的字符串;贪婪)[+-]?\d+(\.\d+)?(%|[A-z]+)?
(整数和十进制数,可选百分比或单位)#[0-9A-f]{3,6}
(颜色)[A-z0-9_-]+
(关键字、命名颜色、“ease-in”之类的东西)([\w-]+)\([^)]+\)
(函数,如等)url()
calc()
第一次过滤
我可以想象,在尝试识别这些模式之前,您可以进行一些过滤。也许我们先修剪值字符串。正如你提到的,并且可以在函数的开头进行转义,因为它不会作为我们上面的任何模式的一部分出现。如果你不希望在任何地方出现未转义的分号,你也可以转义它们。<
>
preprocessPairs()
识别模式
然后,我们可以尝试在值中识别这些模式,对于每个模式,我们可能需要再次运行过滤。我们预计这些模式将由一些空格字符(或两个)分隔。
包括对多行字符串的支持应该是可以的,这是一个转义的换行符。
语言语境
我们需要认识到,我们至少过滤了两个上下文 - HTML 和 CSS。由于我们在元素中包含样式,因此输入必须是安全的,同时它必须是有效的 CSS。幸运的是,您没有在元素的属性中包含 CSS,因此这会稍微容易一些。<style>
style
基于值模式的筛选
- 被撇号包围的字符串 - 除了撇号和分号之外,我们什么都不关心,所以我们需要在字符串中找到这些字符的未转义实例并转义它们
- 同上,只是用双引号
- 应该没问题
- 应该没问题
- 差不多还可以
- 这是有趣的部分
因此,第 1-5 点将非常容易,并且使用前面的简单过滤和修剪将涵盖大部分值。通过一些添加(不知道对性能有什么影响),它甚至可以对正确的单位、关键字等进行额外的检查。
但与其他点相比,我看到一个相对更大的挑战是点#6。您可以决定简单地禁止使用此自定义样式,让您检查函数的输入,例如,您可能希望转义分号,甚至可能通过微小的调整再次检查函数内部的模式,例如 for .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;
}
请评论、讨论、批评,如果我忘了触及你特别感兴趣的话题,请告诉我。
来源
免责声明:我不是以下来源的作者、所有者、投资者或贡献者。我只是碰巧用它们来获取一些信息。
- https://www.w3.org/TR/css-values-3
- https://owasp.org/www-project-web-security-testing-guide/v41/4-Web_Application_Security_Testing/11-Client_Side_Testing/05-Testing_for_CSS_Injection
- https://cheatsheetseries.owasp.org/cheatsheets/Securing_Cascading_Style_Sheets_Cheat_Sheet.html
评论
/(\w+\(.+?\)|".+?(?<!\\)"|'.+?(?<!\\)'|[+-]?\d+(\.\d+)?(%|[A-z]+)?|#[0-9A-f]{3,6}|[A-z0-9_-]+|''|"")[\s,]*/g
preprocessPairs()
评论