Skip to content

Trusted Types for function constructor

Krzysztof Kotowicz edited this page Jun 16, 2020 · 1 revision

In Chrome's Trusted Types implementation there's a bug, that throws upon calling a Function constructor, even if all its arguments are TrustedScript instances:

<meta http-equiv="content-security-policy" content="require-trusted-types-for 'script';">
<body>
<script>
  let opaqueScriptPolicy =
      trustedTypes.createPolicy('test', {
        createScript: opaqueScript => {
          return opaqueScript;
        },
      });

  const expr = 'alert(1)';
  const opaqueExpr = opaqueScriptPolicy.createScript(expr);
  new Function(opaqueExpr)(); // This throws
</script>
</body>

We have developed a temporary workaround - a replacement Function constructor that will create a new function instance via using eval (using a TrustedScript value).

class TrustedFunction {
  static policy = trustedTypes.createPolicy('TrustedFunctionWorkaround', {
    createScript: (_, ...args) => {
       args.forEach( (arg) => {
         if (!trustedTypes.isScript(arg)) {
           throw new Error("TrustedScripts only, please");
         }
       });
       
       // NOTE: This is insecure without parsing the arguments and body, 
       // Malicious inputs  can escape the function body and execute immediately!
       
       const fnArgs = args.slice(0, -1).join(',');
       const fnBody = args.pop().toString();
       const body = `(function anonymous(
       ${fnArgs}
       ) {
       ${fnBody}
       })`;
       return body;
    }
  });

  constructor(...args) {
    return (window || self).eval(TrustedFunction.policy.createScript('', ...args));
  } 
} 

To use it, simply call new TrustedFunction instead of new Function. Note that the input to the function constructor must be trusted, as otherwise the attacker might execute the code at the moment you call the TrustedFunction constructor!

Clone this wiki locally