Ecma TC39 规范提议 - Literals in script

Ecma TC39 规范提议 - Literals in script

动机

HTML 的 DOM (文档对象模型)提供了一系列的机制将任意字符串转换为标签(.innerHTML = ...)或代码(scriptEl.innerText =..., el.onclick = ..., etc)。每一种机制都能触发 XSS 攻击,攻击者可以将代码插入到我们不期望的上下文中,导致基于 DOM 的 XSS 攻击,那正是我们一直试图避免的。

一种解决此问题的好方法(谷歌也在使用)是移除基于字符串的 API ,而是使用强类型的接口,它会在应用程序进入时进行强制的检测和过滤。如果开发人员将自己置身于这种体系之中,他们将不必深入审查每种 XSS 接收器的用法,从而有更多的精力去关注生成类型对象的代码,如: SafeHtml 或 SafeUrl 。可信类型的策略就是为了做到这点。

在大多数情况下,这种机制可以完全可以在 DOM 和 WebIDL(web接口定义语言) 中实现,不需要触及底层的语言。然而,谷歌内部的安全审查员发现,如果没有语言层面的 hook ,很难在网络上普及。

Closure 编译器可以分辨作为文本嵌入到程序中的字符串,以及作为某些操作(方法调用,属性获取等)的结果的字符串。它强制约束类 goog.string.Const ,从而保证对象只能由文本创建,这在谷歌的生产代码中是相当常见的,也被证明是安全的(在没有直接注入脚本的情况下攻击者是不能控制文本的值的)。

也就是说,开发人员可以先创建一个 goog.string.Const,接着生成一个 SafeUrl 对象,然后在工厂方法中使用:

const url = goog.string.Const.from("https://safe.test/totally/safe/url");
return SafeUrl.fromConstant(url);

如果能在平台上应用这个断言,而不是完全依赖基于时间的检测,那将是十分有用的。客户端的 断言能够提高检测的稳健性,进行深度的防御,从而建立一个安全的网络,尤其是代码在程序发布后不经编译偷偷溜走的情况下。

提议

我没有足够的语言背景来给出可靠的建议。相反,我有上面的用例,以及我想从平台方面看到的模糊草图。我很乐意从真正了解这方面语言特性的人那里得到反馈。

考虑到这一点:关于可能合理也可能不合理的猜测如下!

文本字符串类型

一种方法是使用一种与文本字符串相对应的新的字符串类型,WebIDL 可以在其之上建立一种新的字符串类型,以便在执行类型检查时区分文本类型。也就是说,今天我们可以生成如下的WebIDL 代码片段:

interface TrustedHTML {
 static TrustedHTML escape(DOMString html);
}; 

这个接口可以这样调用:TrustedHTML.escape("Literalstring!"),并且 调用TrustedHTML.escape(formField.value) 可以根据需要转义字符串。

理想情况下我们可以添加如下内容:

interface TrustedHTML { 
 static TrustedHTML createFromLiteral(LiteralString html);
}; 

这个接口可以这样调用:TrustedHTML.createFromLiteral("Literalstring!"),但调用TrustedHTML.createFromLiteral(formField.value) 会抛出一个 TypeError。

理想情况下,我们还可以将文字与其他文字(在代码库中常见的线宽限制)结合使用。也就是说,我们会允许以下内容:

TrustedHTML.createFromLiteral("A marginally longer literal string that seems to keep going " +
                             "and going and going and going. Wow, what a long string.");

同样理想的情况下,我们会跟踪字符串的字面意思。也就是说,我们会允许以下内容:

let a = "Literal string!";
TrustedHTML.createFromLiteral(a);

let b = "Another literal!";
TrustedHTML.createFromLiteral(a + b);

let c = a + b;
TrustedHTML.createFromLiteral(c);

限制标签的功能

另一方法是在标签模板字符串上建立类似的体系,也就是说,可以想象如下代码:

function trustedUrlizer(templateString) {
 return TrustedUrl.unsafelyCreate(templateString);
}

return trustedUrlizer`https://safe.test/totally/safe/url`;

如果我们也有一种机制使标签函数仅接收模板字符串,这将是很好的,也就是说,trustedUrlizer(formField.value) 将会执行失败 或许还会抛出类型错误。

Daniel Ehrenberg 在 mikewest/tc39-proposal-literals#2 中也稍微提了一下:

我想象 API 的表层是这样的:模板标签的第一个参数有个额外的属性:文本,它表明了传入模板的参数是否是个文本类型。我们可以通过将其表示为一个内部插槽,并将其文本作为一个自己的getter来使其变为不可伪造,这可以防止攻击者使用任何具有literal: true属性的旧对象调用您的模板:

// Un-monkey-patchable way to get the getter
let getLiteral = Object.getOwnPropertyDescriptor((_ => _)``, "literal").get; 

function literalString(strings, ...keys) {
 if (!getLiteral.call(strings)) throw new Error();
 return String.raw(strings, ...keys);
}

这个 literalString 模板标签就像 String.raw ,但是如果不传入文本的话就会抛出异常。它输出一个文本类型的字符串。这或许是值得推崇的方式来调用需要文本字符串的方法。因为strings对象(及其内部raw对象)被冻结,所以不能破坏文本字符串内容。

为此,我只补充一点,我们希望确保文本在 WebIDL 中被使用的,以便我们对内置标签函数进行强制的检测,但这好像超出了我们今天讨论的范围。

常见问题

  1. 不能只用 Closure 或其它基于时间的检查来做这件事情吗?
    是的,实际上谷歌正在内部这样做。参与构建过程的人员希望通过在已经完成的构建时间分析的基础上对客户端检查进行分层来使其更健壮。上面提到的TrustedTypes也会受益于fromLiteral构建机制,正如谷歌代码库中的情况表明,这个机制既安全又可用。
  2. 我们需要在多大程度上追踪文本?
    一个很好的问题!我很欣慰! 有一件事我们不需要跟踪,就像使用文字作为对象的关键。也就是说,我非常高兴把{“a”:“value”}和{a:“value”}和obj [“a”] =“value”作为键值相同的值。

现有技术


原文:mikewest/tc39-proposal-literals

编辑于 2017-11-26 17:53