On 10 June 2025 the Belgian GDPR supervisory authority (GBA) launched it’s new application where DPOs can be registered and that breaches can be reported. Because I’m interested in GDPR breach notification requirements I decided to take a look at the application on 12 June. Within minutes of looking at the application I noticed a vulnerability. My previous blog post is about some of the legal issues I encountered in the process of reporting the vulnerability. Those issues have been mostly solved and that will be described in more details in my next blog post. This article is specifically about the vulnerability and the fix.
What is the issue?
The application allows controllers in Belgium to register their organisation and manage their DPO registration
and their data breach notification cases. I noticed that the form that allows controllers to register made a
HTTPS-request containing the organisation’s public KBO (the public organisation identifier from the chamber of commerce)
and returning a boolean value indicating their registration status.
This is not a complicated vulnerability, but I see at least two reasons why this might be a problem. the first reason is that I ocasionally report vulnerabilities at organisations that can also be classified as breaches that fall under the GDPR reporting requirement. It is not uncommon that the organisation lets me know that they will report the incident to the data protection authority or that they have already done so, but in a manner that give me a reason to think that they are lying. With this vulnerability, before reporting such an issue with a Belgian organisation I can check if they have been registered. If they have not I know they didn’t report a breach after 10 june 2025. If I check afterwards and they still have not registered I have verified they didn’t report the breach. This while generally the data protection authorities refuse to disclose such information.
As an extention to this issue, with publicly know KBO numbers, it’s possible to periodically scrape the application for a large number of organisations and monitor for changes. A change in registration status can indicate a probable data breach and can trigger an investigation from journalists or influence stock prices.
The solution
Within roughly an hour after my report the GBA took the entire application offline. It took roughly a month to implement changes and re-launch the application. While I have not tested the effectiveness of the changes to mitigate the vulnerability I do not understand why they would be effective. This is my attempt to describe the changes.
Vigenère cipher
Anchr, the company that has build the application, has added encryption to the web form. From what I can unerstand it apears to be a modified version of the Vigenère cipher. That is an encryption cipher of 16’th century design that has been considered secure for a bit over three centuries according to wikipedia. Why the GBA or Anchr chose to use an encryption cipher that has been considered broken since 1863 is not known to me.
The source code implementing the encryption ha been published by the GBA.
_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,!?@#$%^&*()-_=+[]{}<>:;\"'/\\|`~\n\t";
encrypt(plaintext, key, date) {
const rotatedKey = rotateKey(key, date);
let ciphertext = "";
let keyIndex = 0;
for (let i = 0; i < plaintext.length; i++) {
const p = plaintext[i];
const pIndex = _CHARSET.indexOf(p);
if (pIndex === -1) {
ciphertext += p; // unsupported character — leave as-is
continue;
}
const k = rotatedKey[keyIndex % rotatedKey.length];
const kIndex = _CHARSET.indexOf(k);
if (kIndex === -1) {
ciphertext += p; // skip unsupported key char
continue;
}
const cIndex = (pIndex + kIndex) % _CHARSET.length;
ciphertext += _CHARSET[cIndex];
keyIndex++;
}
return ciphertext;
}
Another relevant part of the encryption cipher is the key rotation function:
rotateKey(key, date) {
const shift =
date.getUTCHours() +
date.getUTCMinutes() +
date.getUTCFullYear() +
date.getUTCSeconds();
const realShift = shift % key.length;
return key.slice(realShift) + key.slice(0, realShift);
}
All code to reproduce the encryption algorythm is contained in that same file, but these two snippets contain the practically all you need to understand the workings.
To enrypt data you need two more pieces of information: the encryption key and a timestamp. The encryption key is published here:
{
"generalProperties": {
"addressAutoCompleteConfigurationDTO": {
"countryConfigurations": [
{
"countryCode": "BE",
"countryName": "België",
"sdk": "jVWdXELPmEsR2HiP2xtJL8culAkL8yv9"
}
]
}
}
}
With the encryption key jVWdXELPmEsR2HiP2xtJL8culAkL8yv9
and a clock any client can encrypt values and POST them to the new endpoint URL:
{
data: "TG`↵}kpGTjc0'!SQGV`<]'<|~",
time: "2025-07-31T10:54:37.124Z"
}
The responses are encrypted too, but can be decrypted using the same key and publicly available code. With every client still capable of calling an endpoint with the same functionality I don’t know what the reason was to design this feature.
Conclusion
While I have permission to publish my analysis, I had hoped that the GDA would have chosen to communicate with me prior to implementing this feature. I would have been able to provide feedback and maybe even test the fix. Due to the legal issues surrounding my initial report I refused to validate my stong suspicion that the system is still vulnerable. Maybe this publication helps to communicatate my concerns better. At the very least controllers in Belgium should be concerned that the moment of their first data breach report since 10 june 2025 is not kept confidential by the GBA. Register your organisation as soon as possible instead of at the moment you need to report a breach.