In this article, we will discuss user privacy issues that our team has recently discovered in two independent e-signature solutions, which have been fixed by now. In the past we also discovered vulnerabilities related to e-signature, for example, a high risk vulnerability in the central e-government services portal allowed user impersonation.
Typical Usage Scenarios
With document signing, Lithuanian e-signature users have the choice between a few alternatives for storing public/private keys and certificates. We will use standalone government ID cards as an example. The approach described in this article should however be valid for other types of smart cards and USB tokens, except for the mobile signature option. It is worth mentioning that upon insertion of an e-signature media, only the qualified digital certificate, and not the private key, is accessible without the PIN code. This certificate contains the NAME, the SURNAME, and the PERSON CODE of the owner of the certificate. Also, in case of regular use of a smartcard/token, chances are it will remain in the card reader of the computer. Why bother disconnecting it, one may ask?
Example 1 – GoSign misconfiguration
Before exploring the first example, let’s recall what Cross-Origin Resource Sharing (CORS) is. CORS is a mechanism that enables web browsers to perform cross-domain requests using the XMLHttpRequest API in a controlled manner. These cross-origin requests have an “Origin” header, which identifies the domain starting the request.
Let’s take the following code as a proof of concept:
<script> var createCORSRequest = function(method, url) { var xhr = new XMLHttpRequest(); if ("withCredentials" in xhr) { // Most browsers. xhr.open(method, url, true); } else if (typeof XDomainRequest != "undefined") { // IE8 & IE9 xhr = new XDomainRequest(); xhr.open(method, url); } else { // CORS not supported. xhr = null; } return xhr; }; var url = 'https://localhost:8000/certs'; var method = 'GET'; var xhr = createCORSRequest(method, url); xhr.onload = function() { document.write("<pre>"+JSON.stringify(JSON.parse(xhr.response), null, 2)+"</pre>"); }; xhr.send(); </script>
It is probable that the example code is hosted somewhere on the web or embedded in a legitimate web page using, for instance, a stored cross-site scripting vulnerability, or fully compromising the target website. For it to work, the user of the vulnerable e-signing software has to access this website containing the malicious code. See below the output of the script:
One typical security issue with CORS: a web browser sends an “Origin” header, and the web server should reply with the “Access-Control-Allow-Origin” header. That same header should contain only whitelisted trusted domains. In reality, often the “Access-Control-Allow-Origin” header contains a wildcard “*”, meaning that any domain on the web can communicate with the web server ignoring CORS, without limitations. To prevent such a misconfiguration, we recommend using a whitelisting mechanism. This vulnerability falls under the category of “Security Misconfiguration” of OWASP Top 10.
GoSignCore software (version 3.1.3, dated 2020-06-18) is a standalone e-signing application used to sign documents on Windows-based systems provided by the State Enterprise Centre of Registers of Lithuania. The default installation runs a web service on the TCP port 8000 tied to the local host and is the main channel for qualified digital certificate retrieval and data signing. Because of an insecure configuration of CORS, a malicious website can interact with the GoSign web service and retrieve users’ personal information.
Let’s take the following code as a proof of concept:
<script> var createCORSRequest = function(method, url) { var xhr = new XMLHttpRequest(); if ("withCredentials" in xhr) { // Most browsers. xhr.open(method, url, true); } else if (typeof XDomainRequest != "undefined") { // IE8 & IE9 xhr = new XDomainRequest(); xhr.open(method, url); } else { // CORS not supported. xhr = null; } return xhr; }; var url = 'https://localhost:8000/certs'; var method = 'GET'; var xhr = createCORSRequest(method, url); xhr.onload = function() { document.write("<pre>"+JSON.stringify(JSON.parse(xhr.response), null, 2)+"</pre>"); }; xhr.send(); </script>
It is probable that the example code is hosted somewhere on the web or embedded in a legitimate web page using, for instance, a stored cross-site scripting vulnerability, or fully compromising the target website. For it to work, the user of the vulnerable e-signing software has to access this website containing the malicious code. See below the output of the script: