<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Radio Hackers - Medium]]></title>
        <description><![CDATA[Exploring how the radio spectrum shapes modern technology, defence &amp; cybersecurity. Are you a writer? Contribute a tutorial or share your experiences - Medium]]></description>
        <link>https://radiohackers.com?source=rss----8946e9a3c7a6---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Radio Hackers - Medium</title>
            <link>https://radiohackers.com?source=rss----8946e9a3c7a6---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 04:11:56 GMT</lastBuildDate>
        <atom:link href="https://radiohackers.com/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[I Looked at My Wi-Fi Router Through a Hacker’s Eyes. Here’s What I Found.]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://radiohackers.com/i-looked-at-my-wi-fi-router-through-a-hackers-eyes-here-s-what-i-found-261ba40132ad?source=rss----8946e9a3c7a6---4"><img src="https://cdn-images-1.medium.com/max/1202/1*Q5qADjLtLn-ciu5SaNkKWw.png" width="1202"></a></p><p class="medium-feed-snippet">Your home router does much more than provide internet access. I decided to investigate mine, explore its security, scan its network&#x2026;</p><p class="medium-feed-link"><a href="https://radiohackers.com/i-looked-at-my-wi-fi-router-through-a-hackers-eyes-here-s-what-i-found-261ba40132ad?source=rss----8946e9a3c7a6---4">Continue reading on Radio Hackers »</a></p></div>]]></description>
            <link>https://radiohackers.com/i-looked-at-my-wi-fi-router-through-a-hackers-eyes-here-s-what-i-found-261ba40132ad?source=rss----8946e9a3c7a6---4</link>
            <guid isPermaLink="false">https://medium.com/p/261ba40132ad</guid>
            <category><![CDATA[hacking]]></category>
            <category><![CDATA[tech]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[network-security]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <dc:creator><![CDATA[Satyam Pathania]]></dc:creator>
            <pubDate>Thu, 12 Nov 2026 22:24:17 GMT</pubDate>
            <atom:updated>2026-11-12T22:24:16.575Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Which DJI Drones Support MSDK V5? (2026) | DroneDev Academy]]></title>
            <link>https://radiohackers.com/which-dji-drones-support-msdk-v5-2026-dronedev-academy-fe3790a5e2f6?source=rss----8946e9a3c7a6---4</link>
            <guid isPermaLink="false">https://medium.com/p/fe3790a5e2f6</guid>
            <category><![CDATA[supported-drones]]></category>
            <category><![CDATA[drone-programming]]></category>
            <category><![CDATA[msdk]]></category>
            <category><![CDATA[drones]]></category>
            <category><![CDATA[dji]]></category>
            <dc:creator><![CDATA[Peiman Ataei]]></dc:creator>
            <pubDate>Wed, 11 Nov 2026 23:59:47 GMT</pubDate>
            <atom:updated>2026-11-11T23:59:45.937Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JJfFGhbe2J9-Q2LluaOf4w.png" /></figure><p>You want to build an app for a DJI drone. So you buy a drone. You set up your project. You write your first lines of code.</p><p>And then you find out: this drone is not supported.</p><p>Or maybe the drone is on the list, but your controller is not. Now you have lost money and time.</p><p>So how do you know before you buy? This guide gives you the answer, and shows you how to check any DJI drone yourself.</p><blockquote><strong><em>Quick answer:</em></strong><em> DJI Mobile SDK V5 (MSDK V5) supports mostly enterprise drones, such as the Matrice 400, Matrice 4 series, Matrice 350 RTK, Matrice 300 RTK, Matrice 30 series and Mavic 3 Enterprise series. Only three consumer drones are supported: the Mini 4 Pro, Mini 3 and Mini 3 Pro. Each drone is supported only with specific controllers, so always check the aircraft and the controller together on DJI’s official list.</em></blockquote><p><em>Prefer to watch? This guide is also available as a video on the </em><a href="https://www.youtube.com/@DroneDevAcademy"><em>DroneDev Academy YouTube channel</em></a><em>.</em></p><p><strong>Checked against:</strong> MSDK V5.20.1, November 2026. The video was recorded with the V5.18 list, so it does not show the Matrice 5T, which DJI added in V5.20.1. The list changes, so always check the official page before you buy.</p><h3>What you will learn about supported DJI drones</h3><ul><li>What “supported” really means</li><li>The three DJI drone groups, and which ones MSDK V5 covers</li><li>The enterprise drones on the list, with their controllers</li><li>The three consumer drones on the list</li><li>How to check any DJI drone yourself</li></ul><p>I’m Peiman. In the years I have developed apps for drones, I faced this problem myself: which drone, with which controller, works with MSDK V5? The official list can be confusing. Let’s read it together.</p><p><strong>One note before we start:</strong> this guide covers <strong>MSDK V5 only</strong>, DJI’s Mobile SDK for Android apps. Other DJI SDKs, such as the Payload SDK and Cloud API, have their own lists.</p><h3>What does “supported” mean?</h3><p>“Supported” means MSDK V5 can work with the drone. Your app can:</p><ul><li><strong>Read data</strong> from it, such as telemetry and live video</li><li><strong>Send commands</strong> to it, such as takeoff</li></ul><p>If a drone is not supported, MSDK V5 does not work with it, and your app cannot control it. So in this guide, “Can you program it?” means “Is it supported by MSDK V5?”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3CYJa8u_UrQINNIoCuYqbQ.png" /></figure><h3>DJI supports a pair, not a drone</h3><p>This is the most important part. DJI does not support the drone alone. It supports a <strong>pair</strong>: the aircraft and the remote controller.</p><p>This is because your app talks to the controller, and the controller talks to the aircraft. If the controller has a screen, your app can run on it. If not, your app runs on a phone connected to the controller. (New to this chain? Read <a href="https://dronedevacademy.com/learn/what-is-dji-msdk-v5">What Is DJI MSDK V5?</a> first.)</p><p>The official list also shows <strong>firmware versions</strong> for each aircraft and controller. So remember three things, always together:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3HNJLlUHIqtrpOgHhOFGbA.png" /></figure><h3>The three DJI drone groups</h3><p>Before you read the list, you need to know how DJI groups its drones. There are three main groups:</p><ul><li><strong>Consumer drones</strong> are made for photos, video and travel. Examples are the Mini series, the Air series and the Mavic 3 Pro.</li><li><strong>Enterprise drones</strong> are made for professional work, such as inspection, mapping and public safety. Examples are the Matrice series and the Mavic 3 Enterprise series.</li><li><strong>Agriculture drones</strong> are made for farms, for spraying and spreading. The Agras series is the example.</li></ul><p>Now look at the MSDK V5 list:</p><ul><li><strong>Enterprise:</strong> most of the list</li><li><strong>Consumer:</strong> only three drones, all from the Mini series</li><li><strong>Agriculture:</strong> none</li></ul><h3>What DJI support says about the future</h3><p>In October 2026, I asked DJI support about consumer drones. In their email reply, they said that:</p><ul><li>Other consumer drones are not supported, and there are <strong>currently</strong> no plans to support them.</li><li><strong>Going forward</strong>, MSDK will mainly focus on enterprise aircraft.</li></ul><p>So if you plan a long-term project, an enterprise drone is the safer choice.</p><h3>The enterprise drones supported by MSDK V5</h3><p>Here are the enterprise drones on the list, grouped by family, with their controllers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pZD2ox4z28JvNKbLUYZk-w.png" /></figure><ul><li><strong>Matrice 400</strong>: DJI RC Plus 2</li><li><strong>Matrice 4 series</strong> (Matrice 4E, 4T, 4D and 4TD): DJI RC Plus 2</li><li><strong>Matrice 350 RTK</strong>: DJI RC Plus</li><li><strong>Matrice 300 RTK</strong>: DJI RC Plus, or DJI Smart Controller Enterprise. It is the only enterprise aircraft listed with two controllers.</li><li><strong>Matrice 30 series</strong>: DJI RC Plus</li><li><strong>Mavic 3 Enterprise family</strong> (Mavic 3 Enterprise series, Mavic 3M and Mavic 3TA): DJI RC Pro Enterprise</li><li><strong>Matrice 5T</strong>: new in MSDK V5.20.1. Check the official list for its controller and firmware.</li></ul><p>All of these controllers have a screen, so your app can run directly on the controller.</p><p>The list also includes <strong>payloads</strong>, such as the Zenmuse cameras. Payloads are a topic for another guide.</p><h3>The three consumer drones supported by MSDK V5</h3><p>Only three consumer drones are on the list, and all of them are from the Mini series:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IwhnIya0Iqin3Zv6fdlvQg.png" /></figure><ul><li><strong>DJI Mini 4 Pro</strong>: DJI RC-N2 or DJI RC-N3</li><li><strong>DJI Mini 3</strong>: DJI RC-N1 only</li><li><strong>DJI Mini 3 Pro</strong>: DJI RC-N1 or DJI RC Pro</li></ul><p>Look at the controllers:</p><ul><li>The <strong>RC-N1, RC-N2 and RC-N3</strong> have no screen, so your app runs on a phone connected to them.</li><li>The <strong>RC Pro</strong> has a screen, so your app can run on the controller.</li></ul><h3>Every other consumer drone is not supported</h3><p>Examples include the <strong>Mini 5 Pro</strong>, the <strong>Air series</strong>, the <strong>Mavic 3 Pro</strong> and the <strong>Mini 2</strong>.</p><h3>Be careful with bundles</h3><p>Some drone bundles come with a different controller. For example, a Mini 3 sold with a controller other than the RC-N1. If the controller in your bundle is not on the list, <strong>the pair is not supported</strong>, even if the aircraft is.</p><h3>How to check any DJI drone yourself</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KFt1DR94T1kWhoMZUdcZUg.png" /></figure><p>Checking a drone takes two minutes:</p><ol><li>Open the <a href="https://developer.dji.com/">DJI developer site</a>.</li><li>Go to the Mobile SDK section and open the <a href="https://developer.dji.com/doc/mobile-sdk-tutorial/en/"><strong>MSDK release notes</strong></a>.</li><li>Find the section called <strong>“Supported Product List and Firmware Version”</strong>.</li></ol><p>Then:</p><ol><li><strong>Find your aircraft.</strong></li><li><strong>Find its controllers</strong> under it.</li><li><strong>Check the firmware versions</strong> for both.</li></ol><p>If your aircraft or your controller is not there, it is not supported.</p><p><strong>One last tip:</strong> the list changes. Check it again on the day you buy, and look at the MSDK version on the page.</p><h3>The Developer Scorecard: how to choose a drone</h3><p>When you choose a drone for a project, answer four questions about it:</p><ol><li><strong>Is it supported by MSDK V5?</strong></li><li><strong>Which controller does it use?</strong></li><li><strong>What is listed with it?</strong> (payloads and accessories)</li><li><strong>What is it best for?</strong></li></ol><p>Here is an example from a drone I have used with MSDK V5, the <strong>Matrice 300 RTK</strong>:</p><ul><li><strong>Supported by MSDK V5:</strong> Yes</li><li><strong>Controller:</strong> DJI RC Plus, or DJI Smart Controller Enterprise</li><li><strong>Listed with it:</strong> Zenmuse H20, H30, P1, L1 and L2, and the D-RTK 2 Mobile Station</li><li><strong>Best for:</strong> inspection and mapping with interchangeable payloads</li></ul><p>You can use the same scorecard for any drone on the list.</p><h3>Key takeaways: which DJI drones MSDK V5 supports</h3><ul><li><strong>A DJI drone is supported as a pair:</strong> the aircraft and its controller, with the right firmware.</li><li><strong>MSDK V5 is mainly for enterprise drones.</strong> Only three consumer drones are supported: the Mini 4 Pro, Mini 3 and Mini 3 Pro.</li><li><strong>No agricultural drones</strong> are on the list.</li><li><strong>DJI support says</strong> MSDK will mainly focus on enterprise aircraft going forward.</li><li><strong>Check the official list</strong> on the day you buy, including the controller in your bundle.</li></ul><h3>Frequently asked questions about MSDK V5 supported drones</h3><h3>Is the DJI Mini 4 Pro supported by MSDK V5?</h3><p>Yes, with the DJI RC-N2 or RC-N3 controller. Your app runs on a phone connected to the controller.</p><h3>Is the DJI Mini 5 Pro supported by MSDK V5?</h3><p>No. At the time of writing, the Mini 5 Pro is not on the MSDK V5 supported list.</p><h3>Are the DJI Air series drones supported by MSDK V5?</h3><p>No. The Air series is not on the list. The only supported consumer drones are the Mini 4 Pro, Mini 3 and Mini 3 Pro.</p><h3>Are DJI Agras drones supported by MSDK V5?</h3><p>No. No agricultural drones are on the MSDK V5 list.</p><h3>Can I use any DJI controller with a supported drone?</h3><p>No. DJI supports specific aircraft and controller pairs. If your controller is not listed with your aircraft, the pair is not supported.</p><h3>Where is the official MSDK V5 supported list?</h3><p>On the DJI developer site, in the MSDK release notes, in the section called “Supported Product List and Firmware Version”.</p><h3>Does MSDK V5 work on iOS?</h3><p>No. MSDK V5 is for Android only. Learn more in <a href="https://dronedevacademy.com/learn/what-is-dji-msdk-v5">What Is DJI MSDK V5?</a></p><h3>Next steps after you choose a supported drone</h3><h3>Keep learning DJI drone programming with DroneDev Academy</h3><p>This guide is part of DroneDev Academy, where I teach drone software development from real project experience. Here is where to go next:</p><ul><li><strong>Watch it on YouTube.</strong> This guide has a video version. Subscribe at <a href="https://www.youtube.com/@DroneDevAcademy">youtube.com/@DroneDevAcademy</a> to catch new tutorials as they come out.</li><li><strong>Read more guides.</strong> Step-by-step articles on DJI Mobile SDK V5 are collected on the <a href="https://dronedevacademy.com/learn">all DroneDev Academy guides</a>.</li><li><strong>Learn MSDK V5 step by step.</strong> In the full DroneDev Academy course, we learn the fundamentals of DJI drone programming and build a real drone app together, from zero. <a href="https://dronedevacademy.com/#course">See the DroneDev Academy course</a>.</li></ul><p>If you got stuck somewhere, write to me at <a href="mailto:info@dronedevacademy.com">info@dronedevacademy.com</a>. I read every message.</p><h3>Want the full course?</h3><p>The DroneDev Academy course starts with this same SDK. Join the early-access list to hear first about the course and about new guides like this one.</p><p><a href="https://dronedevacademy.com/#early-access">Get early access</a></p><p><em>Originally published at </em><a href="https://dronedevacademy.com/learn/dji-msdk-v5-supported-drones"><em>https://dronedevacademy.com</em></a><em> on November 11, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fe3790a5e2f6" width="1" height="1" alt=""><hr><p><a href="https://radiohackers.com/which-dji-drones-support-msdk-v5-2026-dronedev-academy-fe3790a5e2f6">Which DJI Drones Support MSDK V5? (2026) | DroneDev Academy</a> was originally published in <a href="https://radiohackers.com">Radio Hackers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Shortwave Radio With No Radio Inside It]]></title>
            <link>https://radiohackers.com/a-shortwave-radio-with-no-radio-inside-it-d8b678c4a795?source=rss----8946e9a3c7a6---4</link>
            <guid isPermaLink="false">https://medium.com/p/d8b678c4a795</guid>
            <category><![CDATA[diy-electronics]]></category>
            <category><![CDATA[software-defined-radio]]></category>
            <category><![CDATA[ham]]></category>
            <category><![CDATA[rtlsdr]]></category>
            <category><![CDATA[radio]]></category>
            <dc:creator><![CDATA[Aeon Flex, Elriel Assoc. 2133 [NEON MAXIMA]]]></dc:creator>
            <pubDate>Wed, 11 Nov 2026 23:56:46 GMT</pubDate>
            <atom:updated>2026-11-11T23:56:45.669Z</atom:updated>
            <content:encoded><![CDATA[<h4>An ESP32, a knob and a speaker can tune receivers on the other side of the planet. The antenna belongs to somebody else.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*AAY-RGFe4PxIMjSL" /><figcaption>Photo by <a href="https://unsplash.com/@eugenegrunge?utm_source=medium&amp;utm_medium=referral">Eugene</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Somewhere in Argentina there is a long wire strung between two points, feeding a KiwiSDR on a shelf. Somewhere in Germany there is a knob. Turn the knob, and the receiver in Argentina retunes, and the audio arrives a quarter second later through a speaker the size of a coin.</p><p>That is the whole trick behind ESPica32, a build Ivan (LU8MIL) shared this week and RTL-SDR.com wrote up on November 5. An ESP32-WROOM-32, a small OLED, one rotary encoder and an I2S amplifier. No tuner chip, no ADC, no antenna. The radio is a network client that has learned to behave like a radio.</p><p>It is a cheap build and a clever one, and the most interesting part is the bug he hit along the way.</p><h3>Why a Radio Without RF Matters Right Now</h3><p>Earlier this year, the RTL-SDR Blog team announced the end of the line for the V4 dongle. The Rafael R828D tuner it depends on is out of production; the stockpile ran dry, and the last chips they could find were faulty. Their stopgap, the V4L, runs on an R828S that is also out of production. Meanwhile, the marketplaces are full of “V4” listings that are a V4 shell wrapped around a V3-clone board.</p><p>Hardware supply is a weather system. Nobody controls it, and the hobby keeps getting caught in it.</p><p>A KiwiSDR client sidesteps that weather entirely. The Kiwi network is a scattered fleet of HF receivers, roughly 0 to 30 MHz, run by operators who chose to share them publicly. Their antennas sit in quiet rural noise floors you will never have in an apartment full of switch-mode power supplies. A client gets you their ears. You trade local RF for latency and for depending on a stranger’s uptime.</p><p>For shortwave broadcast, ham bands, utility stations and weather fax, that is a good trade. For anything involving transmitting, or signals above 30 MHz, it is no trade at all. Know which problem you are solving before you buy parts.</p><h3>The Bill of Materials</h3><p>The build is deliberately minimal. Ivan links each part to AliExpress without giving a total, so the prices below are typical listing ranges, not a quote.</p><p>The brain is an ESP32-WROOM-32 dev board with onboard USB. In the Arduino IDE it shows up as “ESP32 Dev Module.” Nothing exotic, no PSRAM requirement, which matters later.</p><p>The display is a 1.3-inch SH1106 OLED at I2C address 0x3C. A 0.96-inch SSD1306 also works.</p><p>Input is a single EC11 rotary encoder with a pushbutton, or the KY-040 breakout if you hate soldering bare encoders. That one knob carries frequency, volume and mode through LSB, USB, AM, CW and FM.</p><p>Audio goes through a MAX98357A I2S class D amplifier into a 4 to 8 ohm speaker. The amp is good for about 3 W, which is loud for a desk and absurd for a pocket. There is a fallback path using the ESP32’s internal DAC on GPIO25 into an LM386, selected with AUDIO_INTERNAL_DAC, and Ivan is candid that it sounds worse. The internal DAC is 8-bit. Your ears will agree with him.</p><p>Expect to spend somewhere around fifteen to twenty dollars on parts if you buy from the usual overseas sellers and already own a speaker and jumper wire. Add a USB power bank, and it walks around with you.</p><p>If you are building a bench of ESP32 field tools and want the firmware and radio fundamentals behind them laid out in one place, the <a href="https://numbpilled.gumroad.com/l/esp32-field-kit-v2">ESP32 Field Kit: Wireless Research &amp; Offline Hardware Bundle</a> covers the ESP32 and Wi-Fi side of portable recon builds, which is the same toolbox this radio pulls from.</p><p><a href="https://numbpilled.gumroad.com/l/esp32-field-kit-v2">ESP32 Field Kit: Wireless Research &amp; Offline Hardware Bundle</a></p><h3>Wiring and First Boot</h3><p>The sketch uses eight GPIOs, all defined in config.h. The exact pin map lives in a wiring diagram on Ivan&#39;s blog rather than in the text, so pull it from there instead of guessing.</p><p>The power rules are simple and easy to get wrong at 1 AM. The OLED runs from 3V3. The amplifier runs from VIN, which is the 5 V coming off USB. Every ground joins every other ground. The encoder common and the pushbutton’s second leg both go to GND, and the sketch turns on internal pull-ups, so you skip the resistors.</p><p>Two config.h settings save the most frustration:</p><pre>// Blank screen on a 1.3&quot; panel? It is probably an SH1106, not an SSD1306.<br>#define OLED_SH1106 1<br>// Each detent jumping four steps? Many encoders emit two or four per click.<br>#define ENC_STEPS_PER_DETENT 2</pre><p>Before compiling, change the partition scheme. In the Arduino IDE go to Tools, then Partition Scheme, and pick “No OTA (2MB APP/2MB SPIFFS).” The sketch is about 1.2 MB, and Ivan reports it dropping from 94 per cent to 59 percent of available flash after the change. You lose over-the-air updates. On a device that lives on your desk next to a USB cable, that is a low price.</p><p>Libraries are the Espressif ESP32 Arduino core and U8g2 for the display. Notably absent is any off-the-shelf WebSocket library, for reasons covered below.</p><p>First boot puts the ESP32 into access point mode:</p><pre>SSID:     KiwiRadio-Setup<br>Password: kiwiradio<br>Setup:    http://192.168.4.1</pre><p>Join it, enter your Wi-Fi credentials, pick a KiwiSDR server, and the board reboots onto your network at kiwiradio.local. Settings persist in flash. The same web page gives you full remote control and a diagnostics panel that classifies each audio dropout as network, ESP32 or buffer.</p><p>A small security note, because someone has to say it. That setup password is now published in a blog post and a news article. Anyone within Wi-Fi range during first boot can join the setup network and see what you type into it. Do the initial configuration somewhere boring, then treat kiwiradio.local like every other unauthenticated IoT web panel on your LAN. Cheap gadgets with web interfaces belong on the guest VLAN, and this one is no exception.</p><h3>The Bandwidth Wall and the ADPCM Fix</h3><p>Here is the part worth the price of admission.</p><p>The first version streamed uncompressed audio over a WebSocket. Against nearby Kiwis, it worked. Against distant ones, it cut out constantly. Ivan instrumented it and found the ESP32 receiving about 13.5 audio packets per second when the stream needed about 23.4. The chip was falling behind real time by almost half.</p><p>Uncompressed, the stream ran around 24 kB/s. KiwiSDR servers can send IMA ADPCM instead, which encodes each 16-bit sample as a 4-bit difference from a prediction. Four to one. The stream dropped to roughly 6 kB/s, the ESP32 decoded it on the fly, and the audio went clean. His test case was a Kiwi in Argentina played from Germany over a link with about 260 ms of ping.</p><p>The interesting lesson is where the bottleneck lived. Twenty-four kilobytes per second is nothing for 802.11n on paper. The ceiling was everything stacked on top of the radio: TCP windowing over a high-latency path, WebSocket framing, the Wi-Fi driver sharing a core, the I2S DMA wanting its buffers on time. Each layer takes a slice, and at 260 ms round trip, a single lost segment stalls the pipe long enough to drain a small buffer. Shrinking the payload by four gave every layer room to breathe.</p><p>Datasheets quote the clean room. Your packets live in traffic.</p><p>ADPCM decode is cheap integer math, a handful of adds, shifts and a table lookup per sample, which is why a WROOM without PSRAM handles it while also running Wi-Fi and the display. Ivan paired it with a jitter buffer that can grow to two seconds and recovers automatically after a dropout, plus a manual minimum from 100 to 2000 ms and clock drift compensation. Drift matters more than people expect. The Kiwi’s sample clock and your ESP32’s I2S clock are two different crystals on two different continents, and they will never agree exactly. Without compensation the buffer slowly fills or slowly starves, and you get a glitch every few minutes that looks like a network problem and is actually physics.</p><p>The other war story is the WebSocket library. The common Arduino one connected and then got dropped by the Kiwi almost immediately, with badp=5 in the server log. Ivan wrote a smaller custom client that also reports why a disconnect happened. When a protocol peer hangs up on you without explanation, the fastest fix is often to stop using the abstraction and speak the protocol yourself. It is a lesson every embedded developer learns once, usually at the worst possible time.</p><h3>Prototype the Stream Before You Solder</h3><p>Before committing parts to a breadboard, you can poke a Kiwi from a laptop with kiwiclient, the Python client maintained by KiwiSDR&#39;s developer at <a href="https://github.com/jks-prv/kiwiclient">github.com/jks-prv/kiwiclient</a>. It needs Python 3 and numpy, plus PyYAML for its AGC and scanning options.</p><pre>$ git clone https://github.com/jks-prv/kiwiclient<br>$ cd kiwiclient<br>$ pip3 install numpy pyyaml<br>$ python3 kiwirecorder.py --help        # the full flag list lives here</pre><p>A short AM recording from a public receiver looks like this. Swap in a server from the public map at rx.kiwisdr.com and a frequency in kHz.</p><pre># 30-second AM capture at 9.42 MHz; writes a timestamped .wav<br>$ python3 kiwirecorder.py -s kiwi.example.org -p 8073 -f 9420 -m am --tlimit=30</pre><p>The README also documents -m iq --kiwi-wav --station=name for IQ recordings with GNSS timestamps, and a --nc option that streams raw or WAV samples to standard output, which is the old kiwi_nc program folded into kiwirecorder. Pipe that into sox or ffplay and you have a headless radio in a terminal, which is a decent way to measure how stable a given server is from your location before you trust it with the ESP32.</p><p>Keep sessions short. A public KiwiSDR serves a fixed, small number of simultaneous users, and many operators set inactivity or session time limits. Your client occupies a real channel that someone else cannot use. A device that sits tuned to one station all day on a shared receiver is rude, and some operators will block it. If you want always-on listening, run your own Kiwi or an RTL-SDR at home and point the ESP32 at that.</p><h3>Where This Build Goes Next</h3><p>Ivan lists the obvious gaps himself. There is no waterfall yet, which needs a colour display. There is no favourites list. Filter bandwidth options and memory presets are thin, and a commenter on his blog asked for adjustable filters, more memory slots and station names. Band switching sits on a double click because giving bands their own encoder focus would have broken step-size adjustment. One knob is a design constraint, and he is honouring it on purpose.</p><p>For a cyberdeck builder, the module is the interesting part. Strip the OLED and encoder, keep the WebSocket client and the ADPCM decoder, and you have a radio service that runs on any ESP32 with an I2S DAC. Bolt it into a deck that already has a screen and a keyboard, and the deck gains a shortwave receiver for the cost of a few hundred kilobytes of flash.</p><p>The scheduled-listening angle is where it gets fun. A laptop or Pi running kiwirecorder on a timer can capture the same weather fax broadcast or utility net every day and drop the audio into a folder, then hand it to a decoder. Wiring that kind of recurring capture into a bot that pings you on Telegram or Discord when a file lands is exactly the job-plus-integration pattern laid out in the <a href="https://numbpilled.gumroad.com/l/openclaw-operator-workstation">OpenClaw Operator Workstation</a>, whose YAML configs cover scheduled jobs and platform integrations. Keep the schedule polite to the receiver you are borrowing.</p><p><a href="https://numbpilled.gumroad.com/l/openclaw-operator-workstation">OpenClaw Operator Workstation: Local AI Workspace &amp; Runbook</a></p><p>Ivan’s sketch, wiring diagram and write-up are on his Todo-SDR blog, with a Spanish-language video that has English subtitles. Start there, flash it, and go find a receiver on a continent you have never visited. The noise floor on the far side of the world is quieter than yours.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d8b678c4a795" width="1" height="1" alt=""><hr><p><a href="https://radiohackers.com/a-shortwave-radio-with-no-radio-inside-it-d8b678c4a795">A Shortwave Radio With No Radio Inside It</a> was originally published in <a href="https://radiohackers.com">Radio Hackers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I Built an AI-Powered Drone Detection System Using RTL-SDR, Python, and Machine Learning.]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://radiohackers.com/i-built-an-ai-powered-drone-detection-system-using-rtl-sdr-python-and-machine-learning-316ce5ba9e96?source=rss----8946e9a3c7a6---4"><img src="https://cdn-images-1.medium.com/max/1408/1*MshO2vVNjAFJLPByiKOREw.jpeg" width="1408"></a></p><p class="medium-feed-snippet">I captured RF activity using an SDR, converted I/Q samples into spectrograms, and trained a machine-learning model to distinguish&#x2026;</p><p class="medium-feed-link"><a href="https://radiohackers.com/i-built-an-ai-powered-drone-detection-system-using-rtl-sdr-python-and-machine-learning-316ce5ba9e96?source=rss----8946e9a3c7a6---4">Continue reading on Radio Hackers »</a></p></div>]]></description>
            <link>https://radiohackers.com/i-built-an-ai-powered-drone-detection-system-using-rtl-sdr-python-and-machine-learning-316ce5ba9e96?source=rss----8946e9a3c7a6---4</link>
            <guid isPermaLink="false">https://medium.com/p/316ce5ba9e96</guid>
            <category><![CDATA[sdr]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[drones-technology]]></category>
            <dc:creator><![CDATA[Dr.Michael]]></dc:creator>
            <pubDate>Wed, 11 Nov 2026 23:52:38 GMT</pubDate>
            <atom:updated>2026-11-11T23:52:36.830Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[How a Logout Endpoint Led to an Open Redirect Vulnerability]]></title>
            <link>https://radiohackers.com/how-a-logout-endpoint-led-to-an-open-redirect-vulnerability-by-samadhan-shimple-c67ae4fc2fbb?source=rss----8946e9a3c7a6---4</link>
            <guid isPermaLink="false">https://medium.com/p/c67ae4fc2fbb</guid>
            <category><![CDATA[offsec]]></category>
            <category><![CDATA[bug-bounty]]></category>
            <category><![CDATA[penetration-testing]]></category>
            <category><![CDATA[open-redirect]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <dc:creator><![CDATA[Samadhan shimple]]></dc:creator>
            <pubDate>Wed, 11 Nov 2026 23:27:24 GMT</pubDate>
            <atom:updated>2026-11-11T23:27:23.206Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SnBuZ1kiHwIx-b1aMt-Hlw.png" /><figcaption>This image is a AI Generated</figcaption></figure><p><em>How a simple </em><em>redirect_to parameter can turn a trusted application URL into an unexpected external redirect.</em></p><h3>Introduction</h3><p>Sometimes, while testing a web application, the most interesting findings come from places we do not initially pay much attention to.</p><p>A logout endpoint is a good example. Most users expect it to end their session and take them back to a login page. They usually do not think about where the application might send them afterwards.</p><p>During a security test, I looked at a logout URL that accepted a parameter named redirect_to. That small detail caught my attention because redirect parameters can introduce security problems when an application does not validate their values properly.</p><h3>Understanding the Open Redirect Vulnerability</h3><p>An open redirect occurs when a web application redirects users to a destination controlled by someone else without properly validating that destination.</p><p>For example, consider this URL:</p><p><a href="https://example.com/api/logout?redirect_to=https://evil.com">https://example.com/api/logout?redirect_to=https://evil.com</a></p><p>Here, example.com represents the application&#39;s domain, while evil.com represents an external destination used for testing.</p><p>The parameter redirect_to appears to control where the browser goes after the logout request. If the application accepts an arbitrary external URL, the redirect destination is effectively controlled by the person creating the link.</p><p>The problem is not necessarily the logout functionality itself. The problem is that the application trusts a destination supplied by the user without restricting where that destination can lead.</p><h3>How I Investigated the Issue</h3><p>When testing web applications, I try not to assume that every parameter behaves as its name suggests. Instead, I look at what changes when I modify its value.</p><p>In this case, I focused on the logout endpoint and its redirect_to parameter. I wanted to understand whether the application restricted redirects to its own domain or accepted external destinations.</p><p>I used a controlled test URL to check how the application handled an external redirect target.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fwww.youtube.com%2Fembed%2FY6aPIpS3kOo%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fwww.youtube.com%2Fwatch%3Fv%3DY6aPIpS3kOo&amp;image=https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fi.ytimg.com%2Fvi%2FY6aPIpS3kOo%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/5b3a649511e8e4ae6d85fabcb59fbb9c/href">https://medium.com/media/5b3a649511e8e4ae6d85fabcb59fbb9c/href</a></iframe><h3>The test URL</h3><pre>GET /api/logout?redirect_to=https://evil.com HTTP/1.1<br>Host: example.com</pre><p>The important part of the request is the value supplied to redirect_to.</p><p>A secure implementation should validate that value before using it in a redirect response. Simply accepting a URL because it was included in a request is not sufficient.</p><h3>What I observed</h3><p>In my test, the application redirected the browser to the external destination supplied in the parameter.</p><p>The relevant response would look like this:</p><pre>HTTP/1.1 302 Found<br>Location: https://evil.com</pre><p>The 302 Found status indicates a temporary redirect, while the Location header specifies the destination.</p><p>This behaviour confirmed the open redirect condition in the tested scenario. The application accepted an external redirect destination instead of restricting the user to an approved location.</p><h3>Why Does a Logout Redirect Matter?</h3><p>At first, this might not look like a serious security issue.</p><p>After all, the user is visiting a logout endpoint. They are not necessarily accessing private information, changing account settings, or performing a sensitive operation.</p><p>However, the domain shown in a link can influence whether someone trusts it. A user may recognise the application’s domain and assume that the entire link leads to a legitimate page.</p><p>An attacker could potentially take advantage of that trust by sharing a link that begins with the legitimate application’s domain but redirects to an unrelated external website.</p><p>For example, a user might see the trusted domain in a message, click the link, and then arrive at a fake login page hosted elsewhere. The redirect itself does not steal credentials, but it can make a phishing attempt more convincing.</p><p>This distinction matters when evaluating the vulnerability. An open redirect can support a phishing attack, but it does not automatically mean that credential theft, account takeover, or token leakage has occurred.</p><h3>What Is the Actual Security Impact?</h3><p>The impact depends on how the affected application is used and how the redirect functionality interacts with other features.</p><p>For a logout endpoint, the primary confirmed concern is the ability to redirect a user from a trusted domain to an arbitrary external destination.</p><p>Potential consequences include:</p><ul><li><strong>Phishing support:</strong> The trusted domain may make a malicious link appear more legitimate.</li><li><strong>Trust abuse:</strong> An attacker can use the application’s domain as the starting point of a redirect.</li><li><strong>Security control weaknesses:</strong> Redirects may create problems for systems that rely on trusted-domain checks, depending on how those checks are implemented.</li><li><strong>Authentication-flow risks:</strong> If similar redirect behaviour is present in OAuth, SSO, or other sensitive workflows, the impact could be greater.</li></ul><p>The last point requires particular care. A redirect vulnerability in a logout endpoint does not prove that OAuth tokens, authorisation codes, or session credentials can be stolen.</p><p>Those outcomes require separate evidence showing that sensitive information is actually exposed or that another security control can be bypassed.</p><p>For this finding, I would focus the report on the behaviour that was demonstrated: an externally controlled redirect after logout.</p><h3>How Should This Vulnerability Be Classified?</h3><p>The appropriate classification is <strong>Open Redirect</strong>, also known as an unvalidated redirect.</p><p>The relevant Common Weakness Enumeration identifier is <strong>CWE-601: URL Redirection to Untrusted Site (‘Open Redirect’)</strong>.</p><p>The finding is also relevant to web application security testing guidance concerning client-side redirection.</p><h3>What about severity?</h3><p>Severity is one of the most important parts of a bug bounty report, and it is also one of the easiest parts to overestimate.</p><p>For a logout-only endpoint where the main impact is trust abuse and phishing support, <strong>Low severity may be more appropriate</strong> under some bug bounty programs. A program may also classify the issue as informational or decline it if its policy excludes standalone open redirects.</p><p>I would not assign High severity based only on the possibility that the issue could be combined with OAuth, SSO, or token leakage. Those scenarios need to be tested and supported by evidence.</p><p>This approach makes a report more credible because it separates the confirmed vulnerability from the risks that might exist in other circumstances.</p><h3>How Can Developers Fix It?</h3><p>The good news is that open redirects are generally straightforward to prevent when the application does not need arbitrary external destinations.</p><h3>1. Allow only approved destinations</h3><p>The safest approach is to accept relative paths such as /login or /dashboard when the application only needs to redirect users internally.</p><p>If external destinations are required, developers should use a strict allowlist of approved hosts rather than accepting arbitrary URLs.</p><h3>2. Validate the destination on the server</h3><p>Redirect validation should happen on the server before the application sends a redirect response.</p><p>The application should parse the URL and verify its scheme, hostname, and other relevant components. Checking only whether the string contains the trusted domain is not sufficient.</p><p>For example, a URL such as https://example.com@evil.com has evil.com as its hostname, even though the trusted domain appears in the URL.</p><h3>3. Handle unusual URL formats carefully</h3><p>Developers should also consider scheme-relative URLs, backslashes, encoded characters, and differences between URL parsers.</p><p>Validation must match the way the application ultimately interprets the URL. Otherwise, a value that appears safe during an initial string check may still resolve to an untrusted destination.</p><h3>4. Use a fixed destination after logout</h3><p>If the application does not need customizable redirect destinations, the simplest solution may be to remove the user-controlled parameter entirely.</p><p>After logout, the server can send the user to a predefined location, such as /login. This reduces complexity and removes an unnecessary source of risk.</p><h3>Lessons for Bug Bounty Hunters</h3><p>This test was a useful reminder that security testing is not only about discovering complicated vulnerabilities.</p><p>Small parameters can reveal important assumptions in an application’s design. A redirect parameter may look harmless, but its security depends on how the server validates and uses the supplied value.</p><p>When I investigate a possible open redirect, I want to answer three questions:</p><ol><li>Can I control the destination?</li><li>Does the server actually redirect to an untrusted external destination?</li><li>What security impact can I demonstrate beyond the redirect itself?</li></ol><p>The third question is especially important. A technically valid vulnerability and a reportable bug bounty finding are not always the same thing.</p><p>Understanding the difference helps produce better reports, supports more accurate severity ratings, and avoids overstating what a proof of concept actually demonstrates.</p><h3>Final Thoughts</h3><p>An open redirect in a logout endpoint may appear minor, but it is worth investigating because it shows how an application handles user-controlled destinations.</p><p>The key is to report what the evidence proves. If the browser is redirected to an arbitrary external URL, document the request, the response, and the resulting behaviour. Then explain the realistic risks without claiming an account takeover or credential leak that has not been demonstrated.</p><p>For developers, strict redirect validation or a fixed post-logout destination can prevent this class of issue. For bug bounty hunters, the lesson is simple: test carefully, verify the behaviour, and let the evidence determine the severity.</p><p><strong><em>AI assistance:</em></strong><em> I used an AI-powered grammar and spelling checker to review this article. The ideas, research, technical analysis, and final content are my own.</em></p><p><em>Have questions or want to share your own workflow? Drop a comment below or connect with me on :</em></p><p>🔗Linkdin:<em> </em><a href="https://www.linkedin.com/company/sam-shield/">https://www.linkedin.com/company/sam-shield/</a></p><p>🔗Youtube: <a href="https://www.youtube.com/@sam_shield-w7l">https://www.youtube.com/@sam_shield-w7l</a></p><p>🔗Medium: <a href="https://medium.com/@samadhanshimple">https://medium.com/@samadhanshimple</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c67ae4fc2fbb" width="1" height="1" alt=""><hr><p><a href="https://radiohackers.com/how-a-logout-endpoint-led-to-an-open-redirect-vulnerability-by-samadhan-shimple-c67ae4fc2fbb">How a Logout Endpoint Led to an Open Redirect Vulnerability</a> was originally published in <a href="https://radiohackers.com">Radio Hackers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Crowdsourced Drone Detection Network Is Offering $50 a Month for Receivers.]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://radiohackers.com/a-crowdsourced-drone-detection-network-is-offering-50-a-month-for-receivers-c7a49295f84c?source=rss----8946e9a3c7a6---4"><img src="https://cdn-images-1.medium.com/max/1408/1*Jva7JkqtY-6n8rSKnic5kg.jpeg" width="1408"></a></p><p class="medium-feed-snippet">FliteGrid announced a distributed drone-detection network using Wi-Fi and Bluetooth receivers to collect Remote ID broadcasts. Its model&#x2026;</p><p class="medium-feed-link"><a href="https://radiohackers.com/a-crowdsourced-drone-detection-network-is-offering-50-a-month-for-receivers-c7a49295f84c?source=rss----8946e9a3c7a6---4">Continue reading on Radio Hackers »</a></p></div>]]></description>
            <link>https://radiohackers.com/a-crowdsourced-drone-detection-network-is-offering-50-a-month-for-receivers-c7a49295f84c?source=rss----8946e9a3c7a6---4</link>
            <guid isPermaLink="false">https://medium.com/p/c7a49295f84c</guid>
            <category><![CDATA[wireless]]></category>
            <category><![CDATA[wifi]]></category>
            <category><![CDATA[drones]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Dr.Michael]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 07:09:53 GMT</pubDate>
            <atom:updated>2026-11-10T07:09:52.232Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Turn Your ESP32 into an SDR: Capturing and Analyzing 2.4 GHz Signals with Python]]></title>
            <link>https://radiohackers.com/turn-your-esp32-into-an-sdr-capturing-and-analyzing-2-4-ghz-signals-with-python-1ab692276a0f?source=rss----8946e9a3c7a6---4</link>
            <guid isPermaLink="false">https://medium.com/p/1ab692276a0f</guid>
            <category><![CDATA[esp-sdr]]></category>
            <category><![CDATA[esp32]]></category>
            <category><![CDATA[iot]]></category>
            <category><![CDATA[sdr]]></category>
            <category><![CDATA[wifi]]></category>
            <dc:creator><![CDATA[Likhin Maroli]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 10:11:50 GMT</pubDate>
            <atom:updated>2026-11-09T13:00:57.015Z</atom:updated>
            <content:encoded><![CDATA[<p>Imagine capturing raw radio signals from the airwaves using nothing more than a tiny, inexpensive ESP32 chip. Not long ago, this would have sounded almost impossible without dedicated and often expensive SDR hardware. For radio and SDR enthusiasts, however, things have never been more exciting. The arrival of RTL-SDR brought software-defined radio within reach of hobbyists, making RF experimentation accessible to almost everyone. And now, things have taken another interesting turn.</p><p>An ingenious discovery revealed that the ESP32 has a hidden capability: capturing raw I/Q samples from radio signals. This opened the door to ESP-SDR, an ultra-low-cost software-defined radio built around the ESP32.</p><p>In this article, I’ll show you how to use ESP-SDR to capture radio signals and bring those raw I/Q samples into Python for analysis and signal processing.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bGuK-xVdDwI5Tqp9HC03cA.png" /></figure><h3>Step-1:Installing esp-sdr firmware:</h3><p>There are two ways to install ESP-SDR firmware on your ESP32. You can either build the firmware yourself from the <a href="https://github.com/ESPARGOS/esp-sdr">official ESP-SDR GitHub repository</a> or install a prebuilt firmware directly from the <a href="https://espargos.net/espsdr/">ESP-SDR website</a>.</p><p>The second option is by far the easiest. Simply connect your ESP32 board to your computer using a USB cable, open the ESP-SDR website, and follow the installation instructions provided there. I’ll skip the detailed installation steps since the website makes the process straightforward.</p><p>Once the firmware is installed, it’s a good idea to verify that everything is working correctly. You can do this using the browser-based <a href="https://espargos.net/espsdr/app/">ESP-SDR Viewer</a>.</p><p>Connect your ESP32 and open the viewer. If everything is working as expected, you should start seeing the frequency spectrum and waterfall display, as shown below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*6fkfDY6wJZ0apEDW2EQ0fA.png" /><figcaption>Screen shot from ESP-WebSDR running in my system</figcaption></figure><p>After verifying the firmware is working properly, we can move on to the next step.</p><h3>Step 2: Building the Python code:</h3><p>Before diving into the code, let’s take a moment to understand how the ESP-SDR firmware communicates with our Python application.</p><p>The firmware uses a standard USB serial interface for communication. It implements a simple, text-based command protocol through which we can send commands and receive responses. If you’re interested in exploring the protocol in detail, you can find the complete command reference in the <a href="https://github.com/ESPARGOS/esp-sdr/blob/main/docs/protocol.md">official ESP-SDR documentation</a>.</p><p>In this article, however, I’ll focus only on the commands necessary to capture raw I/Q samples at a specified frequency.</p><p><strong>One important thing to note is that continuous I/Q capture and streaming are not supported by every ESP32 variant. Only the ESP32-S3 and ESP32-S31 support continuous capture and streaming. Other variants support only burst-mode capture. You can read the detailed info about capabilities supported by each ESP32 variant </strong><a href="https://github.com/ESPARGOS/esp-sdr/blob/main/README.md"><strong>here</strong></a></p><p>What exactly does burst mode mean? When we issue a capture command, the firmware captures a fixed number of samples and sends them to our application. To capture the next batch of samples, we must issue another capture command. This introduces a delay between consecutive captures, unlike continuous streaming, where samples are captured and transmitted continuously.</p><p>I’m using an ESP32-C6, which supports only burst-mode I/Q capture. So, in the following sections, I’ll walk you through the commands and Python code required to capture I/Q samples using this mode.</p><p><strong>Connecting to the firmware:</strong></p><pre>Connecting with the firmware:<br><br>PORT = &quot;/dev/ttyACM0&quot;     # Serial port in my Linux system(Rpi5) where esp32 is connected<br>BAUD = 2000000            # Default baud rate<br>FREQ = 2457               # Center frequency the chip should tune in<br>SAMPLE_RATE = 80e6        # Sample rate; In this case 80M samples/second<br><br>ser = serial.Serial(PORT, BAUD, timeout=3) # Connecting to esp32</pre><h3>A Helper Function to Send Commands to the ESP32</h3><p>Sending commands to the ESP32 and reading its responses is something we’ll need to do repeatedly throughout this program. Rather than writing the same serial communication code over and over again, let’s create a small helper function to handle this operation.</p><p>With this function in place, we can easily send commands to the firmware and retrieve its responses whenever needed, keeping the rest of our code clean and readable.</p><pre>def command(cmd):<br>ser.write((cmd + &#39;\n&#39;).encode())<br>ser.flush()<br>response = ser.readline().decode(errors=&quot;replace&quot;).strip()<br>print(&quot;TX:&quot;, cmd)<br>print(&quot;RX:&quot;, response)<br>return response</pre><h3>Synchronising with the ESP32</h3><p>Before we begin communicating with the firmware, we first need to synchronise with the ESP32. This ensures that both sides are ready to exchange commands and responses.</p><p>The following example shows the synchronisation process. In the command logs, TX: indicates a command transmitted by our Python application to the ESP32, while RX: represents the response received from the firmware.</p><p>Let’s look at the commands and their corresponding responses:</p><p>The synchronisation command has the following format:</p><pre>TX: SYNC &lt;nonce&gt;<br>RX: SYNC &lt;nonce&gt;</pre><p>Here, nonce is a number generated by our Python application and sent to the firmware. The firmware should respond with a SYNC command containing the same nonce. If the received response matches the command we sent, synchronisation is considered successful.</p><p>Once synchronisation is established, we can proceed with sending other commands to the firmware.</p><p><strong>Corresponding Python code:</strong></p><pre>Corresponding Code:<br>nounce = 123456789  # I am using a fixed nounce here, a random nounce will be better.<br><br>response = command(f&quot;SYNC {nounce}&quot;)<br>if response!= f&quot;SYNC {nounce}&quot;:<br>    raise RuntimeError(f&quot;Synchronization with the firmware failed&quot;)</pre><h3>Setting the Capture Frequency</h3><p>Before capturing I/Q samples, we need to configure the RF centre frequency. We can do this using the FREQ command.</p><p>The command format and firmware response are shown below:</p><pre>TX: FREQ &lt;frequency&gt;<br>RX: OK</pre><p>Here, &lt;frequency&gt; specifies the desired centre frequency in MHz. The firmware responds with OK once the frequency configuration is successful.</p><p><strong>Corresponding Python code:</strong></p><pre>command(f&quot;FREQ {freq}&quot;)</pre><p>In our example, freq is set to 2457 MHz, corresponding to Wi-Fi channel 10 in the 2.4 GHz band.</p><h3>Issuing the Capture Command</h3><p>Now that we have configured the centre frequency, we’re ready to capture I/Q samples. We can initiate the capture process using the CAP16 command.</p><p>The command follows this format:</p><pre>TX: CAP16 &lt;num_samples&gt; &lt;rate_code&gt;<br>RX: DATA &lt;num_samples&gt; &lt;CRC&gt; &lt;time_in_us&gt;<br><br></pre><p>In this example, I’m using 16000 as the number of samples and 0 as the rate code, which corresponds to a sampling rate of 80 MSamples/s.</p><p>Therefore, our capture command will be:</p><pre>TX:CAP16 16000 0<br></pre><p>Once the command is issued, the firmware captures the requested number of samples and responds with a DATA message containing three important pieces of information:</p><pre>RX:DATA 16000 c71df737 47  # This line is known as header<br><br>#Here 16000 is the number of samples captured and c71df737 is the CRC, and 47 <br># is the time in microsecond took to capture data.</pre><ul><li><strong>num_samples:</strong> The actual number of samples captured.</li><li><strong>CRC:</strong> A Cyclic Redundancy Check value used to verify the integrity of the received data and detect transmission errors.</li><li><strong>time_in_us:</strong> The time taken to capture the samples, expressed in microseconds.</li></ul><p>The firmware then transfers the captured I/Q data to our Python application. We’ll look at how to receive and process this binary data in the next section.</p><p><strong>Corresponding Python code:</strong></p><pre>Corresponding Code:<br>header = command(f&quot;CAP16 {num_samples} {rate_code}&quot;)<br>    parts = header.split()<br>    if parts[0] != &quot;DATA&quot;:<br>      raise RuntimeError(&quot;Invalid response&quot;)<br>    count = int(parts[1])<br>    expected_crc = int(parts[2], 16) # Parameter 16 is passed because crc is hex<br>    capture_us = float(parts[3])<br><br># I think the code is self explanatory</pre><h3>Reading the Captured I/Q Samples</h3><p>After issuing the capture command, the firmware first sends a response header containing the metadata we discussed earlier. It then starts transmitting the captured I/Q data over the serial interface.</p><p>We need to read this binary data from the serial port. However, there’s one important detail to consider: <strong>the firmware may transmit the data in multiple chunks rather than sending it all at once.</strong></p><p>If we simply call ser.read() and expect to receive the entire dataset in a single operation, we may end up with incomplete data or a serial timeout.</p><p>To handle this, let’s introduce a helper function called read_exact(). It keeps reading from the serial port until the requested number of bytes has been received.</p><p><strong>The function:</strong></p><pre>def read_exact(size):<br>    data = bytearray()<br><br>    while len(data) &lt; size:<br>        chunk = ser.read(size - len(data))<br><br>        if not chunk:<br>            raise TimeoutError(&quot;Serial timeout&quot;)<br><br>        data.extend(chunk)<br><br>    return bytes(data)</pre><p>The function is straightforward:</p><ol><li>It initialises an empty bytearray to accumulate the received data.</li><li>It repeatedly reads the remaining number of bytes from the serial port.</li><li>Each received chunk is appended to the buffer.</li><li>If a read operation times out before all the requested bytes arrive, it raises a TimeoutError.</li><li>Once all the bytes have been received, it returns the complete data as a bytes object.</li></ol><p>This ensures that we don’t accidentally process an incomplete I/Q capture.</p><h3>Understanding the I/Q Sample Data Format</h3><p>After transmitting the header, the firmware sends the actual I/Q sample data over the serial interface.</p><p>Each sample consists of two components: an in-phase (I) component and a quadrature (Q) component. Each component is represented by a signed 8-bit integer (1 byte).</p><p>Therefore, for 16,000 complex samples, we’ll receive:</p><p>16000 x 2 = 32000 bytes</p><p>The byte arrangement follows this pattern:</p><pre>I0 Q0 I1 Q1 I2 Q2 I3 Q3 ...</pre><p>At this stage, we don’t need to separate the I and Q components. Instead, we’ll read the entire dataset into a single buffer and process it later.</p><p><strong>Corresponding Python code:</strong></p><pre>raw = read_exact(count * 2)  # count = 16000 samples</pre><p>Since each complex sample contains two bytes (one for I and one for Q), we multiply the sample count by two to determine the total number of bytes to read.</p><p>We now have the complete raw I/Q data in memory, ready for further processing.</p><h3>Verifying Data Integrity Using CRC</h3><p>Now that we’ve received the complete I/Q dataset, the next step is to verify its integrity. During transmission, data can potentially get corrupted, so we need a way to ensure that the received bytes match what the firmware originally transmitted.</p><p>Fortunately, the firmware provides a CRC (Cyclic Redundancy Check) value in the response header. We can calculate the CRC of the received data and compare it with the expected CRC provided by the firmware.</p><p><strong>Corresponding Python code:</strong></p><pre>actual_crc = zlib.crc32(raw) &amp; 0xffffffff<br>if expected_crc != actual_crc:<br>    raise RuntimeError(&quot;CRC Mismatch&quot;)<br>else:<br>    print(&quot;CRC Check OK&quot;)</pre><h3>Separating I and Q Components</h3><p>Now that we’ve successfully received the data and verified its integrity using CRC, we can move on to processing the I/Q samples.</p><p>The next step is to separate the interleaved I and Q components into two independent NumPy arrays. We’ll then combine them into complex numbers, which makes subsequent signal processing operations much easier.</p><p><strong>Corresponding Python code:</strong></p><pre>data = np.frombuffer(raw, dtype=np.int8)<br><br>I = data[0::2].astype(np.float32)   # Extract I components<br>Q = -data[1::2].astype(np.float32)  # Extract Q components<br><br>samples = I + 1j * Q               # Combine into complex samples</pre><p>Let’s break this down:</p><ul><li><strong>np.frombuffer():</strong> Interprets the raw byte buffer as an array of signed 8-bit integers.</li><li><strong>I = data[0::2]:</strong> Extracts every other element starting from index 0, giving us the I components.</li><li><strong>Q = -data[1::2]:</strong> Extracts every other element starting from index 1, giving us the Q components. Notice that we negate the Q values to account for the I/Q sign convention used here.</li><li><strong>samples = I + 1j * Q:</strong> Combines the two arrays into a single array of complex numbers.</li></ul><p>We now have our I/Q data represented as complex-valued NumPy samples, ready for further signal processing and analysis.</p><p>Now, the samples variable contains our captured I/Q data as a complex-valued NumPy array. <strong>And finally, we&#39;ve done it! We&#39;ve successfully captured raw I/Q samples from the airwaves using nothing more than an ESP32.</strong></p><p>But capturing the data is only half the fun. Let’s explore what we’ve captured by plotting the waveform and visualising the I/Q samples.</p><p>In the next section, we’ll use Matplotlib to plot the signal amplitude over time and see what our captured RF signal actually looks like.</p><h3>Plotting I and Q Components Against Time</h3><p>Now that we have our I/Q samples in a complex-valued NumPy array, let’s visualise them. We’ll plot the I and Q components separately against time to see how the captured signal varies.</p><p>Since our sampling rate is 80 M Samples/s, the time interval between consecutive samples is:</p><p>Ts = 1 / (80 × 1⁰⁶) = 12.5 ns</p><p>We can construct a time axis using the sampling rate and plot both components using Matplotlib.</p><p><strong>Corresponding Python code:</strong></p><pre>N = len(samples)<br># Time axis in microseconds<br>time = np.arange(N) / SAMPLE_RATE * 1e6<br>plt.figure(figsize=(12, 5))<br>plt.plot(time, samples.real, label=&quot;I&quot;)<br>plt.plot(time, samples.imag, label=&quot;Q&quot;)<br>plt.xlabel(&quot;Time (µs)&quot;)<br>plt.ylabel(&quot;Amplitude&quot;)<br>plt.title(&quot;Received IQ Signal&quot;)<br>plt.legend()<br>plt.grid(True)<br>plt.tight_layout()<br>plt.show()<br></pre><p>Let’s break down what’s happening here:</p><ul><li>N = len(samples) gives us the total number of complex samples.</li><li>np.arange(N) / SAMPLE_RATE generates the time instant corresponding to each sample.</li><li>Multiplying by (10⁶) converts the time axis from seconds to microseconds.</li><li>samples.real extracts the I component, while samples.imag extracts the Q component.</li></ul><p>Finally, Matplotlib plots both components on the same graph, allowing us to observe how the I and Q signals vary over time.</p><p><strong>And there we have it!</strong> We’ve successfully captured raw RF I/Q samples using an ESP32 and visualised them in Python.</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*CeVh_EtrI_IxI3Af4R8Kfw.png" /></figure><p><strong>Plotting Magnitude against time:</strong></p><pre>magnitude = np.abs(samples)<br><br>plt.figure(figsize=(12, 4))<br>plt.plot(time, magnitude)<br>plt.xlabel(&quot;Time (µs)&quot;)<br>plt.ylabel(&quot;Magnitude&quot;)<br>plt.title(&quot;IQ Signal Magnitude&quot;)<br>plt.grid(True)<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*lHfcS9udNUDhCBu3aQUn2Q.png" /></figure><p>IQ scatter plot:</p><pre>I = samples.real<br>Q = samples.imag<br><br>plt.figure(figsize=(8, 8))<br>plt.scatter(I, Q, s=2, alpha=0.5)<br>plt.xlabel(&quot;In-phase (I)&quot;)<br>plt.ylabel(&quot;Quadrature (Q)&quot;)<br>plt.title(&quot;IQ Scatter Plot&quot;)<br>plt.axhline(0, color=&quot;gray&quot;, linewidth=0.5)<br>plt.axvline(0, color=&quot;gray&quot;, linewidth=0.5)<br>plt.grid(True)<br>plt.axis(&quot;equal&quot;)<br>plt.tight_layout()<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*ldgj8WKqmjf75crgiZJNLg.png" /></figure><h3>Playing with IQ Samples: A Little DSP with Python</h3><p>Now, let&#39;s do some simple signal processing on the captured signal</p><h3>Calculating Signal Power</h3><p>One of the simplest signal-processing operations we can perform on captured IQ samples is calculating signal power. This is useful for identifying periods of increased RF activity and detecting possible wireless transmissions.</p><p>An IQ sample consists of two components: the in-phase component (I) and the quadrature component (Q).</p><p>The instantaneous signal power can be calculated by squaring both components and adding them together:</p><p><strong>P[n] = I[n]² + Q[n]²</strong></p><p>We can implement this easily using NumPy:</p><pre>import numpy as np<br>import matplotlib.pyplot as plt<br><br># Calculate instantaneous power<br>power = I**2 + Q**2<br># Plot signal power<br>plt.figure(figsize=(12, 5))<br>plt.plot(power)<br>plt.xlabel(&quot;Sample&quot;)<br>plt.ylabel(&quot;Normalized Power&quot;)<br>plt.title(&quot;Instantaneous Signal Power&quot;)<br>plt.grid()<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5eRA69v19x3Lb9Ri-LdxMA.png" /></figure><p>The resulting plot shows how signal power varies across the captured samples.</p><p>However, instantaneous power can fluctuate considerably between consecutive samples. To obtain a smoother representation, we can calculate the moving average of the signal power.</p><p>A moving average filter calculates the average power over a sliding window of samples. For example, with a window size of 100, it calculates the average power of 100 consecutive samples and then moves forward by one sample to repeat the calculation.</p><p>We can implement this using NumPy’s convolution function:</p><pre>window = 100<br><br>avg_power = np.convolve(<br>    power,<br>    np.ones(window) / window,<br>    mode=&quot;valid&quot;<br>)<br>plt.figure(figsize=(12, 5))<br>plt.plot(avg_power)<br>plt.xlabel(&quot;Sample&quot;)<br>plt.ylabel(&quot;Average Power&quot;)<br>plt.title(&quot;Moving Average Signal Power&quot;)<br>plt.grid()<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ll-4sFPxt7YhrzD11TUemg.png" /></figure><p>The moving average smooths out short-term power fluctuations, making it easier to observe changes in signal activity.</p><p>This technique is particularly useful for detecting possible Wi-Fi transmissions, since wireless transmissions generally produce periods of increased received signal energy.</p><p><strong>Note:</strong> The calculated power is expressed in normalised digital units squared when the IQ samples are normalised. It is not an absolute RF power measurement in watts or dBm.</p><h3>Applying a Low-Pass Filter</h3><p>Another interesting signal-processing operation we can perform on captured IQ samples is low-pass filtering.</p><p>A low-pass filter allows frequency components below a specified cutoff frequency to pass through while attenuating higher-frequency components. This is useful for removing unwanted high-frequency components and isolating a particular frequency range.</p><p>We can use SciPy to implement a Butterworth low-pass filter. A Butterworth filter has a maximally flat frequency response in its passband.</p><p>Let’s apply a fifth-order Butterworth filter with a cutoff frequency of 5 MHz to our captured IQ samples.</p><pre>import numpy as np<br>import matplotlib.pyplot as plt<br>from scipy.signal import butter, sosfilt<br><br># Combine I and Q into a complex IQ signal<br>iq = I + 1j * Q<br># Sampling frequency<br>Fs = 80e6<br># Design a fifth-order Butterworth low-pass filter<br>cutoff = 5e6<br>sos = butter(<br>    5,<br>    cutoff,<br>    fs=Fs,<br>    output=&quot;sos&quot;<br>)<br># Apply the filter<br>filtered = sosfilt(sos, iq)<br># Plot original and filtered signals<br>plt.figure(figsize=(12, 5))<br>plt.plot(np.real(iq[:1000]), label=&quot;Original&quot;)<br>plt.plot(np.real(filtered[:1000]), label=&quot;Filtered&quot;)<br>plt.xlabel(&quot;Sample&quot;)<br>plt.ylabel(&quot;Amplitude&quot;)<br>plt.title(&quot;Low-Pass Filtering&quot;)<br>plt.legend()<br>plt.grid()<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pRty7xtg3iP5ceJ-nUz3gw.png" /></figure><p>Here, we first combine the I and Q components into a complex signal. We then design a Butterworth filter using SciPy and apply it to the complex IQ samples.</p><p>The cutoff frequency determines which frequency components are retained. In this example, frequencies near the baseband DC component and below approximately 5 MHz are passed, while higher-frequency components are attenuated.</p><p>This demonstrates how we can manipulate captured RF signals entirely in software using Python.</p><p><strong>Note:</strong> The filter operates on the complex baseband IQ signal, not directly on the original 2.4 GHz RF carrier. Also, the cutoff frequency is relative to the receiver’s tuned centre frequency.</p><h3>Time to Wrap Up</h3><p>My ultimate dream is to decode a complete Wi-Fi frame entirely in Python. Whether that’s possible with the ESP32-C6 remains to be seen, but I’m certainly going to give it my best shot!</p><p>I hope this article serves as a starting point for others interested in experimenting with ESP-SDR hardware and RF signal processing using Python. There’s still so much to explore, and I’d love to see others take this further and share their experiments.</p><p>Until then, happy ESP-SDR experimentation and Python-based RF signal processing!</p><p>Here you can find my <a href="https://github.com/likhin-maroli/esp-sdr-python/tree/main">GitHub Repository </a>containing the Python code to capture and plot IQ samples.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1ab692276a0f" width="1" height="1" alt=""><hr><p><a href="https://radiohackers.com/turn-your-esp32-into-an-sdr-capturing-and-analyzing-2-4-ghz-signals-with-python-1ab692276a0f">Turn Your ESP32 into an SDR: Capturing and Analyzing 2.4 GHz Signals with Python</a> was originally published in <a href="https://radiohackers.com">Radio Hackers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[One Email Parameter Was All It Took: How Broken Access Control Let Me Create a Folder in Another…]]></title>
            <link>https://radiohackers.com/one-email-parameter-was-all-it-took-how-broken-access-control-let-me-create-a-folder-in-another-c1ab24d34f09?source=rss----8946e9a3c7a6---4</link>
            <guid isPermaLink="false">https://medium.com/p/c1ab24d34f09</guid>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[broken-access-control]]></category>
            <category><![CDATA[penetration-testing]]></category>
            <category><![CDATA[idor-vulnerability]]></category>
            <category><![CDATA[bug-bounty]]></category>
            <dc:creator><![CDATA[Samadhan shimple]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 09:59:08 GMT</pubDate>
            <atom:updated>2026-11-09T09:59:07.065Z</atom:updated>
            <content:encoded><![CDATA[<h3>One Email Parameter Was All It Took: How Broken Access Control Let Me Create a Folder in Another User’s Account</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MxD9Z7YIPFbk77Wzft5_sA.png" /><figcaption>This image is a AI Generated</figcaption></figure><p><strong>A practical bug bounty story about testing authorisation, manipulating request parameters, and finding a potential IDOR vulnerability.</strong></p><h3>Introduction</h3><p>While testing a web application’s folder-creation functionality, I noticed something interesting. The application sent an email address along with the request whenever a user created a new folder.</p><p>At first, this looked like a normal part of the application’s functionality. But it raised an important security question: <strong>Does the server verify which account the authenticated user is allowed to modify, or does it trust the email address sent by the browser?</strong></p><p>To investigate this behaviour, I used two test accounts and Burp Suite to examine how the application handled folder-creation requests.</p><h3>Understanding the Vulnerability</h3><p>Broken Access Control occurs when an application fails to properly enforce permissions for a requested action or resource.</p><p>For example, a user should not be able to modify another user’s account simply by changing an identifier in an HTTP request.</p><p>One related vulnerability is Insecure Direct Object Reference (IDOR). It occurs when an application uses a user-controlled reference to access an object without properly checking whether the current user is authorised to access it.</p><p>In this case, the parameter of interest was an email address associated with the folder-creation request.</p><p>If the server accepts a modified email address and performs an action in another user’s account without checking permissions, this may indicate a cross-account authorisation flaw.</p><h3>My Testing Setup</h3><p>For this test, I used two accounts that I controlled.</p><ul><li><strong>Account 1:</strong> The account used to initiate the folder-creation request.</li><li><strong>Account 2:</strong> The account used to verify whether the folder appeared in another account.</li></ul><p>I logged in to both accounts using separate browser sessions. This helped me compare the behaviour of each account without confusing their authenticated sessions.</p><p>I used Burp Suite to intercept and inspect the HTTP request generated when creating a folder.</p><h3>Step 1: Create a Folder in Account 1</h3><p>I started by logging in to Account 1 and opening the folder-management feature.</p><p>Next, I used the Add Folder functionality to create a new folder. Before forwarding the request, I intercepted it in Burp Suite.</p><p>I examined the request parameters to understand how the application identified the account associated with the new folder.</p><h3>Step 2: Identify the Email Parameter</h3><p>While inspecting the request, I noticed that it contained an email parameter.</p><p>This was worth investigating because an email address is an identifier, not proof that a user has permission to modify the associated account.</p><p>A secure application should verify the authenticated user’s permissions on the server. It should not rely on a client-controlled parameter to make authorisation decisions.</p><h3>Step 3: Modify the Request</h3><p>To test the authorisation boundary, I changed the email parameter from Account 1’s email address to the email address associated with Account 2.</p><p>I then forwarded the modified request using Burp Suite.</p><p>The purpose of this test was to determine whether the server would reject an operation targeting an account that the authenticated user was not authorised to manage.</p><p>This type of test can help identify authorisation checks that are missing or incorrectly implemented.</p><h3>Step 4: Verify the Result</h3><p>After forwarding the request, I checked Account 2 to see whether the folder had been created.</p><p><strong>The observed result was that the folder appeared in Account 2.</strong></p><p>This was an important finding because the request originated from Account 1, but the folder was created under Account 2 after the email parameter was changed.</p><p>Based on this result, the behaviour suggested that the application might be using the supplied email address to determine the target account without properly enforcing the required authorisation checks.</p><p>However, the exact cause still needs to be confirmed. For example, the application could have an intended delegation feature or another authorisation rule that affects the result.</p><h3>Why Is This a Security Issue?</h3><p>Creating a folder might seem like a minor action. However, security testing is not only about how sensitive an operation appears; it is also about whether the operation respects account boundaries.</p><p>If an authenticated user can create folders in another user’s account without permission, the application may allow unauthorised changes to that user’s workspace.</p><p>Depending on how the application uses folders, this could create unwanted content, interfere with organisation, or affect workflows that depend on folder ownership.</p><p>The impact should be limited to what has actually been demonstrated. If only folder creation has been verified, it would be incorrect to claim that the vulnerability also allows file access, data theft, deletion, or account takeover.</p><h3>How Should This Vulnerability Be Classified?</h3><p>The most appropriate classification depends on the application’s behaviour and intended access-control model.</p><p><strong>Potential vulnerability:</strong> Broken Access Control</p><p><strong>Related CWE:</strong> CWE-639 — Authorisation Bypass Through User-Controlled Key, if the evidence confirms that a user-controlled key is used to access or modify another user’s resource without proper authorisation.</p><p><strong>OWASP category:</strong> A01:2021 — Broken Access Control.</p><p><strong>Affected feature:</strong> Add Folder.</p><p><strong>Authentication:</strong> Required in the observed scenario.</p><h3>What About Severity?</h3><p>Severity depends on the impact and the application’s security requirements.</p><p>The final rating should follow the bug bounty program’s severity guidelines. A parameter change alone does not automatically qualify as a high-severity IDOR.</p><h3>How Developers Can Prevent This Issue</h3><p>There are several ways to reduce the risk of this type of authorisation weakness.</p><p><strong>1. Do not trust client-supplied identity parameters.</strong></p><p>An email address supplied by a browser should not, by itself, determine which account a user can modify.</p><p><strong>2. Enforce authorisation on the server.</strong></p><p>Before creating a folder, the application should check whether the authenticated user has permission to perform the requested action in the target account.</p><p><strong>3. Test with multiple accounts.</strong></p><p>Authorisation testing should include requests made by one user that reference resources belonging to another user. Both permitted and denied actions should be tested.</p><p><strong>4. Add automated authorisation tests.</strong></p><p>Developers should test that users cannot create, update, or delete resources in other accounts unless the application’s permission model explicitly allows it.</p><h3>Lessons Learned</h3><p>This test reinforced an important lesson: a small request parameter can reveal a significant authorisation weakness.</p><p>The email parameter was not inherently vulnerable. The security concern was whether the server used that value without independently verifying the user’s permissions.</p><p>For bug bounty hunters, the important part is to identify the account boundary, test it safely with authorised accounts, and verify the actual result.</p><p>A reliable report should explain what was changed, what the server did, why the behaviour violates the expected permission model, and what impact was demonstrated.</p><h3>Final Thoughts</h3><p>Broken Access Control is worth testing because applications often rely on identifiers such as email addresses, user IDs, workspace IDs, or organisation IDs when processing requests.</p><p>Changing an identifier is only the beginning of the test. The real question is whether the server correctly checks authorisation before performing the action.</p><p>In this scenario, the folder appeared in the second account after the email parameter was changed. That observation provides a strong reason to investigate the application’s account-level authorisation logic further.</p><p><strong>Remember:</strong> Report what you can reproduce, demonstrate the impact you can prove, and avoid claiming consequences that you have not verified.</p><p><strong><em>AI assistance:</em></strong><em> I used an AI-powered grammar and spelling checker to review this article. The ideas, research, technical analysis, and final content are my own.</em></p><p><em>Have questions or want to share your own workflow? Drop a comment below or connect with me on :</em></p><p>🔗Linkdin:<em> </em><a href="https://www.linkedin.com/company/sam-shield/">https://www.linkedin.com/company/sam-shield/</a></p><p>🔗Youtube: <a href="https://www.youtube.com/@sam_shield-w7l">https://www.youtube.com/@sam_shield-w7l</a></p><p>🔗Medium: <a href="https://medium.com/@samadhanshimple">https://medium.com/@samadhanshimple</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c1ab24d34f09" width="1" height="1" alt=""><hr><p><a href="https://radiohackers.com/one-email-parameter-was-all-it-took-how-broken-access-control-let-me-create-a-folder-in-another-c1ab24d34f09">One Email Parameter Was All It Took: How Broken Access Control Let Me Create a Folder in Another…</a> was originally published in <a href="https://radiohackers.com">Radio Hackers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Vulnerability Statistics 2026: 6.8 Million Findings Show Risk Isn’t Where You’re Testing]]></title>
            <link>https://radiohackers.com/vulnerability-statistics-2026-6-8-million-findings-show-risk-isnt-where-you-re-testing-5c9fc1a5f2f1?source=rss----8946e9a3c7a6---4</link>
            <guid isPermaLink="false">https://medium.com/p/5c9fc1a5f2f1</guid>
            <category><![CDATA[vulnerability]]></category>
            <dc:creator><![CDATA[Astra Security]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 09:55:47 GMT</pubDate>
            <atom:updated>2026-11-09T09:55:46.797Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hpufFJRaWTEi1vTFqb6Iag.png" /></figure><p>Every December, we do the same thing everyone in the security sector does. We pull the previous year’s data, count the vulnerabilities, and try to figure out what the numbers are trying to say. It’s become a regular ritual for folks running this space, and every year the data tells us something we didn’t expect going in.</p><p>Last year, our platform and pentests logged 6.8 million findings across 8,000+ engagements, and the most useful insights this year were about the <em>geography</em> of vulnerabilities. Where exposures actually live, versus where our testing programs are scoped to find them.</p><p>To put it simply, we ignored the costliest vulnerabilities in front of us while patching the simplest ones.</p><p>We wrote all of this up in the<a href="https://www.getastra.com/reports/state-of-pentesting"> State of Continuous Pentesting Report 2026</a> if you want the full breakdown. Here’s the short version.</p><h3>80% of Cloud Credential Leaks Were Found in Mobile Pentests</h3><p>In 2025, 80% of tracked AWS and S3 credential exposure was surfaced during mobile app pentests, and cloud infrastructure scans caught the other 20%.</p><p>Devs often embed an IAM access key or a hardcoded secret into a mobile client to skip a backend round-trip during a sprint. Once the APK or IPA ships, that key sits in the binary with no clear owner.</p><p>Anyone with strings, APKtool, or a five-line Frida script can pull the credentials, and our data showed this single gap carried over a million dollars in tracked financial exposure before anyone went looking for it deliberately.</p><h3>Other Findings Worth Sitting With</h3><p>Cloud vulnerabilities grew 44x in 2025 while cloud pentesting coverage grew 1.23x, which works out to a 37-to-1 disparity between where risk is growing and where testing is scaling. Cloud alone now drives 39% of vulnerability volume on 14% of pentest engagements, and cloud pentests return an average of 7,480 findings per engagement against 3,060 for web, roughly 2.4x the yield at a fraction of the coverage.</p><p>Severity tells its own story. In 2024, 1 in 40 findings was Critical, and in 2025 it was 1 in 10. Criticals grew 14.6x faster than everything else, and a critical vulnerability was found somewhere on the platform every 48 seconds. Dashboards reporting “total findings up 275%” flatten all of that into background noise.</p><p>The strangest phenomenon in the data was December itself. It produced 1.8 million vulnerabilities, more than all of 2024 combined. Most of that pileup was locked in a month earlier, in November, which was the quietest scanning month of the year by a wide margin. Every quiet November builds a loud December, and the security folks and devs on call over the holidays wear it.</p><p>Then there’s Q3 versus Q4. Q4 had 63% more raw findings, so the count-based dashboards flagged it as the crisis quarter, but September alone was the most dangerous month of the year, and Q3 was 29% more dangerous per finding than Q4.</p><p>The teams that treated Q4 as the crisis spent their best engineers on a compliance surge, while September sat quietly producing the most serious findings of the year with whoever was left.</p><p>The report breaks all of this down quarter by quarter.</p><h3>IDOR Is Everywhere, and Nobody’s Fixing It</h3><p>IDOR (Insecure Direct Object Reference) was the only vulnerability class in the 2025 dataset that appeared across all six tested surfaces simultaneously: web, API, cloud, iOS, Android, and network. Every other class in the dataset has a home surface. IDOR carries $1.1M in tracked financial exposure, the highest of any single vulnerability class in the dataset.</p><p>IDOR is a consequence of how dev teams reason about data ownership when they’re building API endpoints under sprint pressure. Fixing IDOR at the design layer means treating it as a cross-surface architectural pattern instead of a per-surface finding.</p><p>This is exactly where our Autonomous Pentesting product, rolling out to customers this year, changes the shape of the problem. It runs continuously against every deployment, reasons about application architecture the way a human pentester would, and surfaces IDOR patterns easily.</p><h3>What to Fix Now</h3><p>None of the shifts below requires a bigger budget, and all of them are executable inside your existing program. If you take only one thing from the 2025 data, it’s that most security teams already have the tools they need. What’s missing is the wiring between how the attack surface behaves and how the program is set up to read it.</p><ul><li><strong>Assign ownership to cross-surface findings: </strong>A hardcoded cloud key in a mobile binary belongs to nobody today. It gets forwarded between the mobile lead and the cloud lead until someone drops it. Designate a single owner for any finding that spans two or more scopes.</li><li><strong>Retire “total findings” as a risk metric: </strong>Measure by severity, exploitability, and business impact. If your dashboard can’t tell 2024 from 2025 because the total grew smoothly, it’s measuring throughput instead of risk.</li><li><strong>Budget by attack surface, not by legacy scope: </strong>If cloud drives 39% of risk, it should get more than 14% of the security spend. If 80% of your cloud credential exposure surfaces in a mobile scope, mobile is part of your cloud program.</li></ul><h3>Final Thoughts</h3><p>The hardest problem in our sector is admitting that vulnerabilities don’t live where the org chart says they should.</p><p>The credential in the binary, the cloud risk on a web budget, and the Critical hidden inside a “total findings grew 275%” headline. All of it was visible, all of it filed in the wrong column, and read by the team that wasn’t asked to look.</p><p>The exposures are already there, and it’s about time we ask whether your security strategy is scoped to see them.</p><p>If any of this rhymes with what your dashboards are showing,<a href="https://www.getastra.com/reports/state-of-pentesting"> you can read the full State of Continuous Pentesting 2026 report here</a>.</p><p>SAST tells you what could be exploited based on how the code is written. DAST tells you what actually can be exploited when the app is running. Mature programs run both, because each covers the other’s blind spots.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5c9fc1a5f2f1" width="1" height="1" alt=""><hr><p><a href="https://radiohackers.com/vulnerability-statistics-2026-6-8-million-findings-show-risk-isnt-where-you-re-testing-5c9fc1a5f2f1">Vulnerability Statistics 2026: 6.8 Million Findings Show Risk Isn’t Where You’re Testing</a> was originally published in <a href="https://radiohackers.com">Radio Hackers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ELRS vs. DJI O4: I Built an RF Benchmark Instead of Another Drone Detector]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://radiohackers.com/elrs-vs-dji-o4-i-built-an-rf-benchmark-instead-of-another-drone-detector-f2d2d2be1e87?source=rss----8946e9a3c7a6---4"><img src="https://cdn-images-1.medium.com/max/1408/1*6rfsTmyVrFFFQKiahN7EMg.jpeg" width="1408"></a></p><p class="medium-feed-snippet">An actively developed open-source project is measuring modern FPV links using HackRF, PlutoSDR, and Raspberry Pi nodes. A reproducible&#x2026;</p><p class="medium-feed-link"><a href="https://radiohackers.com/elrs-vs-dji-o4-i-built-an-rf-benchmark-instead-of-another-drone-detector-f2d2d2be1e87?source=rss----8946e9a3c7a6---4">Continue reading on Radio Hackers »</a></p></div>]]></description>
            <link>https://radiohackers.com/elrs-vs-dji-o4-i-built-an-rf-benchmark-instead-of-another-drone-detector-f2d2d2be1e87?source=rss----8946e9a3c7a6---4</link>
            <guid isPermaLink="false">https://medium.com/p/f2d2d2be1e87</guid>
            <category><![CDATA[radio]]></category>
            <category><![CDATA[drones]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[sdr]]></category>
            <category><![CDATA[raspberry-pi]]></category>
            <dc:creator><![CDATA[Dr.Michael]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 01:43:38 GMT</pubDate>
            <atom:updated>2026-11-09T01:43:37.034Z</atom:updated>
        </item>
    </channel>
</rss>