A key implementation in Duplicati is the use of a web server for controlling a long-running process that contains the data needed for running and managing recurring backups. The web server allows a standard HTTP-based API, and in turn, this enables the implementation of a cross-platform UI running inside a browser.
The upside to this choice is that the HTTP protocol is widely supported, but the downside is that since it is accessible from a browser, it needs to be secured against any attacks that may originate from a browser, including XSS, CSRF and other known attack vectors.
Previous setup
Up until Duplicati 2.0.9.103 this has been implemented with a cookie and a custom XSRF token scheme to guard against most web based attacks. To guard against access to the server, it will default listen only on the loopback adapter and resist many forms of access. The initial security design did not consider the possibility of privilege escalation on the local machine, and for this reason allowed configuring the web server without a password. Users were advised to set up a password, but it was not required, leading some installations to be set up in an insecure manner.
Improved setup
To remedy this and other issues, version 2.0.9.103 changed the authentication scheme to be based on standard JWT and Bearer tokens. With the new scheme all requests require a valid JWT token to be present as a Authorization header, which prevents most kinds of XSS and XSRF attacks. By also requiring a password, this prevents local privilege escalation attacks as well.
The implementation of this authentication scheme has a number of additional security considerations built into it. Using standard JWT, all tokens are issued and signed, meaning that it is not possible to alter the contents or otherwise forge a token. To implement the authentication flow, there are three different types of tokens:
- Access token: The short-term token that is included as the Bearer token and grants access to each API call
- Refresh token: A long-term token that can be used to obtain new access tokens
- Signin token: A very short-lived token that grants access without a password
While running inside the browser, all requests made towards the API are attached with the API token in the Authorization header. To prevent XSS or XSRF attacks, the access token is never stored in the browser, only kept in memory. This makes it difficult to obtain the token and also guarantees that it will never be automatically attached to a request issued by the browser.
Obtaining an access token
There are 3 ways to obtain an access token:
- With a password
- With a refresh token
- With a signin token
When using the password log in, the API allows a “Remember me” flag. If this is set, the login will both return the access token as normal and return a refresh token as a secure, http-only, path-limited, host-limited token. This refresh token can be exchanged for a new access token and is stored in a way that makes it protected from browser scripting. Only by making requests to the authentication path, is the token included, making it hard to exploit from within the browser. To guard against browser storage hijack or leaks, the token is tracked in the web server. Attempting to use a token out-of-sequence will invalidate all versions of the refresh token.
The final piece of the authentication setup is the signin token, which is used to reduce the number of times a password is required. The signin token is generated either by the running web service, or by another process that has read-access to the database with the JWT signing key.
Duplicati’s TrayIcon
The most common way to run Duplicati is launching the Duplicati.GUI.TrayIcon.exe
executable (called duplicati
on non-Windows) which will add an icon into the tray-area of the UI on the operating system. In the background, the TrayIcon will also host the server components and the web server. The TrayIcon actually communicates with the server via HTTP, despite it being within the same process, and this requires a valid access token. But, since the two pieces are within the same process the TrayIcon can request a signin token from the server, and will use this to set up an access token. If the access token expires, the TrayIcon will automatically request a new signin token. This approach means that a user running with the TrayIcon will not need to know about the password.
The same approach is also used to launch the web-based UI. When the user clicks the TrayIcon and requests to open the UI, a new signin token is issued and this link is opened. The web server accepts the token, same as if the password was supplied, and grants access to the API. This automatic login feature can be disabled, but is enabled by default, as it does not weaken the security and greatly simplifies using Duplicati.
Stand-alone Server
Duplicati can also run the web server without any TrayIcon or GUI components, and this setup also requires an access token to access the API. Such a setup is used when running as a service, where the TrayIcon is in a local user account and the server is running with elevated privileges. In some cases it is also used when running Duplicati on a server, and then allowing access to the UI from another machine. For either setup, a password should already be used, but as mentioned above, this was not previously enforced.
Because it is insecure to run without a password, Duplicati will default to auto-generate a password and launch with that, which works great with the TrayIcon. But since the password is randomly generated, the user does not know it, and cannot log in without the TrayIcon. One way to fix this problem is to provide a known password and not rely on the auto-generated password. This can be done by starting the web server with the commandline argument –webservice-password=...
which will set the password. The password is persisted in the SQLite database, so a single invocation with the password is sufficient to store the password; it does not have to be supplied on additional server startups.
An alternate approach is to examine the log output from the web server. If it starts with a randomly generated password, it will create a new signin token and emit the url to the log output, so it is possible to obtain the link, open it and change the password from there. The lifetime of the signin token is intentionally short, so it may be required to restart the service to force it to generate a new signin token if this approach is used.
The duplicati-server-util tool
With Duplicati 2.0.9.105, a new tool is included that can manipulate the running server. This tool can use a technique similar to the TrayIcon to obtain a signin token without knowing the server password. If the tool is running with permissions that allows it to read the database containing the JWT configuration, it can issue a signin token and perform operations on the API.
One of the operations the tool can perform is to change the password, so a new password can be set, even if the server is running with an auto-generated password. This functionality only works if Duplicati.CommandLine.ServerUtil.exe
/duplicati-server-util
is running on the same machine as the server, because the database is encrypted with a machine-based key.
Securing the database and password
From version 2.0.9.103 the password is saved in the database using a technique called password based key derivation (PBKDF) such that the password used to log in cannot be obtained by reading the database. Previous versions would use a simpler form of hashing that would allow an attacker to read a token that could be used for creating logins.
Although the password is now safe from leaking, the JWT format is dependent on a signing key, which needs to be stored in the database. With the signing key, it is possible to issue a new access- or signin token that will be accepted by the API. This is exactly the method that the server-cli tool and TrayIcon setups are using. (It is not possible to issue a refresh token directly, as that token is backed by a database entry, which the signin and access tokens are not.)
To prevent leaking this key and any credentials stored in the database, version 2.0.9.105 implements a field-level encryption method for the database. While it is possible to provide a key via the environment variable SETTINGS_ENCRYPTION_KEY
it is assumed that most setups will not perform this manual step.
Instead the default encryption key is derived from a serial number on the machine, meaning that the encryption key is only present on the machine itself. Leaking the database will not provide access to the signing key or other credentials, unless the attacker can somehow obtain the hardware details of the originating machine as well.
Update: This was attempted with 2.0.9.105 but turned out to deliver too weak a key. Until a suitable key source is located, the user is responsible for providing an encryption key via SETTINGS_ENCRYPTION_KEY
, and the server will run encrypted if nothing is provided. A warning message is logged if the server runs without encryption, explaining how to fix it.
Additional security concerns
By default, Duplicati will use the unencrypted HTTP protocol for all communications. It is possible to set up TLS (aka SSL) protected HTTPS communication on the server, but this is so far a manual process.
Without encryption, any communication with the server can be observed by an attacker that is able to monitor the traffic, particularly monitoring for localhost traffic would allow extraction of the password, refresh and access tokens, and any credentials or passphrases that are communicated.
It would be ideal if Duplicati could just set up a certificate automatically, but unfortunately this is not a trivial task and has severe security implications on its own. The LetsEncrypt website has a good overview of why it is difficult to apply a certificate to localhost communication.
For all users, it is recommended that a TLS certificate is used, despite it being non-trivial to do.
The open-source cross-platform mkcert tool is one way to set up a local trusted certificate authority (CA), and have that authority issue a certificate for Duplicati. Beware that leaking the generated CA certificate will allow the attacker to impersonate ANY website for your machine.