DEV Community

Cover image for Making Websites for Spies
Zachnology
Zachnology

Posted on • Originally published at zachnology.substack.com

Making Websites for Spies

Abstract JavaScript Events

Cover photo by Agence Olloweb on Unsplash

Have you ever thought about how the CIA communicates with informants all over the globe? There’s the clandestine meeting. Maybe slipping of a note into a pocket as someone walk by. A removable brick in a wall. But is there a way to communicate in plain sight?

It is believed that during the mid 2000s to at least the early 2010s the CIA created a series of fake websites that handlers used to communicate with informants. To the unknowing eavesdropper, it would look like an informant was just visiting a local news website, but the ordinary-looking search box was actually a password input field that would give them access to the communications functionality.

Before I ever heard about this practice, I had built a proof of concept for clandestine interaction on websites too. And since I didn’t use it for international espionage, what I’m about to share is completely declassified…

Abstract Events

We can detect and listen to pretty much any input performed on a website. Whether we need to simply detect a focus or blur event on an input element or we want to track mouse movement across the page, we have access to the data and the triggering event.

This is all fine and dandy, but what if I want to implement keyboard shortcuts for my website? Two of the more popular use cases for this is making enter do something other than submitting a form and creating desktop-like shortcuts for web apps (like Ctrl+ shortcuts).

The case of overriding the behavior of the enter press is mostly trivial. You’ll need to listen to the keydown or keyup event, then check if enter is the key that has been pressed. If so, cancel the default behavior and then do whatever you want enter to do.

document.addEventListener("keydown", (event) => {
  if (event.key === 'Enter') {
    event.preventDefault();
    // your custom logic here
    return false;
  }
};
Enter fullscreen mode Exit fullscreen mode

It works the same way for the Ctrl+ shortcuts but you just check if ctrl is also pressed.

document.addEventListener("keydown", (event) => {
  if (event.ctrlKey && event.key === 'z') {
    event.preventDefault();
    // your custom logic here
    return false;
  }
});
Enter fullscreen mode Exit fullscreen mode

These simple key “overrides” got me thinking about making an event listener factory that took abstract instructions and produced a JS event handler. My goal was to come up with some unique input events and provide an easy to use interface for it.

What if I wanted to listen for more complex input that needed more information than a single mouse or key event could give me? Can I trigger special behavior if a key word is typed in even if an input element is not in focus? Can I make a button behave differently depending on how many times I clicked it, or how fast I clicked it? If we buffer the input, we can easily track these types of events.

I created a proof of concept npm package called abstract-events that exposes a simple interface for this type of event. The create() method accepts 2 parameters: options and a callback. Here are some examples:

Count Events

The first type of abstract event is a count event. It triggers once an input event has been triggered a defined number of times.

Click Count

The code below attaches an event handler to a buttons onclick event that triggers when you’ve clicked the button 4 times.

const abstractEvents = require('abstract-events');

let someButton = document.getElementById('someButton');
someButton.onclick = abstractEvents.create(
    {
        type: 'count',
        count: 4
    },
    () => alert('You clicked the button 4 times!')
);
Enter fullscreen mode Exit fullscreen mode

Timed Click Count

Here’s the same example but ignores clicks that happen outside a 1.5 second window. This could be used to ensure the clicks were intentional to trigger this event.

const abstractEvents = require('abstract-events');

let someButton = document.getElementById('someButton');
someButton.onclick = abstractEvents.create(
    {
        type: 'count',
        count: 4,
        timeout: 1500
    },
    () => alert('You clicked the button 4 times within 1.5 seconds!')
);
Enter fullscreen mode Exit fullscreen mode

Parsed String Events

You can also trigger phrase events. This listens for a specific string before triggering.

Specific String

Here the event is attached to the document’s keydown event. It triggers once you’ve typed in the string “secret”, regardless of focus.

document.addEventListener('keydown', abstractEvents.create({
    type: 'phrase',
    phrase: 'secret'
}, () => alert('You did it!')));
Enter fullscreen mode Exit fullscreen mode

Timed String

Just like click example, you can limit the phrase event to a time window. In this example, the event only triggers if the string “faster” is typed within 1.5 seconds.

document.addEventListener('keydown', abstractEvents.create({
    type: 'phrase',
    phrase: 'faster',
    timeout: 1500
}, () => alert('You did it fast!')));
Enter fullscreen mode Exit fullscreen mode

You can play around with these events on the live demo page.
The source code for the library is available on github.

A Note on Security

Don’t use this for actual privileged access. Because these events are defined entirely in the frontend, they are not suitable for authentication. Someone could easily just look at the source of your web app and see how to bypass the security. Further, having a single global passphrase makes any system easier to hack. Deploying something like this at scale may also make your sites easily discoverable just with google dorking, which brings us back to the CIA’s implementation…

A “Security Through Obscurity” Failure

The CIA’s spy website project did not end well. The sites were just a thin façade for their real purpose. And since websites are constantly up, they can be submitted to scrutiny for longer periods of time than more traditional CIA informant encounters. If you were suspicious of a website someone was viewing, you just had to look at the page source and see that the search text box was actually a password field. Research by Citizen Lab found 885 such websites just by using OSINT (Open-source Intelligence) and discovered evidence that the poor design of security features for these sites led to the compromised identity and arrest of informants worldwide.

Conclusion

This article ended up serving two purposes. One was hopefully food for thought in thinking outside the box on how to use JS events for web input. The other is the importance of security for our web applications, not just using proper authentication or protecting sensitive data but also that we should protect the means of security so attackers don’t have an easy starting place.

Need help with your software project?

My day job is working as a software engineer for Trendsic where I help businesses large and small plan and develop secure and scalable software. Reach out to me at contact@zachnology.io to discuss how I can help bring your ideas to life.

Top comments (1)

Collapse
 
softwaredeveloping profile image
FrontEndWebDeveloping

An interesting post @zachnology .