Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Don't trust the Locals: Exploiting Persistent Client-Side Cross-Site Scripting in the Wild

Marius Steffens
September 27, 2019

Don't trust the Locals: Exploiting Persistent Client-Side Cross-Site Scripting in the Wild

Talk about the threat of persistent client-side XSS, held at the OWASP Global AppSec in Amsterdam.

Marius Steffens

September 27, 2019
Tweet

Other Decks in Research

Transcript

  1. Don't Trust the Locals: Exploiting Persistent Client-Side Cross-Site Scripting in

    the Wild Marius Steffens & Ben Stock CISPA Helmholtz Center for Information Security Based on joint work with Martin Johns and Christian Rossow
  2. Marius Steffens and Ben Stock - Don't Trust the Locals

    about:us 2 ▪ Marius Steffens ▪ PhD student since October 2018 ▪ Member of the Secure Web Applications Group ▪ @steffens_marius ▪ Ben Stock ▪ Tenure-Track Faculty at CISPA ▪ Head of the Secure Web Applications Group ▪ @kcotsneb
  3. Marius Steffens and Ben Stock - Don't Trust the Locals

    mysql_query("INSERT INTO posts ..."); // .. $res = mysql_query("SELECT * FROM posts"); while ($row = mysql_fetch_array($res)) { print $res[0]; } Dimensions of Cross-Site Scripting 3 Reflected XSS Persistent XSS DOM-based XSS echo "Welcome ". $_GET[“name”]; document.write("Welcome " + location.hash.slice(1));
  4. Marius Steffens and Ben Stock - Don't Trust the Locals

    Dimensions of Cross-Site Scripting echo "Welcome ". $_GET[“name”]; document.write("Welcome " + location.hash.slice(1)); mysql_query("INSERT INTO posts ..."); // .. $res = mysql_query("SELECT * FROM posts"); while ($row = mysql_fetch_array($res)) { print $res[0]; } localStorage.setItem("name", location.hash.slice(1)); // .. document.write("Welcome " + localStorage.getItem("name")); Reflected Persistent Server Client 4
  5. Marius Steffens and Ben Stock - Don't Trust the Locals

    Dimensions of Cross-Site Scripting echo "Welcome ". $_GET[“name”]; document.write("Welcome " + location.hash.slice(1)); mysql_query("INSERT INTO posts ..."); // .. $res = mysql_query("SELECT * FROM posts"); while ($row = mysql_fetch_array($res)) { print $res[0]; } localStorage.setItem("name", location.hash.slice(1)); // .. document.write("Welcome " + localStorage.getItem("name")); Reflected Persistent Server Client 4
  6. Marius Steffens and Ben Stock - Don't Trust the Locals

    Client-Side Persistence Mechanisms Cookies - limited character set • e.g., no semicolon • only 4096 chars 5
  7. Marius Steffens and Ben Stock - Don't Trust the Locals

    Client-Side Persistence Mechanisms Cookies - limited character set • e.g., no semicolon • only 4096 chars - bound to eTLD+1 or hostname - http://foo.bank.com -> https://bank.com -> https://bar.bank.com 5
  8. Marius Steffens and Ben Stock - Don't Trust the Locals

    Client-Side Persistence Mechanisms Cookies - limited character set • e.g., no semicolon • only 4096 chars - bound to eTLD+1 or hostname - http://foo.bank.com -> https://bank.com -> https://bar.bank.com 5 let username = getCookie('username'); let elem = document.getElementById('logout'); elem.innerHTML = `<h2> Logout ${username}<h2/>`;
  9. Marius Steffens and Ben Stock - Don't Trust the Locals

    Client-Side Persistence Mechanisms (cont.) Web Storage - Comes in two flavors: • Session and Local Storage 6
  10. Marius Steffens and Ben Stock - Don't Trust the Locals

    Client-Side Persistence Mechanisms (cont.) Web Storage - Comes in two flavors: • Session and Local Storage - bound to origin - https://foo.bank.com -> https://foo.bank.com 6
  11. Marius Steffens and Ben Stock - Don't Trust the Locals

    Client-Side Persistence Mechanisms (cont.) Web Storage - Comes in two flavors: • Session and Local Storage - bound to origin - https://foo.bank.com -> https://foo.bank.com 6 let libCode = localStorage["cachedLib"]; eval(libCode);
  12. Marius Steffens and Ben Stock - Don't Trust the Locals

    Client-Side Persistence Mechanisms (cont.) Indexed DB - NoSQL storage shared with Service Worker - Databases, Object Stores, Transactions, … 7
  13. Marius Steffens and Ben Stock - Don't Trust the Locals

    Client-Side Persistence Mechanisms (cont.) Indexed DB - NoSQL storage shared with Service Worker - Databases, Object Stores, Transactions, … - bound to origin - https://foo.bank.com -> https://foo.bank.com 7
  14. Marius Steffens and Ben Stock - Don't Trust the Locals

    Client-Side Persistence Mechanisms (cont.) Indexed DB - NoSQL storage shared with Service Worker - Databases, Object Stores, Transactions, … - bound to origin - https://foo.bank.com -> https://foo.bank.com 7 let proto = getIDBentry('database', 'objectStore', 'proto'); let sc = document.createElement('script'); sc.src = `${proto}//script.com/lib.js`; document.body.appendChild(sc);
  15. Marius Steffens and Ben Stock - Don't Trust the Locals

    From Persistence to Code Execution 8 Cookies Local Storage HTML JavaScript Script Resource
  16. Marius Steffens and Ben Stock - Don't Trust the Locals

    From Persistence to Code Execution 8 Cookies Local Storage HTML JavaScript Script Resource
  17. Marius Steffens and Ben Stock - Don't Trust the Locals

    From Persistence to Code Execution 8 Cookies Local Storage HTML JavaScript Script Resource IDB Duck Typing
  18. Marius Steffens and Ben Stock - Don't Trust the Locals

    Collection of Flows <script> let stored = localStorage.getItem("user_id"); eval("user='" + stored + "'"); </script> key: user_id value: foo 9
  19. Marius Steffens and Ben Stock - Don't Trust the Locals

    <script> let stored = localStorage.getItem("user_id"); eval("user='" + stored + "'"); </script> Automated Exploit Generation 10 eval("user='foo'"); key: user_id value: foo
  20. Marius Steffens and Ben Stock - Don't Trust the Locals

    <script> let stored = localStorage.getItem("user_id"); eval("user='" + stored + "'"); </script> Automated Exploit Generation 10 eval("user='foo'"); key: user_id value: foo
  21. Marius Steffens and Ben Stock - Don't Trust the Locals

    <script> let stored = localStorage.getItem("user_id"); eval("user='" + stored + "'"); </script> Automated Exploit Generation 10 eval("user='foo'"); key: user_id value: ';alert('XSS');// eval("user = '';alert('XSS');//'"); key: user_id value: foo
  22. Marius Steffens and Ben Stock - Don't Trust the Locals

    Validation of Exploitability key: user_id value: ';alert('XSS');// 11
  23. Marius Steffens and Ben Stock - Don't Trust the Locals

    Validation of Exploitability key: user_id value: ';alert('XSS');// 11
  24. Marius Steffens and Ben Stock - Don't Trust the Locals

    Empirical Study (using Tainted Chrome) 12
  25. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Found 1,946 out of 5,000 domains making use of stored data in their application ▪ 1,645 cookies, 941 localStorage Empirical Study (using Tainted Chrome) 12
  26. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Found 1,946 out of 5,000 domains making use of stored data in their application ▪ 1,645 cookies, 941 localStorage ▪ Found 418 domains with exploitable data flow ▪ 213 (13%) cookies, 222 (24%) localStorage Empirical Study (using Tainted Chrome) 12
  27. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Found 1,946 out of 5,000 domains making use of stored data in their application ▪ 1,645 cookies, 941 localStorage ▪ Found 418 domains with exploitable data flow ▪ 213 (13%) cookies, 222 (24%) localStorage Empirical Study (using Tainted Chrome) 12 Developers put trust into integrity of persisted values
  28. Marius Steffens and Ben Stock - Don't Trust the Locals

    Types of Information Stored 13 Unstructured Data (214) No code/apparent structure
  29. Marius Steffens and Ben Stock - Don't Trust the Locals

    Types of Information Stored 13 Structured Data (108) JSON/JS Objects Unstructured Data (214) No code/apparent structure
  30. Marius Steffens and Ben Stock - Don't Trust the Locals

    Types of Information Stored 13 Structured Data (108) JSON/JS Objects Code Caching (101) HTML/JS code Unstructured Data (214) No code/apparent structure
  31. Marius Steffens and Ben Stock - Don't Trust the Locals

    Types of Information Stored 13 Structured Data (108) JSON/JS Objects Configurations (28) Hostnames Code Caching (101) HTML/JS code Unstructured Data (214) No code/apparent structure
  32. Marius Steffens and Ben Stock - Don't Trust the Locals

    Types of Information Stored 13 Structured Data (108) JSON/JS Objects Configurations (28) Hostnames Code Caching (101) HTML/JS code Unstructured Data (214) No code/apparent structure Real-world exploitability?
  33. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Requirement for successful attack: persisted malicious payload - single infection is sufficient - extracted on every page load Infection Vector 14
  34. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Requirement for successful attack: persisted malicious payload - single infection is sufficient - extracted on every page load Infection Vector: Network Attacker 15 http://cats.com http://bank.com <script> persist() </script>
  35. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Requirement for successful attack: persisted malicious payload - single infection is sufficient - extracted on every page load Infection Vector: Network Attacker 16 http://cats.com http://bank.com HSTS
  36. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Requirement for successful attack: persisted malicious payload - single infection is sufficient - extracted on every page load Infection Vector: Network Attacker 17 http://cats.com http://nx.bank.com <script> persist() </script>
  37. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Requirement for successful attack: persisted malicious payload - single infection is sufficient - extracted on every page load Infection Vector: Web Attacker 18 https://attacker.com https://bank.com?vuln=persist() <script> persist() </script>
  38. Marius Steffens and Ben Stock - Don't Trust the Locals

    Real-World Impact of Vulnerabilities 19
  39. Marius Steffens and Ben Stock - Don't Trust the Locals

    Real-World Impact of Vulnerabilities ▪ 293 domains for Network attacker - lack of HTTPS - no includeSubdomains ▪ 65 domains for Web attacker - reflected CXSS in same origin - lower bound 19
  40. Marius Steffens and Ben Stock - Don't Trust the Locals

    IDB exploitability (using duck typing) 21
  41. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ 1,439 / 10,000 sites use IDB somehow - 573 to test if IDB is around (for fingerprinting) - Of the rest, most through third-party code ▪ We checked IDB for content resembling HTML or JS code - Trivially stupid analysis - parse as JS and see if there is an error - parse as HTML and see if there are elements - Recall 1/4th of previous exploits through code caching, rest in more complex constructs IDB exploitability (using duck typing) 21
  42. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ 1,439 / 10,000 sites use IDB somehow - 573 to test if IDB is around (for fingerprinting) - Of the rest, most through third-party code ▪ We checked IDB for content resembling HTML or JS code - Trivially stupid analysis - parse as JS and see if there is an error - parse as HTML and see if there are elements - Recall 1/4th of previous exploits through code caching, rest in more complex constructs - 60/80 sites with stored code exploitable IDB exploitability (using duck typing) 21
  43. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ 1,439 / 10,000 sites use IDB somehow - 573 to test if IDB is around (for fingerprinting) - Of the rest, most through third-party code ▪ We checked IDB for content resembling HTML or JS code - Trivially stupid analysis - parse as JS and see if there is an error - parse as HTML and see if there are elements - Recall 1/4th of previous exploits through code caching, rest in more complex constructs - 60/80 sites with stored code exploitable IDB exploitability (using duck typing) 21 Even extremely lightweight analysis finds real bugs
  44. Marius Steffens and Ben Stock - Don't Trust the Locals

    Types of Information Stored 22 Structured Data (108) JSON/JS Objects Configurations (28) Hostnames Code Caching (101) HTML/JS code Unstructured Data (214) No code/apparent structure
  45. Marius Steffens and Ben Stock - Don't Trust the Locals

    Types of Information Stored 22 Structured Data (108) JSON/JS Objects Configurations (28) Hostnames Code Caching (101) HTML/JS code Unstructured Data (214) No code/apparent structure So, how can we protect our applications properly?
  46. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ No apparent structure, most likely data - 214 domains - Problem: no sanitization, easy injection of markup Types of Information Stored: Unstructured Data 23 let lastUrl = localStorage['previousUrl']; let elem = document.getElementById('goBackAnchor'); elem.innerHTML = `<a href="${lastUrl}"> Back </a>!"`; localStorage['previousUrl'] = window.location.href;
  47. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ No apparent structure, most likely data - 214 domains - Fix: use context-aware sanitization (e.g. DomPurify) Types of Information Stored: Unstructured Data 24 let lastUrl = localStorage['previousUrl']; let elem = document.getElementById('goBackAnchor'); let domString = `<a href="${lastUrl}"> Back </a>!"`; elem.innerHTML = DOMPurify.sanitize(domString); localStorage['previousUrl'] = window.location.href;
  48. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Either JSON or JS-Objects (single ticks) - 108 domains - Problem: eval to parse objects Types of Information Stored: Structured Data 25 let visitorInfo = localStorage['visitorInfo']; let parsed = eval(visitorInfo); // ... parsed['numberOfPageHits'] += 1; localStorage['visitorInfo'] = JSON.stringify(parsed);
  49. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Either JSON or JS-Objects (single ticks) - 108 domains - Fix: don’t make use of eval to parse objects Types of Information Stored: Structured Data 26 let visitorInfo = localStorage['visitorInfo']; let parsed = JSON.parse(visitorInfo); // ... parsed['numberOfPageHits'] += 1; localStorage['visitorInfo'] = JSON.stringify(parsed);
  50. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Either JS or HTML code - 101 domains - Problem: full trust into integrity of storages Types of Information Stored: Code Caching 27 let code = localStorage['libCode']; eval(code);
  51. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Either JS or HTML code - 101 domains - Fix: cryptographically sign stored code and check signature Types of Information Stored: Code Caching 28
  52. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Either JS or HTML code - 101 domains - Fix: cryptographically sign stored code and check signature let signedCode = localStorage['libCode']; let codeOffset = signedCode.indexOf("\n"); let signature = signedCode.substr(2, codeOffset).trim(); let code = signedCode.substr(e + 1); if (verifySignature(PUBKEY, signature, code)){ eval(code); } Types of Information Stored: Code Caching 28
  53. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Alternative: Service Workers - JavaScript workers running in separate context - Cache out of reach for network and XSS attacker - Can intercept network requests (and respond) Types of Information Stored: Code Caching 29
  54. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Alternative: Service Workers - JavaScript workers running in separate context - Cache out of reach for network and XSS attacker - Can intercept network requests (and respond) self.addEventListener('fetch', function(event) { event.respondWith( cache.match(event.request).then(function(response) { return response || fetchAndCache(event.request); }) ); }); Types of Information Stored: Code Caching 29
  55. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Hostname/protocol of script location - 28 domains - Problem: attacker can point to host under his control Types of Information Stored: Hostnames 30
  56. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Hostname/protocol of script location - 28 domains - Problem: attacker can point to host under his control let host = localStorage['host']; let sc = document.createElement('script'); sc.src = '//${host}/script.js'; document.body.appendChild(sc); Types of Information Stored: Hostnames 30
  57. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Hostname/protocol of script location - 28 domains - Fix: only allow hosts under own control Types of Information Stored: Hostnames 31
  58. Marius Steffens and Ben Stock - Don't Trust the Locals

    ▪ Hostname/protocol of script location - 28 domains - Fix: only allow hosts under own control const allowed_hosts = ['host1.com', 'host2.com']; let host = localStorage['host']; if (allowed_hosts.includes(host)){ let sc = document.createElement('script'); sc.src = '//${host}/script.js'; document.body.appendChild(sc); } Types of Information Stored: Hostnames 31
  59. Marius Steffens and Ben Stock - Don't Trust the Locals

    Summary & Conclusion ▪ Persistent Client-Side XSS is a real threat ▪ One-time infection vectors to gain permanent foothold 32
  60. Marius Steffens and Ben Stock - Don't Trust the Locals

    Summary & Conclusion ▪ Persistent Client-Side XSS is a real threat ▪ One-time infection vectors to gain permanent foothold 32
  61. Marius Steffens and Ben Stock - Don't Trust the Locals

    Summary & Conclusion ▪ Persistent Client-Side XSS is a real threat ▪ One-time infection vectors to gain permanent foothold ▪ Of 1,946 domains using Local Storage or cookies in their application ▪ 418 (22%) with exploitable flow from persistence ▪ End-to-end exploit for 293 (network attacker) and 65 sites (web) 32
  62. Marius Steffens and Ben Stock - Don't Trust the Locals

    Summary & Conclusion ▪ Persistent Client-Side XSS is a real threat ▪ One-time infection vectors to gain permanent foothold ▪ Of 1,946 domains using Local Storage or cookies in their application ▪ 418 (22%) with exploitable flow from persistence ▪ End-to-end exploit for 293 (network attacker) and 65 sites (web) ▪ Dead simple IDB analysis shows 60/80 sites exploitable 32
  63. Marius Steffens and Ben Stock - Don't Trust the Locals

    Summary & Conclusion ▪ Persistent Client-Side XSS is a real threat ▪ One-time infection vectors to gain permanent foothold ▪ Of 1,946 domains using Local Storage or cookies in their application ▪ 418 (22%) with exploitable flow from persistence ▪ End-to-end exploit for 293 (network attacker) and 65 sites (web) ▪ Dead simple IDB analysis shows 60/80 sites exploitable ▪ https://github.com/cispa/persistent-clientside-xss 32
  64. Marius Steffens and Ben Stock - Don't Trust the Locals

    Summary & Conclusion ▪ Persistent Client-Side XSS is a real threat ▪ One-time infection vectors to gain permanent foothold ▪ Of 1,946 domains using Local Storage or cookies in their application ▪ 418 (22%) with exploitable flow from persistence ▪ End-to-end exploit for 293 (network attacker) and 65 sites (web) ▪ Dead simple IDB analysis shows 60/80 sites exploitable ▪ https://github.com/cispa/persistent-clientside-xss ▪DON’T TRUST THE LOCALS! 32
  65. Marius Steffens and Ben Stock - Don't Trust the Locals

    Summary & Conclusion ▪ Persistent Client-Side XSS is a real threat ▪ One-time infection vectors to gain permanent foothold ▪ Of 1,946 domains using Local Storage or cookies in their application ▪ 418 (22%) with exploitable flow from persistence ▪ End-to-end exploit for 293 (network attacker) and 65 sites (web) ▪ Dead simple IDB analysis shows 60/80 sites exploitable ▪ https://github.com/cispa/persistent-clientside-xss ▪DON’T TRUST THE LOCALS! 32 Thank you for the attention. Questions?