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
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
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/>`;
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
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);
▪ 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
▪ 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
▪ 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
Types of Information Stored 13 Structured Data (108) JSON/JS Objects Code Caching (101) HTML/JS code Unstructured Data (214) No code/apparent structure
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
▪ 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>
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
▪ 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
▪ 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
▪ 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
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
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?
▪ 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;
▪ 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;
▪ 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);
▪ 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);
▪ 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);
▪ 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
▪ 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
▪ 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
▪ 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
▪ 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
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
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
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
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
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?