- Introduction
- People
- Threat model
- High level analysis
- Detailed analysis: examples of vulnerabilities
- Other security-relevant issues
Introduction
On Wednesday the 7th of March, VUSec was contacted with the request to look into the security of the Election Software used in the Netherlands, in particular for the municipality elections and the referendum on the Wet op de inlichtingen- en veiligheidsdiensten, both on the 21st of March 2018. During our analysis, we made use of
- the source code for the election software
- the CDROM with the actual software made available to all the municipalities
- the valid inputs for the election software from previous elections (available online here)
We emphasize that we are a university research group that happens to specialize in systems security. We are not a professional pen testing organization and we certainly do not specialize in testing web applications. This means (a) that we just found a subset of the issues and some of them may not be easily exploitable, and (b) no, we will not pentest your website for you 🙂
During our analysis, we focused (almost) exclusively on the code. A related and very readable security analysis by Sijmen Ruwhof that focuses more on the context and use is available here. We agree with his findings and just report on the more technical issues that we discovered.
We only considered the software as it is intended to be used. It may well be that in some country, or for some election, some features are not used. We were interested in the security of the software itself. Even if you use only some of the features, but the software is in general very vulnerable, this should be reason for concern.
Since we had very limited time for our analysis and a ton of other deadlines, our analysis is certainly not comprehensive. Phrased differently, there are almost certainly (many) other security issues besides the ones reported here. Nevertheless, the issues we found, combined with the security analysis by Sijmen Ruwhof are sufficient reason for us to conclude that we should not rely on this software for something that is so essential to the heart of a democratic state itself as the election results.
Indeed, even during our limited analysis, we found a number of security issues. We have termed them all “vulnerabilities” as they allow attackers to bypass security checks and/or manipulate results. Again, for some of them, it is unclear at this point under what circumstances they may be abused. For others this is, unfortunately, very clear.
Also note that, despite reassurances of transparency in the process, it is unclear exactly how the system will be used. This means that we are unable to conduct a complete security analysis. For instance, we recently learned that in some cases, result files generated by OSV will be used in an Excel spreadsheet to add them all up and produce the final results. We have no idea how this is done, or how safe it is. We do know that MS Office (of which Excel is a part) is known for having suffered from many security bugs. We do not know how the data entry takes place, we do not know what scripts are running in Excel, we do not even know what version of Excel is used. This is a bit strange.
Moreover, we want to stress that even if we had not found any issues and the software was well-written while incorporating the latest security insights, then we still would not want to completely rely on software to determine the results of the elections. Software is inherently vulnerable and corruption and manipulation by attackers may have huge consequences for the trustwortiness of the election results, and, as a consequence, the trust voters may have in the democratic system. Do not gamble with the elections. Do not rely on software alone.
People involved in the analysis
- Marco Oliverio
- Sanjay Rawat
- Sebastian Österlund
- Andrei Tatar
- Herbert Bos
Threat model
We assume that the computers involved in the elections are not exposed to the Internet or otherwise networked with other computers, the software on the computers is not compromised prior to the elections, and the election software is not malicious by itself. These are very conservative assumptions and in practice, it may be very hard to guarantee these issues. Still, we give the system deployment the benefit of the doubt and assume that these computers are secure initially and not reachable from the Internet.
However, in case vulnerabilities are present, we assume that an attacker may try to manipulate the software to produce the wrong results. If secure, the software should stop or isolate any form of manipulation. In particular, even if there is a corrupt official at a local polling station, she should not be able to affect count of votes of other polling stations.
Particular example: when one or two polling stations in, say, Amsterdam produce the wrong results (because a corrupt official entered fabricated figures), these results should never be able to diminish the number of votes for a specific party at the station at the next higher level of aggregation (where all the totals of the local polling stations are added up).
High-level analysis
The software we analysed concerns the Ondersteunende Software Verkiezingen (OSV) versie 2.21.4 on for modern (64 bit) machines, developed by the German software vendor IVU Traffic Technologies AG. The software is written in Java and is quite hard to read due to the coding style, but also due to the fact that identifier names as well as documentation strings and comments use English and German inconsistently. In fact, the coding style even mixes languages within a single variable name. Very creative—we had not seen that one before!
The software builds on old server technology. For instance, the version of JBoss (an application server technology) is 4.2.3, which contains dangerous vulnerabilities such as a remote code execution vulnerability. While we were not yet able to exploit this vulnerability, it shows that this is old software and you can use it at your peril. As we will mention later, similar observations are true for other parts of the software stack.
In addition, all analysts unanimously concluded that the code was not of high quality (see also our anecdotal evidence [1]). Within two hours of obtaining the software, we found several issues, including our first cross-site scripting problem and the first simple, automated overnight test of the software triggered another vulnerability that led to a crash. Whether these issues are exploitable is still unclear. The point is that we found them with zero effort in a few hours.
Many obvious security issues surface immediately. For instance, the fact that single-character passwords are happily accepted, and that the double entry procedure (which requires 2 people to enter the votes to guard against deliberate or inadvertent mistakes) allows the second person to simply override the numbers entered by the first person—these are all glaring blunders. Again, we refer to Sijmen Ruwhof for the nice (and worrying) analysis of such issues. Again, in this report we look at the code itself: what checks are missing, or implemented badly, and what system-level vulnerabilities are available for attackers to compromise the system.
Detailed analysis
During our review of the code we stumbled on a number of bad things. We list a few of them here for illustration purposes. We first describe the issues and then provide the two input files (input1 and input2) that abuse these vulnerabilities.
Vulnerability 1: Integer overflow
OSV uses variables of type java.Int to store the numbers of votes. Unfortunately, arithmetic operations (such as addition, subtraction, etc.) are not checked against integer overflows. When the result of an arithmetic operation is more than 2^31 (2 to the power 31), Java will silenty overflow, producing a negative result.
While the numbers of votes in national elections is very unlikely to reach such numbers (we would need an election with roughly half the world population as voters), maliciously crafted input files can easily trigger the faulty behaviour.
To make things worse, the software replaces, in report screens, such negative numbers with the html entity (which shows on screen as an invisible null space) even though the negative numbers are still used in calculation.
Attack example:
- Corrupt officials of two local polling stations (PSB) files tamper with the results files: in each of them the number of votes for a candidate is set to a value “close to” the value 2^31.
- When the total accross all polling stations is computed, these two values will trigger an overflow and add a negative delta to the overall computation.
- Thus, the attacker has breached the security boundaries and succeeded in lowering the overall number of votes for a particular party.
Below you can find the input1 and input2 files that can corrupt the aggregated results by subtracting 11 votes for the first referendum choice. As a bonus, these files also exploit vulnerability 2 to bypass the XML checks (although this is not strictly necessary for attack 1), and vulnerability 3, the no hash-numbers-confirmation.
We captured several of our attacks (including this one) graphically in screenshots, as shown below.
(a) count before | (b) no hash | (c) upload works | (c) count after |
Note:
The reporting screens of the two polling stations will not show errors, but they will show unusual numbers. As a result, this attack may not be stealthy. On the other hand, it is also worth noting that the report screen is not automatically shown after uploading the corrupted xml, requiring the operator to be especially diligent to notice this attack.
Our conclusion is that this is a very serious security issue. In the end, OSV raison d’être is to tally the votes, but as shown above, it cannot even always count them properly. Regardless of whether the votes are sent digitally as .eml files via email or USB sticks (making the vulnerability easily exploitable), or printed by hand and then manually typed in by an official at the station that does the aggregation (making it more likely that the very large numbers draw attention), this bug shows how vulnerable the system is.
You had one job.
Vulnerability 2: XML schema validation and application checks bypass
As a security measure, the OSV software checks that imported files are valid XML files and comply with a corresponding xml schema.
The XML schema enforces, amongs other things that:
- the name/ID of the polling station is correct,
- no negative numbers have been entered, and
- all required fields are filled in
Unfortunately the XML schema validation and relative checks can be bypassed thanks to a very poor logic check that demonstrates lack of security savviness:
In ImportUtil.java:195 :
try {
doc = new Builder(reader, false).build(inputStream);
reader.setFeature("http://apache.org/xml/features/validation/schema", true);
Document validatedDoc = new Builder(reader,true).build(getStreamWithSchema(doc, schema));
// return document root
return validatedDoc.getRootElement();
} catch (ValidityException e) {
LOGGER.error(e, e);
if (doc != null && e.getMessage().contains("kr:RegisteredParty")
&& e.getMessage().contains("xml:space")) {
// Ignore error
// "cvc-complex-type.3.2.2: Attribute 'xml:space' is not allowed to appear in element 'kr:RegisteredParty'"
return doc.getRootElement();
}
throw new ImportException(e.getMessage(), e);
...
There is a bypass of the validation if the “xml:space” and “kr:RegisteredParty” strings are part of the error message. It is trivial to produce a document that generates an error message with these strings. For example, we can add:
<kr:CreatedByAuthority Id="xml:spacekr:RegisteredParty"/>
to produce the error message:
nu.xom.ValidityException: cvc-pattern-valid: Value
'xml:spacekr:RegisteredParty' is not facet-valid with respect to
pattern 'CSB|((HSB|SB)\d+)|(\d{4})' for type
'#AnonType_IdCreatedByAuthority'
Thus the xml is accepted, because the if-condition in the Java snippet shown is satisfied.
The xml validation bypass allows us to skip another (weak) application-level check that works only if the number of elements checked is exactly one (which of course it need not be when not constrained by the xml schema). See for example:
EingangMsgXmlFactory.java:115:
Nodes authority = xmsg.query("//eml:" + XMLTags.EML_AUTHORITY_IDENTIFIER, XMLTags.CONTEXT_EML);
if (authority.size() == 1) {
String authorityId = XMLImportHelper.getAttribute((Element) authority.get(0), XMLTags.ATTR_EML_ID);
// ... checks here
}
We just need to add a superfluous authorityIdentifier node to skip this check completely.
Vulnerability 3: HASH check for importing file.
The hash used to defend against in-flight corruption of the result files sometimes is just shown and the OSV does not ask the user for subpart of it. We did not spend much time trying to understand why this happens and if that can be controlled, but we have different example files that never ask for numbers, see input1 and input2)
Vulnerability 4: Election definition and XSS:
The content from the election definitions files is used without proper escaping, so it can be used to inject malicious javascript code. Even if these files are considered trusted (because they came from the government) there is no mechanism to ensure that they were not manipulated by a third party on their way to the polling stations (as they are not signed, see also 6). We are not sure whether these are important issues, but they show that input validation is not properly taken care of in the OSV software.
Vulnerability 5: Error handling and XSS
Not all errors are correctly handled to show correct message to user and most of the time a stacktrace of the exception is printed back. As the stacktrace is not properly escaped for html, it is trivial to craft a URL that will execute javascript in the context of the application. As an example a GET to
/P4_HSB/jsp/wahl/login_dialog.jsp
with the param cmd set to
<script>alert("Oh hai")</script>
will happily show the script’s alert box (see screenshot).
before | after |
Other security-relevant issues
There are many other issues that we found. A partial list can be found below.
- Incomplete source code, and unsigned binary code.
The sources available are incomplete with respect to the binaries, as at least some crucial components such as the installer definitions/code and xsd schemas are missing. Moreover, it is not trivial to check that the sources are the ones used to obtain the binaries, nor that the redistributed 3rd party components are unmodified with respect to their upstream sources.
As far as we can see there is no secure mechanism to protect against tampering with the binaries on their way to the polling station as the binaries are not explicitly signed/checksummed, even though Java natively supports such facilities.
2. Password hashing and login
In the password hashing process, the concatenation of the username and password strings are decoded as if they were base64 strings before hashing. Not only does this reduce the password strength by several orders of magnitude for the same string length (100K+ Unicode code points vs. 64 valid base64 digits), but also can cause a password to be silently truncated when hashed, producing what are essentially even more collisions. Consider, for instance, a user with the password (sans quotes) “votes=good”. Due to the base64 decoding step, this will be identical to having the password “votes”. Not good at all!
Moreover, this step is done in order to obtain an array of bytes (suitable as input to a hash function) from a unicode string, an operation otherwise known as string encoding, of which there exist many established standards. Reinventing the wheel was not only unnecessary but actively counterproductive.
As a minor issue, because of how the login process is implemented, there is a trivial sidechannel to see if a user exists or not. Even though the intended message to be displayed on login failure is “Foutieve gebruikersnaam of foutief wachtwoord.”—“wrong user or password”, if the user does not exist you get a different error message instead: “Fouten in de toepassing (systeem)” This could potentially lead to a user-lockout DoS attack, if ever the computer is connected to a network, since the number of permitted login attempts is capped to 6 per username.
3. Interface binding
The software binds on all interfaces (0.0.0.0) even if it should be accessed just by localhost.
4. Access to JSPs
The software is designed and written to be accessed through a single common gateway .jsp site and servlet, each of which performs specific integrity checks. However, all other .jsp sites are freely accessible with no input validation.
5. Outdated software stack
The software stack used is outdated: Java version “1.6.0_45” Java(TM) SE Runtime Environment (build 1.6.0_45-b06) Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode) jboss-4.2.3.GA. They are no longer supported.
6. Attack surface
The more lines of program code, the more vulnerabilities, and the more opportunities for attackers to manipulate the elections. OSV is huge. Over 75,000 lines of Java and 16,000 lines of JSP, not including comments or blank lines. This is for software that has, as its main task, adding a set of numbers together. Why does it need so much code? Here is the breakdown:
------------------------------------------
Language files blank comment code
------------------------------------------
Java 687 15545 47042 76995
HTML 102 647 2286 18303
JSP 78 614 897 16280
XSLT 90 126 5 14927
JavaScript 8 2994 2626 11296
XML 20 433 551 5160
XSD 5 70 65 3053
CSS 4 211 76 1279
SQL 3 126 0 771
DTD 2 350 1216 234
DOS Batch 5 6 4 18
------------------------------------------
SUM: 1004 21122 54768 148316
------------------------------------------
File 1: Data corresponding to attack 1, part 1.
<?xml version="1.0"?> <EML xmlns="urn:oasis:names:tc:evs:schema:eml" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:kr="http://www.kiesraad.nl/extensions" xmlns:rg="http://www.kiesraad.nl/reportgenerator" xmlns:xal="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" xmlns:xnl="urn:oasis:names:tc:ciq:xsdschema:xNL:2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Id="510b" SchemaVersion="5" xsi:schemaLocation="urn:oasis:names:tc:evs:schema:eml 510-count-v5-0.xsd http://www.kiesraad.nl/extensions kiesraad-eml-extensions.xsd"> <!--Created by: Ondersteunende Software Verkiezingen by IVU Traffic Technologies AG, program: P4_PSB, version: 2.21.4--> <TransactionId>1</TransactionId> <ManagingAuthority> <AuthorityIdentifier Id="0014">Groningen</AuthorityIdentifier> <!-- <AuthorityIdentifier Id="0003">Appingedam</AuthorityIdentifier> --> <AuthorityAddress/> <kr:CreatedByAuthority Id="xml:spacekr:RegisteredParty"/> </ManagingAuthority> <AuthorityIdentifier/> <!-- <kr:CreationDateTime>2018-03-12T11:22:10.988</kr:CreationDateTime> --> <!-- <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/> --> <Count> <EventIdentifier/> <Election> <ElectionIdentifier Id="NR20180321"> <ElectionName>de Wet op de inlichtingen- en veiligheidsdiensten 2017</ElectionName> <ElectionCategory>NR</ElectionCategory> <kr:ElectionSubcategory>NR</kr:ElectionSubcategory> <kr:ElectionDate>2018-03-21</kr:ElectionDate> </ElectionIdentifier> <Contests> <Contest> <ContestIdentifier Id="1"> <ContestName>Groningen</ContestName> </ContestIdentifier> <TotalVotes> <Selection> <AffiliationIdentifier Id="1"> <RegisteredName>Antwoord 1</RegisteredName> </AffiliationIdentifier> <ValidVotes>2147483643</ValidVotes> </Selection> <Selection> <Candidate> <CandidateIdentifier Id="1"/> </Candidate> <ValidVotes>2147483643</ValidVotes> </Selection> <Selection> <AffiliationIdentifier Id="2"> <RegisteredName>Antwoord 2</RegisteredName> </AffiliationIdentifier> <ValidVotes>1000</ValidVotes> </Selection> <Selection> <Candidate> <CandidateIdentifier Id="1"/> </Candidate> <ValidVotes>1000</ValidVotes> </Selection> <Selection> <AffiliationIdentifier Id="2"> <RegisteredName>Antwoord 2</RegisteredName> </AffiliationIdentifier> <ValidVotes>4</ValidVotes> </Selection> <Selection> <Candidate> <CandidateIdentifier Id="1"/> </Candidate> <ValidVotes>4</ValidVotes> </Selection> <Cast>2</Cast> <TotalCounted>-2147482649</TotalCounted> <RejectedVotes ReasonCode="ongeldig">0</RejectedVotes> <RejectedVotes ReasonCode="blanco">0</RejectedVotes> <UncountedVotes ReasonCode="geldige stempassen">0</UncountedVotes> <UncountedVotes ReasonCode="geldige volmachtbewijzen">0</UncountedVotes> <UncountedVotes ReasonCode="geldige kiezerspassen">0</UncountedVotes> <UncountedVotes ReasonCode="toegelaten kiezers">0</UncountedVotes> <UncountedVotes ReasonCode="meer getelde stembiljetten">0</UncountedVotes> <UncountedVotes ReasonCode="minder getelde stembiljetten">0</UncountedVotes> <UncountedVotes ReasonCode="meegenomen stembiljetten">0</UncountedVotes> <UncountedVotes ReasonCode="te weinig uitgereikte stembiljetten">0</UncountedVotes> <UncountedVotes ReasonCode="te veel uitgereikte stembiljetten">0</UncountedVotes> <UncountedVotes ReasonCode="geen verklaring">0</UncountedVotes> <UncountedVotes ReasonCode="andere verklaring">0</UncountedVotes> </TotalVotes> </Contest> </Contests> </Election> <Election/> <!-- <Election/> --> </Count> </EML>
File 2: Data corresponding to attack 1, part 2.
<?xml version="1.0"?> <EML xmlns="urn:oasis:names:tc:evs:schema:eml" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:kr="http://www.kiesraad.nl/extensions" xmlns:rg="http://www.kiesraad.nl/reportgenerator" xmlns:xal="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" xmlns:xnl="urn:oasis:names:tc:ciq:xsdschema:xNL:2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Id="510b" SchemaVersion="5" xsi:schemaLocation="urn:oasis:names:tc:evs:schema:eml 510-count-v5-0.xsd http://www.kiesraad.nl/extensions kiesraad-eml-extensions.xsd"> <!--Created by: Ondersteunende Software Verkiezingen by IVU Traffic Technologies AG, program: P4_PSB, version: 2.21.4--> <TransactionId>1</TransactionId> <ManagingAuthority> <AuthorityIdentifier Id="0014">Groningen</AuthorityIdentifier> <!-- <AuthorityIdentifier Id="0003">Appingedam</AuthorityIdentifier> --> <AuthorityAddress/> <kr:CreatedByAuthority Id="xml:spacekr:RegisteredParty"/> </ManagingAuthority> <AuthorityIdentifier/> <!-- <kr:CreationDateTime>2018-03-12T11:22:10.988</kr:CreationDateTime> --> <!-- <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/> --> <Count> <EventIdentifier/> <Election> <ElectionIdentifier Id="NR20180321"> <ElectionName>de Wet op de inlichtingen- en veiligheidsdiensten 2017</ElectionName> <ElectionCategory>NR</ElectionCategory> <kr:ElectionSubcategory>NR</kr:ElectionSubcategory> <kr:ElectionDate>2018-03-21</kr:ElectionDate> </ElectionIdentifier> <Contests> <Contest> <ContestIdentifier Id="1"> <ContestName>Groningen</ContestName> </ContestIdentifier> <TotalVotes> <Selection> <AffiliationIdentifier Id="1"> <RegisteredName>Antwoord 1</RegisteredName> </AffiliationIdentifier> <ValidVotes>2147483642</ValidVotes> </Selection> <Selection> <Candidate> <CandidateIdentifier Id="1"/> </Candidate> <ValidVotes>2147483642</ValidVotes> </Selection> <Selection> <AffiliationIdentifier Id="2"> <RegisteredName>Antwoord 2</RegisteredName> </AffiliationIdentifier> <ValidVotes>1000</ValidVotes> </Selection> <Selection> <Candidate> <CandidateIdentifier Id="1"/> </Candidate> <ValidVotes>1000</ValidVotes> </Selection> <Selection> <AffiliationIdentifier Id="2"> <RegisteredName>Antwoord 2</RegisteredName> </AffiliationIdentifier> <ValidVotes>4</ValidVotes> </Selection> <Selection> <Candidate> <CandidateIdentifier Id="1"/> </Candidate> <ValidVotes>4</ValidVotes> </Selection> <Cast>2</Cast> <TotalCounted>-2147482650</TotalCounted> <RejectedVotes ReasonCode="ongeldig">0</RejectedVotes> <RejectedVotes ReasonCode="blanco">0</RejectedVotes> <UncountedVotes ReasonCode="geldige stempassen">0</UncountedVotes> <UncountedVotes ReasonCode="geldige volmachtbewijzen">0</UncountedVotes> <UncountedVotes ReasonCode="geldige kiezerspassen">0</UncountedVotes> <UncountedVotes ReasonCode="toegelaten kiezers">0</UncountedVotes> <UncountedVotes ReasonCode="meer getelde stembiljetten">0</UncountedVotes> <UncountedVotes ReasonCode="minder getelde stembiljetten">0</UncountedVotes> <UncountedVotes ReasonCode="meegenomen stembiljetten">0</UncountedVotes> <UncountedVotes ReasonCode="te weinig uitgereikte stembiljetten">0</UncountedVotes> <UncountedVotes ReasonCode="te veel uitgereikte stembiljetten">0</UncountedVotes> <UncountedVotes ReasonCode="geen verklaring">0</UncountedVotes> <UncountedVotes ReasonCode="andere verklaring">0</UncountedVotes> </TotalVotes> </Contest> </Contests> </Election> <Election/> <!-- <Election/> --> </Count> </EML>
- [1] Funny anecdote: in the comments to some functions the developers put online references describing what/how/why the function does what it does. We find wikipedia links, java tutorial links, and our favourite: a class based off of copy-pasted code from a stackoverflow answer