
Introduction
UrBackup is an open-source software that offers multiple features for backup and ransomware protection:
UrBackup is an easy to setup Open Source client/server backup system, that through a combination of image and file backups accomplishes both data safety and a fast restoration time.
According to GitHub and Shodan, there are a few hundred public instances of UrBackup running over the Internet, not counting the ones running over LAN. Its Docker image has over 10 million downloads. It is written mainly in C++ and works on multiple operating systems, including Windows and Linux.
A vulnerability was identified that leads to Denial of Service. The UrBackup Internet Service component allows a connecting client to specify how many times the server should repeat its key derivation operation, which runs on the PBKDF2 algorithm. This algorithm is made to be slow and hard to compute, and raises CPU consumption when used excessively, which ultimately leads to DoS due to resource exhaustion.
Initial Surface Mapping
The software was downloaded and installed from its official website, which offers precompiled binaries and source code. The installation process is straightforward, by simply running the installer binary and clicking “Next”.
Once executed, UrBackup creates a process and binds to ports 55413, 55414, and 55415 on all interfaces by default.
- 55413 – FastCGI
- 55414 – HTTP Web Interface
- 55415 – Internet Service

The listener is defined in urbackupserver/dllmain.cpp, where one can also note that the listener and Internet mode are enabled by design unless an administrator disables them. If nothing custom is configured, the default behavior is to start the listener on port 55415.

In Server.cpp a CServiceAcceptor is built, whose constructor binds the socket and starts listening, that object is handed to createThread, which runs it on a thread of its own. From here, the main thread carries on booting the rest of the server.

Note that this createThread function is not the WinAPI, but a custom implementation of UrBackup that wraps around the WinAPI.

The CServiceAcceptor class has a method named operator(), which loops forever waiting for incoming connections and hands each new one to a worker thread. It is essentially the entry point for the thread.
The while block makes it loop forever, and select() blocks until a connection is received.

When a TCP connection is opened, ACCEPT_CLOEXEC() returns a brand new socket for it. The peer address (from whoever made the connection) is converted to a printable string and passed to AddToWorker.

It’s worth noting that ACCEPT_CLOEXEC is simply a macro that expands to accept() on Windows, a Winsock function that takes the next pending connection and returns a new socket for it. This is where the incoming connection is established.

The server keeps a pool of worker threads instead of giving each connection its own thread, each pool holding up to 20 (defined on ServiceWorker.h:12). If every worker is full, it creates another. The last AddClient call will essentially push the socket onto that worker’s queue.

After some back and forth, the InternetServiceConnector::Init() method is called, which runs the per-connection setup routine. Once a connection is made to the port, it stores the connection, sets the protocol state, and transmits the server’s opening message.
A random challenge is generated for the connection, packed into a buffer together with the parameters the client is expected to use, and written to the socket. One of these parameters, which is what the vulnerability is all about, is the pbkdf2_iterations, the server’s iteration count.

All of this runs off the TCP connection before the client has sent anything, the attacker’s first interaction with the service can already be the authentication packet.
Secure Key Derivation vs CPU Consumption
The Password-Based Key Derivation Function 2 (PBKDF2) is a key derivation function defined in RFC 8018, widely adopted and known for its enhancements for the security of hashed passwords. Two major things are to be considered for PBKDF2:
- The algorithm implements salting, which works by adding a random string (the salt) to the password before hashing it. This prevents the traditional rainbow table attacks, as hashing the same password with different salts will produce different hashes on each use.
- The hash function is applied multiple times to the password and salt combination. This process is named stretching and makes the computation of the hash slow while also increasing the cost of processing it, with the intention of reducing the feasibility of brute-force attacks.
These features add many advantages to using PBKDF2, including:
- Enhanced security against brute-force attacks, as the hash computation for the algorithm is slow and resource-intensive, it is usually impractical to perform such attacks.
- Mitigation of rainbow table attacks, as already mentioned, due to the use of unique and random salts.
- Flexibility and customization for the number of iterations based on the available computational power, which helps balance security and performance.
While these are very relevant advantages that make PBKDF2 a solid choice, this is best applied in certain situations. One to mention would be the storage of passwords and other sensitive information, as never storing such data in cleartext is a basic security standard, and PBKDF2’s deliberate slowness makes brute-forcing stolen hashes impractical.
For other situations, however, these security mechanisms embedded within the algorithm might not be necessary, or even become a disadvantage. In UrBackup, the PBKDF2 algorithm is implemented in a way where iterations are controlled by the client. It is accessible to anyone who connects to the port, even if unauthenticated.
There is a function named generateBinaryPasswordHash defined in the CryptoFactory class within UrBackup, which is a wrapper around the Crypto++ library.

