11 minute read

A couple of weeks ago, I described the Adversary in the Middle (AitM) family of attacks on this post: Starkiller Phishing Kit: Why MFA Fails Against Real-Time Reverse Proxies.

Enticed by the structure of the attack itself, the first thing that came into my mind was: how can we protect a regular internet user? Someone who does not care about “threat agents” and “threat models”, but simply wants to use their machine to pay a parking ticket without giving the credentials of their bank to some weird guy three thousand kilometers away?

Here we go - I started my RustRover, and off I went coding.

So, no macOS hardening today. It’ll be back soon.

Introduction

I always found the idea of writing an extension for a browser a challenge, because:

  • I hate JavaScript. With a passion.
  • I tried several ones - very few have a real meaning.
  • In general, they enlarge attack surfaces.

Nevertheless, with the idea of thwarting an attack aimed at an user that is culturally unprepared against it, and potentially unprotected, the only logical place where the protection could happen is the browser.

Obtorto collo I started reading some documentation. I discovered that the world of browsers is quite heterogeneous and there is nothing such as standards.

Firefox

To avoid the dispersion of my (very little amount, nowadays) resources, I decided to focus only on one browser - the one I like the most: Firefox. The good news is that several other browsers I use (LibreWolf, Mullvad) are Firefox Based - so write once, run in many places. Good.

I was tempted to support also Chrome and Safari.

Firefox exposes more APIs than Chrome, including the access to webRequest. With blocking. Turns out that Chrome has decommissioned this feature in MV3. MV3 is also where Chrome has removed the support for webRequestBlocking which is - incidentally - what I plan to use to block requests. Finally, there’s the legend that Chrome developers have to fork over 5 bucks to Mountain View just to get started. I wasn’t in the mood to find out if it’s true.

As for Safari - Safari is the outsider. I like it - I already said it a few times, but Apple does not make developer’s life easy. Believe me - they just don’t. Safari extensions must be distributed within the App Store, within a macOS app wrapper. Signed, notarised, Apple reviewed. For a free tool, that’s overkill. Several limitations on the API seal the deal - some API I planned to use may not be there. So… So the answer for Apple Users must be something else (which I am writing, but that’s material for another discussion).

TBA Lore - what’s in a name: ElectricEye

If you strolled for some time on this blog, you know that I often use Heavy Metal, Maths, or paradox references. It’s just a matter of avoid christening something like my exceptional extension - which I find boring.

My first two programs are named after a Black Sabbath and a Megadeth song. Aradia’s workflow is modeled after Mercyful Fate’s “Come to the Sabbath” lyrics. My cat’s name is Sabbath and no, I don’t use that name in my passwords, save your time.

Long story short, this time was Judas Priest’s Electric Eye

I’m made of metal
My circuits gleam
I am perpetual
I keep the country clean

Hopefully this guy will keep “Zia Maria”’s transactions clean. And safe. While the attacker strives to be an invisible spy, EE is the eye that peers deeper—spotting the proxy before it can ever touch Zia Maria’s data.

Technical Analysis

The blueprint - how EE works

First, and foremost, how it does not work.

I am not a fan of blacklist. They’re inherently broken. If IT Security has a mantra, it is “Kill ‘em all, let God sort them out”, or better “Block ‘em all, allow only few well-known good ones”.

Blacklists are broken by design. While you strive to find all the domains in which I deploy my evil proxies, I spawn 5 more. 10 more. The only limit is the cost of buying them - but again: I saw domains available for $3…

Long story short - a blacklist would be late. Always.

There must be a better approach - and the better approach is always about structure. The Nature of Things never lies.

A web page works mainly at three levels: DNS, HTTP headers, DOM. There’s a fourth layer in the AitM attack: TLS. In each of these, the AitM leaves breadcrumbs that can be followed. Precious hints.

Several independent layers that must converge to a precise set of circumstances to confirm an AitM. Miss one — no problem. Miss all of them? That’s your attacker.

DNS - the URL

Here my analysis is quite simple:

  • High entropy: an algorithmically generated domain name such as xk4f9q2m.evil.com have high entropy. Surely, way higher than bancaintesa.it or vodafone.co.uk.
  • Punycode / homograph: аrаdia.zone vs. aradia.zone. Can you spot the difference? let’s look at the bytes:
