focused on analysis and countermeasures of automated web attacks. Previously, he worked at the Chrome team at Google with a focus on the Chrome Apps API. His interests include web security, source code analysis, and algorithms. • Sergey Shekyan Principal Engineer at Shape Security, focused on the development of the new generation web security product. Prior to Shape Security, he spent 4 years at Qualys developing their on demand web application vulnerability scanning service. Sergey presented research at security conferences around the world, covering various information security topics. Who We Are
browser layout engine with bells and whistles (PhantomJS -‐ WebKit, SlimerJS -‐ Gecko, TrifleJS -‐ Trident) • Custom software that models a browser (ZombieJS, HtmlUnit) • Selenium (WebDriver API) What Is a Headless Browser and How it Works
Page JavaScript Context Control Callback Injection PageEvent Callbacks are serialized var page = require('webpage').create(); page.open(url, function(status) { var title = page.evaluate(function() { return document.title; }); console.log('Page title is ' + title); });
that can provide certain amount of interaction to reveal web application topology, Automated DOM XSS, CSRF detection • SEO (render dynamic web page into static HTML to feed to search engines) • Reporting, image generation Legitimate uses and how you can benefit
• Login brute force attacks • Click fraud • Bidding wars Web admins tend to block PhantomJS in production, so pretending to be a real browser is healthy choice Malicious Use of Headless Browser
y.o.) • Uses Qt Framework’s QtWebKit wrapper around WebKit • Qt rendering engine • Qt network stack • Qt Cookie Jar, that doesn’t support RFC 2965 • No Media Hardware support (no video and audio) • Exposes window.callPhantom and window._phantom • No sandboxing How It Is Different From a Real Browser
need. ! ! ! ! ! ! ! ! ! • Spoofing Plugin objects inside the PluginArray is tedious and hard. • Websites can actually create a plugin to test it. • CONCLUSION: Not a good idea to spoof plugins. Headless Browser Hide page.onInitialized = function () { page.evaluate(function () { var oldNavigator = navigator; var oldPlugins = oldNavigator.plugins; var plugins = {}; plugins.length = 1; plugins.__proto__ = oldPlugins.__proto__; ! window.navigator = {plugins: plugins}; window.navigator.__proto__ = oldNavigator.__proto__; }); };
PhantomJS. Camel case in some header values is also a good point to look at. Headless Browser Seek PhantomJS 1.9.7 GET / HTTP/1.1 User-‐Agent: Accept: Connection: Keep-‐Alive Accept-‐Encoding: Accept-‐Language: Host: Chrome 37 GET / HTTP/1.1 Host: Connection: keep-‐alive Accept: User-‐Agent: Accept-‐Encoding: Accept-‐Language:
defined in PhantomJS prior to version 2 Headless Browser Seek (function () { if (!Function.prototype.bind) { console.log("PhantomJS environment detected."); return; } console.log("PhantomJS environment not detected."); })();
defined in PhantomJS prior to version 2 Headless Browser Seek (function () { if (!Function.prototype.bind) { console.log("PhantomJS environment detected."); return; } console.log("PhantomJS environment not detected."); })(); Function.prototype.bind = function () { var func = this; var self = arguments[0]; var rest = [].slice.call(arguments, 1); return function () { var args = [].slice.call(arguments, 0); return func.apply(self, rest.concat(args)); }; };
• Stack trace generated by SlimerJS ! ! • Hmm, there is something common… Headless Browser Seek at querySelectorAll (phantomjs://webpage.evaluate():9:10) at phantomjs://webpage.evaluate():19:30 at phantomjs://webpage.evaluate():20:7 at global code (phantomjs://webpage.evaluate():20:13) at evaluateJavaScript ([native code]) at global code (/Users/sshekyan/Projects/phantomjs/spoof.js:8:14) Element.prototype.querySelectorAll@phantomjs://webpage.evaluate():9 @phantomjs://webpage.evaluate():19 @phantomjs://webpage.evaluate():20
catch (e) { err = e; } if (indexOfString(err.stack, 'phantomjs') > -‐1) { console.log("PhantomJS environment detected."); } else { console.log("PhantomJS environment is not detected."); } Headless Browser Seek It is not possible to override the thrown TypeError generic indexOf(), can be spoofed, define your own
{ QVariant evalResult; QString function = "(" + code + ")()"; evalResult = m_currentFrame-‐>evaluateJavaScript(function, QString("phantomjs://webpage.evaluate()")); return evalResult; } at at at at at evaluateJavaScript ([native code]) at at global code (/Users/sshekyan/Projects/phantomjs/spoof.js:8:14) • Produces
! ! Headless Browser Hide at querySelectorAll (Object.InjectedScript:9:10) at Object.InjectedScript:19:30 at Object.InjectedScript:20:7 at global code (Object.InjectedScript:20:13) at evaluateJavaScript ([native code]) at at global code (/Users/sshekyan/Projects/phantomjs/spoof.js:8:14) TypeError: Cannot read property '0' of null at HTMLDocument.Document.querySelectorAll.Element.querySelectorAll [as querySelectorAll] (<anonymous>:21:5) at <anonymous>:2:10 at Object.InjectedScript._evaluateOn (<anonymous>:730:39) at Object.InjectedScript._evaluateAndWrap (<anonymous>:669:52) at Object.InjectedScript.evaluate (<anonymous>:581:21)
• No sandboxing in PhantomJS • Blindly executing untrusted JavaScript • outdated third party libs (libpng, libxml, etc.) Can possibly lead to: • abuse of PhantomJS • abuse of OS running PhantomJS How to turn a headless browser against the attacker
Document.prototype.querySelectorAll = Element.prototype.querySelectorAll = function () { var err; try { null[0](); } catch (e) { err = e; } if (indexOfString(err.stack, 'phantomjs') > -‐1) { return html; } else { return oldQSA.apply(this, arguments); } }; Headless Browser Seek It is not possible to override the thrown TypeError generic indexOf(), can be spoofed, define your own
don’t use it • Do not run with ‘-‐ -‐web-‐security=false’ in production, try not to do that in tests as well. • Avoid opening arbitrary page from the Internet • No root or use chroot • Use child processes to run webpages • If security is a requirement, use alternatives or on a controlled environment, use Selenium Tips for Using Headless Browsers Safely
Make sure you want to do it. • Do sniff for headless browsers (some will bail out) • Combine several detection techniques • Reject known unwanted user agents (5G Blacklist 2013 is a good start) • Alter DOM API if headless browser is detected • DoS • Pwn Tips For Web Admins