What makes PBKDF2 resource-intensive could be summarized in three main aspects:
- The iterations value decides how many times it runs, and every round costs the same, hence, the total cost will be the iteration count multiplied by the cost of one HMAC.
- Each round that runs depends on the one before it, every round hashes the output of the previous round. This means that round 5 cannot begin executing until round 4 has finished.
- Nothing can be skipped as each round contributes to the answer, so the process cannot be jumped ahead or reduced.

Connecting all the dots, the bottom line is that the same characteristics that increase the algorithm’s security can become an issue and be used to degrade performance and affect availability.
Root Cause
At this point, some things are cleared out based on the code shown:
- Whenever a client connects to the service port, the UrBackup server speaks first
- Interactions are received from the client even before authentication has been completed
- The server allows clients to define the number of iterations for the PBKDF2 cryptographic operation
Tracing the overall flow to reach the key derivation function, it starts at InternetServiceConnector.cpp, where a cursor rd is created based on the buf variable that contains the raw user-supplied request.

This leads to the hashing function generateBinaryPasswordHash that was already shown.

Note that the function call is employing std::max, which will get the largest value between iterations and client_iterations.
Tracing back both variables to their definition, one can note that iterations come from a type casting operation.

The pbkdf2_iterations value is hardcoded at the beginning of the file, setting the minimum value of 20,000.

Meanwhile, the client_iterations value is initially derived from the iterations variable, however, it gets overwritten by the user-supplied value from the cursor, as the variable gets passed by reference.

This means that the minimum value will always be 20,000, and the maximum value is controlled by the user. If a user supplies a value larger than 20,000, the user-supplied value will be prioritized due to the std::max call.
Since the PBKDF2 algorithm was made to be slow and hard to compute, and the number of iterations is controlled by the end user, anyone with access to the port can provide a very large value when connecting to the service and cause the server to process a large number of hashes, causing a spike in CPU usage, ultimately leading to denial of service due to resource exhaustion. By sending a single packet, one can consume minutes or even hours of processor time on the server.
Authentication and Reachability
One very important detail is how one could reach the cryptographic operation before authenticating, as an opportunity for unauthenticated denial of service is the worst-case scenario. The authentication flow starts on InternetServiceConnector::ReceivePackets, where the client name and password values are parsed.

This leads to a lookup, where the server fetches the shared secret based on the client name by leveraging the getAuthkeyFromDB function.

Under normal circumstances, the hashing mechanism is only reached if the checks are passed.

By inspecting the getAuthkeyFromDB function, it was identified that a check is performed based on a variable named restore_prefix, which is defined as “##restore##”.


If the client name starts with the restore prefix, the function returns the global restore key. The function will only try to fetch the client name further if this condition fails.
This is what makes the attack possible. The check in front of the key derivation only requires a non-empty key, never a valid one. This key does not grant authentication, the request is still rejected, but that only happens after the hashing has already been completed.

Worth noting that this is very likely an intentional feature, to support restore clients with no registered identity. Meanwhile, it allows an initial interaction without providing valid credentials by supplying the restore prefix as the client name, which makes the denial of service attack viable.
Exploitation Proof of Concept
An exploit was developed in Python as a proof of concept, to demonstrate a practical scenario. It constructs a single authentication packet, leveraging the aforementioned restore prefix, and measures the time taken for the server to return its rejection. This is wrapped around multithreading as well to allow for a more aggressive attack.
The lab environment was a Windows virtual machine with 4 CPU cores and 8 GB of RAM, running the software in a clean install with default settings.
Below is shown the exploit in execution.

As a result, the UrBackup server process would cause a spike in CPU usage, which could last for minutes or even hours if the attack is performed sequentially while leveraging multiple threads.

Mitigation
After a couple of weeks, version 2.5.38 was released with a fix.

The updated source code was downloaded from the official website and analyzed. UrBackup version 2.5.38 now has a defined value for the maximum client iterations, which comprises the base iterations multiplied by ten.

A check is in place that sets errmsg to a custom message if the supplied number of iterations is larger than the value defined.

While one can note that the request is not dropped right away, there is another check to validate errmsg further down in the code. The generateBinaryPasswordHash hashing function is only reachable if errmsg is empty.

This mitigates the original issue, the maximum iterations are now capped, and the hashing operation is only reachable if the check is passed. There could still be residual risk from this, in a scenario where an attacker provides 199,000 iterations, for instance, while using multithreading, it can still cause resource consumption and performance degradation. This was communicated to the vendor, who confirmed there are other mechanisms to further mitigate this.
Timeline
- August 15, 2026: Vulnerability report sent to UrBackup team via e-mail
- August 16, 2026: Vendor acknowledged the issue
- August 30, 2026: Version 2.5.38 released with a fix
- September 2, 2026: Pending CVE assignment from MITRE
References