String xxd
аrаdia.zone 00000000: d0b0 72d0 b064 6961 2e7a 6f6e 65 ..r..dia.zone
aradia.zone 00000000: 6172 6164 6961 2e7a 6f6e 65 aradia.zone

Good luck spotting out that thing only with your eyes.

  • Typosquatting: a simpler variant of the homograph attack: apple.com vs. app1e.com.
  • Subdomain anomalies: stats.pure-evil.online - a subdomain that has nothing to do with the root domain.

HTTP Layer

I am a pentester. Or I used to be that before I undertook “the way of the exploiting fist” - but the mindset is framed in blood. As a pentester, you first look at the structure of the HTTP header. If you don’t you’re not a nice guy.

There’s a plethora of checks that one may wanna do to see “how secure” an HTTP transaction can be. Some apply, some don’t. The thing is - quite often also good people misconfigure their web servers, and I did not want to increase the level of noise.

I went for a few choices. Simple ones:

  • Missing CSP: A “Free-for-all” script injection. With no CSP, any browser will blindly trust whatever is loaded. A properly crafted CSP could prevent the browser from sending data to unauthorised domains, or loading external scripts required by the kit. If missing, the AitM proxy has a good time manipulating DOM and stealing cookies. Plus, no CSP (frame-ancestors) means that inserting an iframe is easy as stealing ZiaMaria’s cookies. Both Browser’s and cookies in the jar-oh!
  • Missing HSTS: HSTS (HTTP Strict Transport Security) is what protects from Downgrade Attacks (or SSL Stripping), and session hijacking. Zia Maria would end up sending her data over unencrypted media. No-no!
  • Proxy headers - such as X-Forwarded-For, Via, X-Real-IP. Proxy breadcrumbs. Fingerprints. Call ‘em whatever.

TLS Layer

TLS is where things get interesting.

An AitM proxy sits between you and the server. This means two separate TLS handshakes: one with the victim, one with the legitimate server. The proxy negotiates both — and it negotiates them with its own capabilities, not the server’s. This leaves traces.

The most reliable signal I implemented is certificate age. AitM kits are opportunistic by nature — domains are bought, Let’s Encrypt certificates are generated, the kit is deployed. Fast. The whole infrastructure can be up in hours. A certificate that is less than 72 hours old on a domain claiming to be your bank is… suspicious. Very.

Is a fresh certificate a definitive proof of an AitM? No. Legitimate services rotate certificates too: ChatGPT scored a borderline 0.45 in my tests for exactly this reason. But combined with other signals, it becomes a meaningful contribution to the overall risk score.

The other theoretical signals: CA anomalies, cipher suite mismatches, TLS version downgrade. These are on the roadmap. For now, certificate age does the heavy lifting.

DOM Layer

This is the one that seals the deal.

DNS, HTTP headers, TLS. All useful signals. But a well-configured AitM proxy can get most of them right. Strip the proxy headers, use a aged domain, forward the security headers. Sophisticated kits do exactly that.

The DOM, however, is harder to fake.

Evilginx, and most AitM kits for what matters, work by proxying the legitimate site in real time. The HTML comes from the real server, gets modified on the fly, and is served to the victim. The problem is that rewriting every single URL in the DOM is hard. Expensive. Error-prone. Most kits don’t bother, or better, don’t bother completely.

The result? The browser is on stats.pure-evil.online. But the DOM is full of references to aradia.zone. Links, form actions, canonical tags, og:url. The real domain bleeds through.

Electric Eye’s content script scans the DOM continuously — with a MutationObserver, throttled at 500ms to avoid killing performance. When the ratio of links pointing to a different domain crosses a threshold, DomainMismatch fires. Weight: 0.50. Confidence: proportional to the ratio.

On a clean site — Repubblica, Google, Apple — this signal never fires. On stats.pure-evil.online proxying aradia.zone — it fires immediately. Combined with the other signals, the score hits 1.00. Critical. Hold your horses!

Risk scoring

Aradia, EE, and Unveiler (yet to come, but you’ll hear this name quite soon) mechanism is quite easy: events rise signals. Signals are modelled into numbers. Numbers become risk ratios. Risk ratios become decision rationale.

