In the midst of "trying to be creative", I thought I should finally pull my finger out and catch up on some work that I haven't had a chance to blog about. Especially as Michele has progressed from bugging me about pushing up this code, to simply ignoring me entirely (still love you dude :P). So I've taken some time over the past couple of weeks to put together my thoughts on abusing dormant tabs (or other running JavaScript contexts) to detect when browsers change network. Not to mention that I presented this at BSidesSF months ago! I have to preface that like most of the code I write, it's fairly hacky, and there's certainly a few bits that aren't as complete as I'd like. And I'm constantly time-poor (nb: I really struggle to open the laptop outside of work these days), so the idea of maintaining this code makes me want to cry. With that said, here it goes.

The idea was simple enough. Why not adjust BeEF's (beef from here on in because .. capitalization fatigue) Autorun Rules Engine (ARE) such that instead of just running a set of modules upon hook, how about we prepare some modules, and wait for the network-context of the browser to change, and THEN run some modules. Even better, how about we try and store the results of those modules locally until the browser returns back to the original context.

This scenario becomes particularly interesting in the context of different networks with altering risk profiles, such as a public network versus a corporate network, or even airgapped networks. From an attacker's perspective, these non-Internet-accessible networks are a juicy target. It's also these sorts of scenarios that really highlight the importance of addressing CSRF vulnerabilities, especially on internal networks.

I've run into some networks where inline security appliances have halted the beef payload from downloading. Sure, bypassing these is not that difficult, such as using TLS, or obfuscating the JS payloads. But it's also in the face of these controls that the dormant-forward method really shines. Imagine hooking a browser whilst it's on a public wifi with no perimeter controls. That tab, if it remains open, will continue to run. If that computer then changes network to a more critical network, there's nothing preventing the already in-memory JS from continuing to run. Even though the SOP is great at preventing JS from interacting freely with other origins, the control doesn't prevent all information from returning to differing origins. We can still gather information based on whether cross-origin requests fail, how quickly they fail. Plus, there's plenty of other 'blind' CSRF attacks that can exploit internally-accessible systems, regardless of the SOP preventing the hooked browser from directly accessing the HTTP responses (See: whatsinmyredis).

But, before we get into the specifics, for those that haven't spent time in beef lately feel free to check out a presentation I was lucky enough to present at CactusCon last year for a quick refresher of WTF beef is..

The History of the Autorun Rules Engine

To try and provide a bit of context of the ARE, we have to delve into the history of beef. Back in the dark ages (when beef was written in PHP), one of the standard features was the autorun configuration. It was simple; when a browser is hooked to beef, run a module. It took a while until the same feature reappeared in the ruby re-write of beef, but it exists. And it's as simple as updating either your global (or module-specific) config.yaml to ensure that in the module definition you have defined autorun: true. You can see this demonstrated over here.

ARE is similar, but provides a lot more 'logic' around when modules run, and how to run multiple modules. The wiki provides a lot more context around ARE, and how to use it. But in short, modules can either be sequentially chained (i.e. they run one after the other) or nested-forward chained (i.e. each module depends on the previous module to have completed properly, and can take the output of the previous module too). The nested-forward example is perfect, in that, it first gathers the internal IP of the browser, then takes that and uses it to configure and run the internal network fingerprinting module.

Dormant-forward Chain Mode

The new dormant-forward chain mode has the following phases:

  1. Setup
  2. Monitor for network changes
  3. Run arbitrary beef modules

The Setup phase itself goes through two steps:

  1. Gather information about the current network I'm on. This is a combination of the existing Get Internal IP WebRTC module, and a new beef service to allow a browser to gather information about its external network. Such as ASN, ISP etc.
  2. Initiate timers to help detect when the network has changed, and prepare subsequent modules

The Monitor phase is composed of methods that run when the browser's network connectivity appears to have changed. This includes checking things like:

  • Are we not offline or online?
  • Are we back on the original network or a new network?
If we're on a new network, then lets kick off the network detection methods and determine if we have queued modules to execute.

Once we've determined we're going to run new modules there are a few configurable options that modify how we execute the modules. This includes:

  • How stealthy do we want to be on the network? i.e. do we want to cache module results locally in the browser until we return to the original network, or do we want to just send the results straight out
  • What are we going to do when we return home? Are we going to just kill all the timers and network-change detection, or do we keep on going
The stealthy parameter directly ties back into wanting to minimize our presense to perimeter network detection devices. With this enabled, we could have a browser hooked, connect to an internal network. Gather information about that network. Wait for a public network, and then send the results back to beef. Any perimeter network detection may not even be aware that there WAS a hooked browser on the network.

The Setup

For our example scenario the setup is like this: Let's assume a mobile browser gets hooked to beef on a public network.

View post on

The browser meets the ARE targeting and is sent the dormant-forward payload.

View post on

.. some time passes ... The browser ends up on a different network.

View post on

Beef modules are run against local subnets, based on the new internal IP of the browser. The modules include ping-sweep on a subset of the local subnet, and then a port scan against discovered hosts. Again, this port scan is only against a sub-set of ports.

View post on

The browser does NOT send the results of these modules back to the beef server.

View post on

.. some more time passes ... The browser returns back to the original network, and sends its cached module responses back to the beef server.

View post on

Here is an example of the ARE JSON file:


Overall I was happy with the proof of concept, especially highlighting risks of devices crossing network boundaries with malicious JS in-memory, in the DOM. There are some loose-ends and a few implementation details which may not make this capability immediately usable by everyone. The biggest issue discussed at Bsides was accurate detection of networks. There were some suggestions to adjust when to send module-data back to beef. For instance, instead of waiting for the original network, perhaps just wait for the network to change to any network, then send the data.

Another issue that needs a bit of work is the new /aslookup capability in beef. The fact this is currently served from beef, and is used to detect the network, may divulge the beef server to detection technology. The idea was to make this capability as small as possible and perhaps allow it to be quickly deployed to Heroku or AWS. This would provide another avenue of obfuscating the location of your beef server from network perimeter devices.

Currently the dormant-forward option can only run either 1 or 2 modules. The original nested-forward mode can run 1+n modules, but I haven't reimplemented the module insertion logic exactly the same, and have been trying to think of nicer ways to accomplish this with JS.

Due to these issues, the code is available in the airgap branch. But, hopefully after a bit more tidying up, this will be available in master.

You can download the presentation from here: [PDF]

The demonstration video is here:

And a recording of my BSidesSF 2017 presentation is available here: