URI Argument Injection Vulnerability in Bitcoin Core 0.18 and Earlier
URI Argument Injection Vulnerability in Bitcoin Core 0.18 and Earlier
This article describes an URI Argument Injection vulnerability discovered in Bitcoin Core 0.18 and earlier. The vulnerability was fixed in Bitcoin Core 0.19.0. Bitcoin Core 0.19.0 and later are not vulnerable. This vulnerability only affects Windows and Linux.
In 2019, I was browsing the pwnie award nominations and found an entry titled “RCE in Qt5-Based GUI Apps.”
As Bitcoin Core’s GUI (
bitcoin-qt) uses Qt5, this immediately piqued my interest.
The linked article describes a vulnerability in multiple Qt5 based software could have malicious code executed on a user’s computer after clicking a malicious URI.
I proceeded to investigate whether this same vulnerability affects and found that it was theoretically possible.
However it seems that this vulnerability is unlikely to actually be exploited.
Understanding this vulnerability first requires that we understand how URIs are handled. Windows and Linux handles this in a similar manner. MacOS handles URIs differently so it is not affected by this vulnerability.
In Windows, the operating system executes a pre-registered command with the URI as an argument.
This command is stored in the Windows registry and contains format specifiers for the location within in the command to insert the URI.
Windows replaces the format specifier
%1 with the URI and then executes the command string.
For Bitcoin Core, the registry key is
and the command is
"C:\Program Files\Bitcoin\bitcoin-qt.exe" "%1"
In Linux, the desktop environment searches for a
.desktop file which contains a line like
It then executes the command specified by the
.desktop file for Linux has:
This command is, like Windows, a string with format specifiers.
The desktop environment will replace the format specifier
%u with the URI and execute the command string.
URI Argument Injection
Since URI handling in both Windows and Linux basically amounts to creating a string containing the URI and then executing it, a maliciously crafted URI could make the string such that it looks like a command with additional command line arguments.
For example, consider the URI
With the Window’s URI command, the simple string replacement it does would create the command
"C:\Program Files\Bitcoin\bitcoin-qt.exe" "bitcoin:BC1QXUFYM7NNUSQZW9R2U5G98USKDAT7XQN4VYZ63S" "-argument"
Notice how this looks like the URI is followed by a command line argument
In fact, it not only looks like that, it is interpreted by the OS that way and
-argument is provided to the software as a separate command line argument instead of as part of the URI as it should be.
In this way, a malicious URI could cause the software to start with a dangerous command line argument.
Injecting Arguments into Bitcoin Core
Since URI argument injection is a known issue, software developers have ways to avoid them. Bitcoin Core’s argument parser ignores any arguments that follow a URI. So it is not possible to use inject Bitcoin Core specific arguments through a URI. However, as noted earlier, the problem is with Qt.
The Qt framework specifies a set of Qt specific command line arguments. When Qt is initialized, the command line arguments are be passed into Qt in order for those arguments to be processed by Qt. This is done before Bitcoin Core processes the command line arguments. Qt will look through the argument list, find the arguments it can handle, and remove them from the list as it handle them. Then the arguments get passed to Bitcoin Core’s argument parser. This processing by Qt does not recognize URIs and will process any arguments regardless of their position in the arguments list. Thus it is possible to use a URI to inject Qt specific arguments.
Dangerous Command Line Arguments
Most of the Qt arguments are benign and Qt’s documentation specifies the available arguments.
These arguments primarily affect the way that the application looks.
However there is one specific argument that is problematic:
-platformpluginpath loads some plugin from a path and executes it.
With some other tricks (described in the original “RCE in Qt5” article), it is possible to get a malicious plugin onto the victim’s computer, and once they click a malicious URI that injects
-platformpluginpath, have that plugin be executed.
As a malicious plugin can be executed, this is classified as a remote code execution vulnerability.
URI Argument Injection Mitigations
Of course, operating systems, browser developers, and software developers do not necessarily do the naive implementation. As previously mentioned, Bitcoin Core ignores command line arguments found after a URI. So a URI that injects arguments cannot cause Bitcoin Core to start with any additional Bitcoin Core specific command line arguments. Additionally, Bitcoin Core specifically registers a URI handler that surrounds the URI with double quotes so that an argument cannot be injected with just spaces. The attacker has to make a URI that itself contains double quotes to do the injection.
But browser developers are aware of the double quote trick, so they will sanitize the URI before passing it to the operating system to be handled.
Browsers will escape the double quotes, and often do other sanitizing such as replacement of spaces, to ensure that the OS treats the entire URI as a single URI argument, not as multiple command line arguments.
For example, for the URI
bitcoin:BC1QXUFYM7NNUSQZW9R2U5G98USKDAT7XQN4VYZ63S" "-argument, a possible string actually used would be
bitcoin:BC1QXUFYM7NNUSQZW9R2U5G98USKDAT7XQN4VYZ63S\" \"-argument and the final command
`"C:\Program Files\Bitcoin\bitcoin-qt.exe" "bitcoin:BC1QXUFYM7NNUSQZW9R2U5G98USKDAT7XQN4VYZ63S\" \"-argument"
Since the double quotes are escaped by being preceded with a backslash (
\), they are not treated as delimiters between arguments but instead as part of a single argument string.
Another method of sanitizing includes using URL encoding of special characters instead of escaping.
It is important to note that browsers must do this sanitizing in Windows. Windows itself does not sanitize the URI, so a browser which does not sanitize the URI would allow for arguments to be injected. However most of the browsers in use today will sanitize the URI in some way which prevents this vulnerability from being exploited.
When it comes to Linux, the
%u specifier specifically means that the entire URI is to be treated as a single argument.
Properly implemented desktop environments will do this correctly and not allow part of the URI to be treated as separate arguments.
I do not know of any desktop environment that does not do this, so on Linux, the desktop environments prevent this vulnerability from being exploited.
Thus, in practice, argument injection via a URI is almost impossible to execute with Bitcoin Core and modern browsers and desktop environments. It is because of these mitigations that I believe that this vulnerability cannot actually be exploited.
Bitcoin Core’s Fix
Even though I believe that this vulnerability cannot actually be exploited, it is still prudent to fix the issue. The fix for this vulnerability is to disallow Qt from processing command line arguments. Instead of initializing Qt with the real list of command line arguments, we initialize it with an empty list of arguments. This was implemented in PR #16578 which was merged in August 2019. While this does fix the problem, it does also have the downside that users who do wish to use Qt’s other command line arguments to customize how it looks will be unable to do so via command line arguments. Instead this will need to be done through environment variables. A future followup would be to allow only some of Qt’s command line arguments to be used and/or disallow the dangerous ones.
This vulnerability has been assigned CVE-2021-3401.
- 2019-08-02: Vulnerability discovered
- 2019-08-03: Vulnerability reported to email@example.com
- 2019-08-03 - 2019-08-09: Further vulnerability investigation, disclosure to additional developers, and development of fix
- 2019-08-09: PR #16578 opened
- 2019-08-15: PR #16578 merged
- 2019-11-24: 0.19.0.1 released containing fix
- 2021-02-01: 0.18.0 End of Life
- 2021-02-01: Disclosure published