#YWH-PGM2323-49 : SDM - Insecure USB file handling during 'importOperation'
During the public Bug Bounty program an independent researcher uncovered the following issue in the e-voting solution's source code. We want to thank Ruben Santamarta (reversemode) for his observation:
Summary: The communication between the online and the offline instances of the SDM is done by an import/export functionality using a USB stick. The SDM Backend implements an endpoint for the frontend to perform an 'Import' operation in order to copy files from the USB to the SDM directory. The vulnerability lies in the way this operation has been implemented, as there is no validation of the files that are copied, allowing to overwrite multiple files in the SDM directory. Among other things, this allows to bypass the subsequent signature verification of the imported database, overwrite key materials and run arbitrary commands through the possibility to extend the SDM plugins defined by the customer.
Background: The import operation is implemented in the OperationsController.java
@PostMapping(value = "/import")
@ApiOperation(value = "Import operation service")
@ApiResponses(value = { @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 403, message = "Forbidden"),
@ApiResponse(code = 500, message = "Internal Server Error") })
public ResponseEntity<OperationResult> importOperation(
@RequestBody
final OperationsData request) {
if (StringUtils.isEmpty(request.getPath())) {
return handleIncompleteRequest();
}
try {
exportImportService.importData(request.getPath());
exportImportService.verifySignaturesOnImport();
exportImportService.importDatabase();
} catch (IOException e) {
OperationsOutputCode code = OperationsOutputCode.ERROR_IO_OPERATIONS;
return handleException(e, code.value());
} catch (InvalidParameterException e) {
OperationsOutputCode code = OperationsOutputCode.MISSING_PARAMETER;
return handleInvalidParamException(e, code.value());
} catch (CMSException | SignatureException e) {
OperationsOutputCode code = OperationsOutputCode.SIGNATURE_VERIFICATION_FAILED;
return handleException(e, code.value());
} catch (GeneralCryptoLibException e) {
OperationsOutputCode code = OperationsOutputCode.CHAIN_VALIDATION_FAILED;
return handleException(e, code.value());
} catch (CertificateException e) {
OperationsOutputCode code = OperationsOutputCode.ERROR_CERTIFICATE_PARSING;
return handleException(e, code.value());
} catch (ConsistencyCheckException e) {
OperationsOutputCode code = OperationsOutputCode.CONSISTENCY_ERROR;
return handleException(e, code.value());
} catch (Exception e) {
OperationsOutputCode code = OperationsOutputCode.GENERAL_ERROR;
return handleException(e, code.value());
}
return new ResponseEntity<>(HttpStatus.OK);
}
The provided filter in the copyFolder method does not implement a whitelist of the files that should be copied. As a result, the entire USB drive will be replicated to the sdm directory, creating directories if required and overwriting files when they exist.
/**
* Import all files from selected exported election event to user/sdm
*
* @param usbElectionPath path to selected exported election event
* @throws IOException
*/
public void importData(String usbElectionPath) throws IOException {
Filter<Path> filter = file -> true;
Path usbFolder = absolutePathResolver.resolve(usbElectionPath);
Path sdmFolder = pathResolver.resolve(Constants.SDM_DIR_NAME);
copyFolder(usbFolder, sdmFolder, filter, false);
}
The problem lies in the fact that the importData operation take place before the signature validation of the dump of the sdm internal database (db_dump.json) and its subsequent import takes place.
exportImportService.importData(request.getPath());
exportImportService.verifySignaturesOnImport();
exportImportService.importDatabase();
An attacker is able to replace the Trusted CA
certificate in the previous USB copy operation as it is located at SDM_DIR_NAME + /config
. As a result the signature verification can be effectively bypassed.
Additionally, the SDM includes the possibility to be extended by plugins defined in SDM_DIR_NAME + /plugins.xml
. Those plugins are bound to certain phases of the configuration process. An attacker controlling the USB drive is able to create/modify the plugins.xml
file during the import operation and extend the SDM by injecting arbitrary commands, which will be executed in OperationsController.java#L284.
protected List<String> getCommands(PhaseName phaseName) throws IOException, JAXBException, SAXException, XMLStreamException {
Path pluginXmlPath = pathResolver.resolve(Constants.SDM_DIR_NAME).resolve(PLUGIN_FILE_NAME);
if (!pluginXmlPath.toFile().exists()) {
pluginXmlPath = pathResolver.resolve(Constants.SDM_DIR_NAME).resolve(Constants.SDM_CONFIG_DIR_NAME).resolve(PLUGIN_FILE_NAME);
if (!pluginXmlPath.toFile().exists()) {
LOGGER.error("The plugin.xml file is not found");
return new ArrayList<>();
}
}
Plugins plugins = XmlObjectsLoader.unmarshal(pluginXmlPath);
PluginSequenceResolver pluginSequence = new PluginSequenceResolver(plugins);
return pluginSequence.getActionsForPhase(phaseName);
}
Risk
The vulnerability facilitates the compromise of the offline instance of the Secure Data Manager. An attacker controlling part of the Secure Data Manager infrastructure (the online Secure Data Manager or the USB key to transfer data) could leverage the lack of import restrictions to execute arbitrary commands on the offline instance of the Secure Data Manager.
Resolution
We are going to prevent the attack in the following way:
- Implementation of a whitelist approach when importing files from the USB key
- Restricting the possibility to update/overwrite existing files