Summary
A typical usage of Opener is to open a url such as https://google.com
. Opener will launch a browser and open that page. It can also be used to execute commands such as npm run lint
. Although code execution is part of the intended purpose of Opener, we believe it is a security issue if, for example, a crafted url can run an arbitrary shell command rather than just launching a browser.
On Windows, in contrast to other platforms such as MacOS and Linux, Opener uses a shell (cmd.exe
) to open links. This creates a risk that an attacker can execute arbitrary shell commands.
Product
Opener
Tested Version
1.5.1
Details
Issue 1: Command injection on Windows
lib/opener.js
has logic for three platforms: Linux, MacOS, and Windows. On Linux and MacOS, Opener uses xdg-open
and open
, respectively, to open the url. Those programs are specifically designed for opening links. On Windows, however, Opener uses cmd.exe
to open the link. That means that special characters in the argument list could lead to the execution of arbitrary shell commands. The code already contains logic to handle the &
character, but it does not handle the ^
character, which has a similar effect.
Below is example of how a client of the Opener library might unwittingly expose themselves to this vulnerability. The value of ${user}
is attacker-controlled in this example, but you would not expect that to enable an attacker to run arbitrary shell commands.
const opener = require("opener");
opener(`https://github.com/${user}`);
If ${user}
is the string ^&calc
, then on Windows this code will open the (meaningless) url https://github.com/%5E
in a browser and launch calc.exe
.
Impact
On Windows, this issue may lead to remote code execution if a client of the library calls the vulnerable method with untrusted input. Other platforms (MacOS, Linux) are not vulnerable.
Remediation
We recommend adding logic to also escape ^
characters, as follows:
index 5fa88f3..08888c6 100644
--- a/lib/opener.js
+++ b/lib/opener.js
@@ -55,9 +55,9 @@ module.exports = function opener(args, options, callback) {
// Furthermore, if "cmd /c" double-quoted the first parameter, then "start" will interpret it as a window title,
// so we need to add a dummy empty-string window title: http://stackoverflow.com/a/154090/3191
//
- // Additionally, on Windows ampersand needs to be escaped when passed to "start"
+ // Additionally, on Windows ampersand and caret need to be escaped when passed to "start"
args = args.map(function (value) {
- return value.replace(/&/g, "^&");
+ return value.replace(/[&^]/g, "^$&");
});
args = ["/c", "start", "\"\""].concat(args);
}
Resources
We have written a CodeQL query, which automatically detects this vulnerability.
Coordinated Disclosure Timeline
2020-08-26: Report sent to d@domenic.me 2020-08-26: Rejected as not-a-security-issue by d@domenic.me 2020-08-27: Posted PR to fix the issue: https://github.com/domenic/opener/pull/34
Credit
This issue was discovered by GitHub Engineer @max-schaefer (Max Schaefer).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2020-145
in any communication regarding this issue.