フロントエンドエンジニアのためのセキュリティ対策 ~XSS編~ / #frontkansai 2019

フロントエンドエンジニアのためのセキュリティ対策 ~XSS編~ / #frontkansai 2019

FRONTEND CONFERENCE 2019( https://2019.kfug.jp )でセキュリティ、主にXSSについて話をしました。
demo: https://shisama.dev/xss-test

# Technical Topics
- 3 types of XSS ( Reflected XSS, Stored XSS, DOM based XSS)
- XSS with React
- DOMPurify
- Content Security Policy
- Trusted Types

5cf7e9533a457726cd51232e06c1da9a?s=128

Masashi Hirano

November 02, 2019
Tweet

Transcript

  1. 9.

    https://www.ipa.go.jp/security/vuln/report/vuln2019q3.html ෼ྨ ݄ʙ݄ ಧग़ड෇։͔࢝Βͷྦྷܭ ιϑτ΢ΣΞ੡඼ ݅  ݅ ΢ΣϒαΠτ ݅

     ݅ ߹ܭ ݅  ݅ 8FC͕ιϑτ΢ΣΞʹ ൺ΂ͯഒҎ্ͷಧग़਺ ಧग़݅਺ (2019೥7݄ʙ9݄)
  2. 10.

    https://www.ipa.go.jp/security/vuln/report/vuln2019q3.html ΢ΣϒαΠτͷ੬ऑੑͷछྨผͷಧग़        XSS

    (ΫϩεαΠτɾεΫϦϓςΟϯά) DNSͷઃఆෆඋ SQLΠϯδΣΫγϣϯ HTTPͷෆਖ਼ར༻ σΟϨΫτϦɾτϥόʔαϧ ϑΝΠϧͷޡެ։ ͦͷଞ
  3. 14.

    https://www.owasp.org/index.php/Japan " ΠϯδΣΫγϣϯ 42-ΠϯδΣΫγϣϯɺίϚϯυΠϯδΣΫγϣϯͳͲ " ೝূͷෆඋ Ϣʔβʔͷೝূ৘ใͷ࿙Ӯ " ػඍͳ৘ใͷ࿐ग़ ࡒ຿৘ใ΍ݸਓ৘ใͳͲ઄औɺվ͟Μ

    " 9.-֎෦ΤϯςΟςΟࢀর 99& ֎෦ΤϯςΟςΟʹΑΔϦϞʔτίʔυͷ࣮ߦͳͲ " ΞΫηε੍ޚͷෆඋ ଞͷϢʔβʔͷσʔλ΍ݖݶͷมߋ " ෆద੾ͳηΩϡϦςΟઃఆ ҆શͰͳ͍ઃఆʹΑΔ໰୊ " ΫϩεαΠτεΫϦϓςΟϯά 944 ϒϥ΢β্ͰͷεΫϦϓτ࣮ߦʹΑΔ৘ใ࿙͍͑ͳͲ " ҆શͰͳ͍σγϦΞϥΠθʔγϣϯ ϦϞʔτ͔Βͷίʔυ࣮ߦ " ط஌ͷ੬ऑੑͷ͋Δίϯϙʔωϯτͷ࢖༻ ੬ऑੑͷ͋ΔϥΠϒϥϦͳͲʹΑΔ߈ܸ΍ѱӨڹ " ෆे෼ͳϩΪϯάͱϞχλϦϯά ϞχλϦϯάͷෆඋʹΑΔ߈ܸݕ஌࿙Ε OWASP Top10 2017
  4. 16.

    XSS

  5. 23.

    Reflected XSS (൓ࣹܕXSS) <input type="hidden" name="token" value=#{qs.token} /> <input type="hidden"

    name="token" value=""/><script>alert("XSS")</script><div /> ΫΤϦετϦϯάͷ஋Λͦͷ··HTMLʹ൓ө
  6. 24.

    Reflected XSS (൓ࣹܕXSS) <input type="hidden" name="token" value=#{qs.token} /> <input type="hidden"

    name="token" value=""/><script>alert("XSS")</script><div /> ΫΤϦετϦϯάͷ஋Λͦͷ··HTMLʹ൓ө
  7. 27.

    DOM Based XSS const hash = decodeURIComponent(location.hash.slice(1)); document.querySelector('#result').innerHTML = hash;

    <img src=x onerror=alert('XSS')> ϩέʔγϣϯϋογϡͷ஋Λ JavaScriptͰૠೖ
  8. 28.

    DOM Based XSS const hash = decodeURIComponent(location.hash.slice(1)); document.querySelector('#result').innerHTML = hash;

    <img src=x onerror=alert('XSS')> ϩέʔγϣϯϋογϡͷ஋Λ JavaScriptͰૠೖ
  9. 29.

    DOM Based XSS const hash = decodeURIComponent(location.hash.slice(1)); document.querySelector('#result').innerHTML = hash;

    ιʔε (Source) γϯΫ (Sink) ୅දతͳSink - innerHTML - location.href - document.write - jQuery() ͳͲ ୅දతͳSource - location.hash - location.href - document.referrer - IndexedDB ͳͲ
  10. 30.

    3छྨͷXSS • Reflected XSS • Stored XSS • DOM Based

    XSS ࠷ऴతʹ͸ඃ֐ऀͷϒϥ΢β্Ͱ JavaScript͕࣮ߦ͞ΕΔ
  11. 35.
  12. 36.

    จࣈྻͷΤεέʔϓɾ࡟আॲཧͷ࣮૷ function sanitizer(str) { return str .replace(/&/g, "&amp;") .replace(/</g, "&lt;")

    .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;") ... } ࣮૷͢Δ͕େม ςετ΋େม ࢓༷࿙Ε͍ͯͳ͍͔ෆ҆
  13. 40.

    const App = props => ( <div className="App"> <script>alert(“xss")</script> </div>

    ); React DOM escapes any values by default 944͸ى͖ͳ͍
  14. 41.

    const App = (props) => ( <div className="App"> <a href=“javascript:alert(“xss")">click

    me!</a> </div> ); React DOM escapes any values by default
  15. 42.

    const App = (props) => ( <div className="App"> <a href=“javascript:alert(“xss")">click

    me!</a> </div> ); React DOM escapes any values by default
  16. 43.

    ReactͷXSS • javascript:εΩʔϜͳͲଐੑ஋͸Τεέʔϓ͞Εͳ͍ • Ϣʔβʔ͕ೖྗͰ͖Ε͹XSS͸ൃੜ͢Δ <div className=“App"> <form> <input value={title}

    onChange={onChangeTitle} /> <input value={url} onChange={onChangeUrl} /> </form> <a href={url}>{title}</a> </div>
  17. 44.

    const Link = props => { const protocol = new

    URL(props.url).protocol; const safeUrl = /^https?:/.test(protocol) ? props.url : ""; return <a href={safeUrl}>{props.children}</a>; }; const App = () => { const { url, title, onChangeUrl, onChangeTitle } = useInput(); return ( <div className="App"> <form> <input value={title} onChange={onChangeTitle} /> <input value={url} onChange={onChangeUrl} /> </form> <Link url={url}>{title}</Link> </div> ); };
  18. 45.

    const Link = props => { const protocol = new

    URL(props.url).protocol; const safeUrl = /^https?:/.test(protocol) ? props.url : ""; return <a href={safeUrl}>{props.children}</a>; }; const App = () => { const { url, title, onChangeUrl, onChangeTitle } = useInput(); return ( <div className="App"> <form> <input value={title} onChange={onChangeTitle} /> <input value={url} onChange={onChangeUrl} /> </form> <Link url={url}>{title}</Link> </div> ); }; IUUQ·ͨ͸IUUQTϓϩτίϧͷΈڐՄ͢Δ͜ͱ ͰɺzKBWBTDSJQUz͔Β࢝·ΔจࣈྻΛແ֐Խ <a href="">click me!</a>
  19. 49.

    DOMPurify const untrustedStr = location.hash; // <img src=x onerror="javascript:alert(1)" />

    const trustedStr = DOMPurify.sanitize(untrustedHTML); $('#foo').innerHTML = trustedStr; // <img src=x />
  20. 50.

    DOMPurify const untrustedStr = location.hash; // <img src=x onerror="javascript:alert(1)" />

    const trustedStr = DOMPurify.sanitize(untrustedHTML); $('#foo').innerHTML = trustedStr; // <img src=x /> MPDBUJPOIBTIͷ஋͕ ͦͷ··4JOL͞ΕͨΒ εΫϦϓτ͕࣮ߦ͞ΕΔ
  21. 51.

    DOMPurify const untrustedStr = location.hash; // <img src=x onerror="javascript:alert(1)" />

    const trustedStr = DOMPurify.sanitize(untrustedHTML); $('#foo').innerHTML = trustedStr; // <img src=x /> %0.1VSJGZTBOJUJ[FʹΑΓ ةݥͳจࣈྻ͚ͩআڈ͞ΕΔ
  22. 58.

    ▼ Response Headers content-security-policy: script-src ‘self’ *.trusted.com σΟϨΫςΟϒ ιʔε •

    HTTPϨεϙϯεϔομʔʹ௥Ճ͢Δ • Ϧιʔεͷऔಘઌ΍εΫϦϓτͷ࣮ߦΛࢦఆͨ͠ιʔεͷΈʹ੍ݶͰ͖Δ • script-src ‘self’; img-src *.trusted.comͷ ͷΑ͏ʹෳ਺ઃఆ΋Մೳ script-src ‘self’; img-src *.trusted.com Content Security Policy
  23. 59.

    <meta http-equiv="Content-Security-Policy" content="script-src 'self'"> Content Security Policy • HTMLͷ<meta>λάͰࢦఆͰ͖ΔͷͰϑϩϯτΤϯυ͚ͩͰ΋࣮૷Մೳ •

    <meta>ΑΓઌʹઃఆ͞Εͨ஋ͷ্ॻ͖ෆՄ • <meta>ΑΓઌʹಡΈࠐ·ΕͨϦιʔε΍εΫϦϓτʹ͸ద༻Ͱ͖ͳ͍ • <meta>Ͱ͸ઃఆͰ͖ͳ͍σΟϨΫςΟϒ͕͋Δ e.g. report-only
  24. 60.

    ▼ Response Headers content-security-policy-report-only: script-src ‘self’ *.trusted.com report-uri /csp-report •

    ࣮ࡍͷCSPΛద༻ͤͣʹϨϙʔτ͚ͩૹΔ͜ͱ͕Մೳ • CSPͰڐՄ͞Ε͍ͯͳ͍৔߹ɺϨϙʔτΛPOST͢Δ • CSPͷຊ൪ಋೖલʹӨڹΛ֬ೝ͢Δ͜ͱ͕Մೳ • <meta>λάͰͷઃఆ͸ෆՄ Content-Security-Policy-Report-Only
  25. 61.

    { "csp-report": { "document-uri": "http://example.com/index.html", "referrer": "", "blocked-uri": "http://invalid-cdn.com/js/react.js", "violated-directive":

    "script-src self *.trusted.com*", "original-policy": "script-src 'self' *.trusted.com; report-uri /csp-report/", "disposition": "report" } } Ϩϙʔτͷྫ
  26. 74.

    content-security-policy: script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa’ ‘strict-dynamic’ <script nonce="EDNnf03nceIOfn39fn3e9h3sdfa"> const script = document.createElement('script');

    script.src = ‘/static/js/main.js’; document.head.appendChild(script); </script> CSP Lv.3 strict-dynamic QBSTFSJOTFSUFEͰͳ͍TDSJQUͷಈతੜ੒͕Մೳ ※parser-inserted = HTMLύʔαʔ΍XMLύʔαʔʹΑͬͯૠೖ͞ΕΔ͜ͱ
  27. 78.

    Trusted Types URL String HTML String Script String Scritp URL

    TrustedURL TrustedHTML TrustedScript TrustedScriptURL TrustedTypes จࣈྻΛ҆શͳܕʹม׵ͯ͠ݕূ͢Δ
  28. 80.

    content-security-policy: require-trusted-types-for ‘script’; trusted-types *; script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa’ ‘strict-dynamic’ <script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">

    const script = document.createElement('script'); script.textContent = location.hash.slice(1); document.head.appendChild(script); </script>
  29. 81.

    content-security-policy: require-trusted-types-for ‘script’; trusted-types *; script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa’ ‘strict-dynamic’ <script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">

    const script = document.createElement('script'); script.textContent = location.hash.slice(1); document.head.appendChild(script); </script> 944͸ى͖ͳ͍
  30. 82.

    content-security-policy: require-trusted-types-for ‘script'; trusted-types my-policy; const myPolicy = trustedTypes.createPolicy('my-policy', {

    createHTML: (s) => { return customSanitize(s) }, createURL: (s) => { /* ΤεέʔϓॲཧͳͲ */ }, createScript: (s) => { /* εΫϦϓτ಺༰ͷνΣοΫͳͲ */ }, }) Trusted Types Policy จࣈྻΛ҆શͳܕʹม׵͢ΔϙϦγʔΛੜ੒͢Δ
  31. 83.

    Trusted Types Policy ϙϦγʔʹΑͬͯੜ੒͞Εͨܕ͸࣮ߦՄೳʹͳΔ const hash = decodeURIComponent(location.hash.slice(1)); const trustedHtml

    = myPolicy.createHTML(hash) document.body.innerHTML = trustedHtml; content-security-policy: require-trusted-types-for ‘script’; trusted-types my-policy;
  32. 84.

    const myPolicy = trustedTypes.createPolicy("my-policy", { createHTML: (s) => { return

    DOMPurify.sanitize(s); } }); Trusted Types & DOMPurify const hash = decodeURIComponent(location.hash.slice(1)); const trustedHtml = myPolicy.createHTML(hash) document.body.innerHTML = trustedHtml; <img src=x onerror=“javascript:alert(‘xss’)”/> <img src=x />
  33. 88.

    <script src="https://w3c.github.io/webappsec-trusted-types/dist/es5/trustedtypes.build.js" data-csp="trusted-types my-policy“></script> <script> trustedTypes.createPolicy('my-policy', ...); trustedTypes.createPolicy('unknown', ...); //

    throws document.body.innerHTML = 'foo'; // throws Trusted Types Polyfill npm CDN $ npm install trusted-types import {tt} from 'trusted-types' tt.createPolicy('my-policy', ...);
  34. 89.

    • Demo Page: https://shisama.dev/xss-test/ • GitHub: https://github.com/shisama/xss-test/ • DOM Based

    XSS • CSP • Trusted Types ※devToolsͷconsoleΛ։͖ͳ͕Β֬ೝ͍ͯͩ͘͠͞
  35. 101.

    ҎԼͷ੬ऑੑ͕هࡌ͞Ε͍ͯΔ • ̍ʣ SQL ΠϯδΣΫγϣϯ • ̎ʣ OS ίϚϯυɾΠϯδΣΫγϣϯ •

    ̏ʣ ύε໊ύϥϝʔλͷະνΣοΫʗ σΟϨΫτϦɾτϥόʔαϧ • ̐ʣ ηογϣϯ؅ཧͷෆඋ • ̑ʣ ΫϩεαΠτɾεΫϦϓςΟϯά • ̒ʣ CSRFʢΫϩεαΠτɾϦΫΤετɾ ϑΥʔδΣϦʣ • ̓ʣ HTTP ϔομɾΠϯδΣΫγϣϯ • ̔ʣ ϝʔϧϔομɾΠϯδΣΫγϣ ϯ • ̕ʣ ΫϦοΫδϟοΩϯά • ̍̌ʣόοϑΝΦʔόʔϑϩʔ • ̍̍ʣΞΫηε੍ޚ΍ೝՄ੍ޚͷܽ མ
  36. 102.

    ηΩϡϦςΟνΣοΫʹ࢖͑Δ΋ͷҰྫ • Ϧετ • ΢Σϒ݈߁਍அ࢓༷ • OWASP Cheat Sheet Series

    • HTML5 Security Cheat Sheet • πʔϧ • OWASP ZAP • Vuls • VAddy
  37. 106.

    ࢀߟ • ͦΖͦΖCSP Lv.2 nonce΍Ζ͏ - teppeis blog • Masato

    Kinugawa Security Blog: CVE-2018-5175: FirefoxͰCSPͷstrict-dynamic όΠύε • ҆શͳจࣈྻͰ͋ΔͱܕͰݕূ͢Δ Trusted Types ʹ͍ͭͯ - Jxck • Avoiding XSS in React is Still Hard - javascript-security - Medium • Securing Web Apps with Modern Platform Features (Google I/O ’19)