$30 off During Our Annual Pro Sale. View Details »

[BSides NYC] Trusted Types: DOM XSS Protection ...

Ecenaz (Jen) Ozmen
October 22, 2024
6

[BSides NYC] Trusted Types: DOM XSS Protection at Scale

Ecenaz (Jen) Ozmen

October 22, 2024
Tweet

Transcript

  1. Proprietary + Confidential ecenazo@ & yattia@ October 2024 ISE Web

    Trusted Types: DOM XSS Protections at Scale
  2. Proprietary + Confidential Jen Ozmen Software Engineer @ Google Focusing

    on deploying security mitigations to Google’s web services @[email protected] github.com/eozmen410 Who are we?
  3. Proprietary + Confidential DOM XSS is a big problem Trusted

    Types as DOM XSS protection Writing safe code from the start Rolling out Trusted Types for existing applications How to refactor Trusted Types blockers 01 02 03 04 05 Agenda
  4. Proprietary + Confidential XSS 35.6% Non-web issues 49.1% Mobile app

    vulnerabilities Business logic (authorization) Server/network misconfigurations ... Google Vulnerability Reward Program payouts
  5. Proprietary + Confidential • Risk: user input gets interpreted as

    code What is XSS? <p>Description: foobar</p> <p>Description: <script>alert(1337)</script></p> <div onclick="code()"> data <script> code() </script> </div> • Malicious scripts are injected into otherwise benign and trusted websites
  6. Proprietary + Confidential • Allows the attacker to take control

    of the account of the logged in user– the attacker can do anything that the victim can What is XSS? • Client-side Remote code execution (RCE)
  7. Proprietary + Confidential DOM XSS is a client-side XSS variant

    caused by many JavaScript APIs not being secure by default. • User controlled strings get converted into code • Many dangerous and error-prone DOM sinks like innerHTML Example: https://example.com/#<img src=x onerror=alert('xss')> How does XSS happen? var foo = location.hash.slice(1); document.querySelector('#foo').innerHTML = foo;
  8. Proprietary + Confidential Element.innerHTML HTMLFormElement.action window.open HTMLAreaElement.href HTMLMediaElement.src HTMLFrameElement.src HTMLSourceElement.src

    HTMLTrackElement.src HTMLInputElement.src location.assign location.href document.write HTMLButtonElement.formAction HTMLFrameElement.srcdoc HTMLImageElement.src HTMLEmbededElement.src HTMLScriptElement.textContent HTMLInputElement.formAction HTMLScriptElement.innerText HTMLBaseElement.href
  9. Proprietary + Confidential • DOM APIs are not secure by

    default ◦ User-controlled strings get turned into code ◦ ~60 APIs can do this • Client-side JavaScript is dynamic and complex ◦ Cannot statically analyze ◦ Sometimes inlined in HTML ◦ JS is getting complicated with complex client-side code • Organizational ◦ Dependencies! ◦ Complexity from too many engineers DOM XSS is a widespread problem
  10. Proprietary + Confidential The idea behind Trusted Types Require strings

    for passing (HTML, URL, script URL) values to DOM sinks. typed objects HTML string Script string Script URL string TrustedHTML TrustedScript TrustedScriptURL becomes
  11. Proprietary + Confidential if(self.trustedTypes && self.trustedTypes.createPolicy){ const myTrustedHTMLPolicy = trustedTypes.createPolicy('my-trusted-html-policy',

    { createHTML: (input) => { // Sanitize the input here if needed return DOMPurify.sanitize(input); } }); element.innerHTML = myTrustedHTMLPolicy.createHTML(value)); } else { element.innerHTML = value; } element.innerHTML = DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: true });
  12. Proprietary + Confidential • See violations in real time ◦

    Tests catch Trusted Types violations too! • Prevent regressions ◦ New functionality that violates Trusted Types -> immediate breakage • Can be added as a static header in the server ◦ See Webpack documentation Enforce Trusted Types early! Content-Security-Policy: require-trusted-types-for 'script' <meta http-equiv="Content-Security-Policy" content="require-trusted-types-for ‘script’" />
  13. Proprietary + Confidential [ { "filePath": "[...]/index.js", "messages": [ {

    "ruleId": "safety-web/trusted-types-checks", "message": "[ban-element-innerhtml-assignments] Assigning directly to Element#innerHTML can result in XSS vulnerabilities.", "line": 15, "column": 1, "messageId": "ban_element_innerhtml_assignments", "endLine": 15, "endColumn": 48 } ] } ] github.com/google/safety-web web.dev/articles/trusted-types#fix-violations element.innerHTML = value;
  14. Proprietary + Confidential element.innerHTML = value; import {sanitizeHtml} from 'safevalues';

    import {safeElement} from 'safevalues/dom'; ... const safeHtmlValue = sanitizeHtml(value); ... safeElement.setInnerHtml(element, safeHtmlValue); github.com/google/safevalues/blob/main/src/dom/xss-d om-remediation.md
  15. Proprietary + Confidential • Use recommended frameworks! • Enforce early,

    shift left! ◦ Enforcement behavior as a part of the application development cycle ◦ Static Analysis • The right tools for the job Summary
  16. Proprietary + Confidential • 0 DOM XSS reported on products

    with Trusted Types enforcement (~400+ applications) • Many of your favorite Google products– Gemini, Google Photos, Google Docs, Gmail etc. • Enforcement-by-default on new products (built on our modern web framework) Trusted Types Success Story at Google
  17. Proprietary + Confidential • Making an existing web application Trusted

    Types compatible is difficult… • because we follow a comprehensive refactoring approach with our first-party applications • …but still worth it, especially because of the refactoring & runtime guarantees A word of warning…
  18. Proprietary + Confidential 5 Enforcem ent M ode 4 Repeat

    2 and 3 as necessary 3 Refactor blockers C ollect and Triage Violations 2 1 Report-O nly M ode Rollout Steps
  19. Proprietary + Confidential Always escape/sanitize user inputs import {htmlEscape} from

    'safevalues'; const html = htmlEscape('<img src=a onerror="javascript:alert()">'); // SafeHtml{'&lt;img src=a onerror=&quot;javascript:alert()&quot;&gt'} Use safevalues! github.com/google/safevalues/blob/main/src/dom/xss-dom-remediation.md
  20. Proprietary + Confidential Not markup? Use safe DOM APIs element.innerHTML

    = "some text"; // TT violation element.textContent = "some text"; // no violation!
  21. Proprietary + Confidential Constant values Or inline policy for the

    constant value: import {safeScript} from 'safevalues'; import {safeScriptEl} from 'safevalues/dom'; const myScript = safeScript`return foo;`; safeScriptEl.setTextContent(scriptEl, myScript); let policy = self.trustedTypes.createPolicy('constantFooPolicy', { createHTML : (_ignored) => 'return foo;', }); scriptEl.text = policy.createHTML(''); With safevalues:
  22. Proprietary + Confidential Can’t refactor? Use a legacy conversion import

    {legacyUnsafeResourceUrl} from 'safevalues/restricted/legacy'; import {safeScriptEl} from 'safevalues/dom'; safeScriptEl.setSrc(script, legacyUnsafeResourceUrl(url));
  23. Proprietary + Confidential Trace the source of your strings! •

    Type information and typed objects as close to the source as possible • Easier to reason about where the strings come from
  24. Proprietary + Confidential Or a Reviewed conversion import {safeScriptEl} from

    'safevalues/dom'; import {scriptSafeByReview} from 'safevalues/restricted/reviewed'; if (currentUser.ROLE === 'ADMIN') { const scriptText = scriptSafeByReview( userInput, `Codepath only allowed for admins.`); safeScriptEl.setSrc(scriptEl, scriptText); }
  25. Proprietary + Confidential • Many OSS libraries (React recaptcha, echarts,

    draggable …) cause violations • Ideally: Patch upstream and upgrade our code: ◦ We submitted PRs on GitHub! ◦ Benefits: Help the community, clean integration with the product • However: ◦ PRs still pending the approval of the owners (discussion about TT and the change) ◦ Cannot upgrade certain libs easily ◦ Transitive dependency • Solution: ◦ Setup a private fork / vendoring (just like a monorepo!) ◦ Patch the violations ourselves and push them in our private fork Third Party Code
  26. Proprietary + Confidential • Default policy is a fallback that

    allows to fix violations without having to update a dependency we don’t have access to • Default policy: TT policy applied to ALL the inputs for a specific violation type What is a default policy? trustedTypes.createPolicy('default', { createHTML: (string) => { return DOMPurify.sanitize(string); } })
  27. Proprietary + Confidential • We don’t want to make TT

    silent for the app: Use the return null technique (spec) Using the default policy for static inputs const jqueryPatterns = [ "<a href='#'></a>","<textarea>x</textarea>", ... ]; trustedTypes.createPolicy('default', { createHTML: (string) => { if (jqueryPatterns.includes(string)) { return string; } return null; } })
  28. Proprietary + Confidential Pros Cons • Less refactoring work compared

    to the comprehensive approach • Not blocked by violations from external dependencies • Does not offer protection to non-compatible browsers • As vulnerable as the HTML sanitizer configurations • Need to configure the HTML sanitizer (lots of custom attributes in practice) • Could lead to silent behavioral changes not surfaced as violation reports Caveats of the Default Policy
  29. Proprietary + Confidential • DOM XSS is a prevalent problem

    in web security • Trusted Types provides a robust layer of defense against this threat • Trusted Types deployment is difficult … but worth it! ◦ 0 DOM XSS* in your application ◦ Path to enforcement increases security • Trusted Types deployment with many dependencies is definitely possible. • Please patch and upstream OSS libraries that you find! ◦ The ecosystem is stronger when we all work together Conclusions