I deliberately avoided to bluntly block suspicious connections. I just decided to have a popup telling you a risk ratio. And if you’re not colorblind nor dyscalculic (which I am, by the way. Dyscalculic, I mean), you can decide if proceeding on navigation or doing your banking differently.

The model is not complex. It is a simple multiplicative model with corrections, because additive models tend to amplify background noises. Easy Peasy.

If risk is usually modelled as likelihood × impact, here I decided to model risk factor as weight × confidence. For now, the model holds.

Let’s oxidate their gluteus maximus!

Rust.

No discussion. The engine had to be written in a serious language that compiles into WASM. I could do C++ but I tend to avoid it, lately. Traumatised by its string management, despite the improvements in the latest versions.

JavaScript was not a choice. Sure as death and taxes, it’d enlarge the attack surface. No-no.

Engine written in pure Rust. No frills, no weird unsafe stuff. Pure Rust. And it works fine.

There is some JavaScript, indeed. The glue I needed to pass data to WASM. That’s what JS is for. Someone said that “tongue is that sexual organ that some perverts use to talk”. Likewise, JS is that glueish language that some perverts use to program.

Demo-ish

I plan to do a video with this, unfortunately 1 minute of video requires 10 minutes of takes and this is a low-priority task for me.

I hope you can appreciate the efforts nevertheless.

On a “normal” website, EE works almost transparently. See the images below, for instance.

About:Debugging

Has a meaning: there is no data here. Let’s see something more complex - my websites:

bytearchitect.io

and:

aradia.zone

These show that cobbler’s children are the worst shod. I am complaining about a CSP I don’t put in my websites. What a dork I am.

But these values are good. Take for instance also

apple.com

or

paypal.com

Then, let’s be mean. I spun up a VPS and configured it like this:

ssh root@$VPS_IP
cd ~/evilginx2
./build/evilginx -p phishlets/

and from there:

config domain pure-evil.online
config ipv4 $VPS_IP
phishlets hostname aradia pure-evil.online
phishlets enable aradia
lures create aradia
lures

Now let’s assume that Zia Maria wants my SWAF (SwampWAF) and receives a phishy email steering her to a malicious domain. That’s what EE could do for her:

Aradia 1

Aradia 2

Aradia 3

You can see a slight discrepancy between the values (the first one is lower). That’s the magic of DOM analysis: the higher the number of links in the page, the higher the chance that some of them are not rewritten. And - BANG! - I spot you out!

There is a grey area - I am still working on it. Take Amazon:

Amazon

Here signals are/can be quite misleading:

  • High entropy: amazon.com has a very low entropy, but the guys spin up subdomains and dynamically generated URLs. A real nightmare.
  • CSP/HSTS missing: Cobbler’s children, again.
  • ProxyHeaderDetected - dominating factor. Blame mr. CloudFront.

As I said, this is something I am still working on. A neater model.

Current status

I submitted the first version to the Mozilla Add-ons store. I am totally unaware of Mozilla’s timelines — but in general, it takes a week or so for an extension to be approved. I don’t expect Mozilla to be any faster.

As soon as it’s out, I’ll post the link. In the meantime, if you want to play with it, write me at hello@aradia.zone and I’ll send you a copy directly. Spamming a hacker is not a very smart move in a world made of technology. Just saying — to wake up the hamster in your head.

Here the download link: https://addons.mozilla.org/en-US/firefox/addon/electric-eye/

Conclusion

Electric Eye is not a silver bullet. No tool is.

What it is, is a different approach. Instead of chasing domains and updating blacklists, it looks at behaviour. At structure. At the things that an AitM proxy cannot easily hide — because hiding them means breaking the attack itself.

The false positive rate is low. The true positive rate, against a real Evilginx deployment, is 1.00. That’s not a coincidence — that’s the DOM bleeding through.

There is work still to do. The CDN grey area needs a neater model. The TLS signals are partially roadmap. And a video demo is coming — eventually, when I find 10 consecutive minutes of patience.

But Zia Maria is already safer than she was last week. And that’s enough for now.

More is coming. Stay paranoid, have fun… and keep your horses held!