20 comments

[ 2.9 ms ] story [ 54.9 ms ] thread
Huh. A neat hack, I always knew wildcards were risky, but didn't realize quite how dangerous they could be.
This has less to do with wildcards and more with passing arbitrary filenames to commands without making sure they are not interpreted as options. That these filenames come from the expansion of * is irrelevant - they could just as well be read from a file. This "vulnerability" doesn't even need to involve a shell at all - any exec*() with the same arguments will have the same result.

Most commands provide a "--" flag to indicate that all further arguments are not to be interpreted as anything other than filenames/whatever non-option arguments the command consumes.

GNU chmod/chown include this. I don't remember if stock tools on SunOS did, but I'd often use GNU coreutils compiled for Sun as they had more features.
> This has less to do with wildcards and more with passing arbitrary filenames

I think the issue being pointed out was that wildcards are expanded to filenames before parsing of arguments, which of course means you may not know what all filenames you're passing.

While this is something we all know, it can certainly slip through the cracks when you decide you just want to delete everything in the folder.

> This "vulnerability" ...

I don't know that it's a vulnerability proper so much as a warning to think before hitting enter.

Kinda true, but in a typical shell script (or even any other kind of program) there aren't many user controlled variables that would typically be incorporated directly into a command invocation. Pathname expansions are the most common and likely vector because it's common to both introspect directory trees and then pass those file paths as arguments to other utilities.

That's why I always put a line like

  set -f # disable pathname expansion
at the top of my shell scripts, in addition to several other settings (e.g. -e, -u, and -C). Because file globbing is a common operation in shell scripts I invariably have to re-enable it locally (e.g. set +f; set -- *; set -f; for F; do ...; done) somewhere. That extra work reinforces that pathnames are tainted, making it easier to remember to use the "--" option argument terminator where ever else I pass file names--particularly names not statically defined in the script--whether they come from globbing or some other route.

As an aside, I really dislike GNU-style argument vector permuting, where getopt/getopt_long rearranges "cmd foo bar -x" to be "cmd -x foo bar". On BSD doing something like 'ls /tmp "$X"', where X is user controlled, is relatively safe because "/tmp" terminates option parsing. Not so with GNU getopt. I get the convenience of permuting the vector, but I just don't think it's worth the cost in terms of security (it's a dangerously nuanced semantic) or in terms of the size of the GNU getopt/getopt_long implementation (it could be many times simpler and shorter, not to mention requiring only a constant amount of work and memory, but-for the permuting).

(comment deleted)
> This "vulnerability" doesn't even need to involve a shell at all - any exec*() with the same arguments will have the same result.

Wildcard expansions are done by shells, so no, exec() wouldn't trigger this "vulnerability".

Unless you're talking about a specific language's exec() that either does its own wildcard expansion, or actually runs its arguments in a shell.

system() invokes a shell
Good to know, now I can patch sacc in order to run lynx under a new shell.
This is really a vulnerability that applies in any case where you pass user supplied arguments to a command line program. Suppose you have a bunch of files on a server and you allow the user to send a list of files that they want to download as tar. You're careful to avoid directory traversal attacks so you reject filenames that contain slashes. Then, you do:

    exec(["tar", "cz", ...user_wanted_files])
The user sends you a GET /api/storage/download_tar?files=--checkpoint%3D1&files=--checkpoint-action%3Dexec%3Dsh+shell.sh which causes you to execute the contents of shell.sh. No shell, no glob necessary.

To do it correctly, you would have to do:

    exec(["tar", "cz", "--", ...user_wanted_files])
Or add ./ to the start of each filename.
(comment deleted)
The simplest mitigation probably bears mentioning: Prepend all wildcarded relative paths with `./`, because `rm ./*` does what you'd expect.
Why ./ (a good habit) and -- were not mentioned seems odd.
Many tools allow the use of the "--" argument to stop argument parsing. It's a clumsy work-around, but it's better than nothing.
Windows in this regard does it right. It provides standard calls for expanding globs, but the glob handling is handled purely in the application logic, not in the shell itself.

There are some advantages to doing this in the shell, but really they are dwarfed by the problems that are caused by users not being very careful about quoting and "--" arguments.

Sounds like unix filesystems should not allow files named starting with an “-“.
Once I knew the trick of putting ./ before the dash ('./-'), I have never had a problem with files starting with '-'.
There's no problem for folks who know the "secret." But what is the utility?

None, IMHO. I've never named a file starting with a dash in my career or as a child, except as an experiment to see if it could be done. Files promptly deleted afterward.