0
$ touch '"; echo world "'

$ find .  -exec sh -c 'ls -l "$@"' sh {} \;
total 0
-rw-rw-r-- 1 t t 0 Jun  8 23:13 '"; echo world "'
-rw-rw-r-- 1 t t 0 Jun  8 23:13 './"; echo world "'

I was wondering why the beginning and ending double quotes in the filename not paired with the beginning and ending double quotes in "$@", so that echo world in the filename can be run?

Is it because a quote must be recognized by sh during lexical analysis of the shell command, in order to quote? Here the quotes in the filename appear in the shell command only after parameter expansion, which already passed lexical analysis and is too late for the quotes in the filename to be recognized? Similarly to https://unix.stackexchange.com/a/448643/674?

Differently, adding eval does not make the injection work either, because although eval will make " and ; in the filename recognizable by the shell, " in the filename will also be removed by the shell:

$ find .  -exec sh -c 'eval ls -l "$@"' sh {} \;
total 0
-rw-rw-r-- 1 t t 0 Jun  8 23:13 '"; echo world "'
ls: cannot access './; echo world ': No such file or directory

Debug to see what eval actually executes:

$ find .  -exec sh -c 'echo ls -l "$@"' sh {} \;
ls -l .
ls -l ./"; echo world "

$ ls -l ./"; echo world "
ls: cannot access './; echo world ': No such file or directory

Thanks.

On a side note, why are there two items for the same file in the output of find command? since . is a hard link to the current directory, why does find not output about the directly itself but the file under it?

Tim
  • 101,790

1 Answers1

2

why the beginning and ending double quotes in the filename not paired with the beginning and ending double quotes in "$@"

Because variable expansion isn't a straight text replacement. The quotes inside a variable are not processed as quotes, they're just ordinary characters at that point.

This is similar to any variable containing quotes.

$ foo='"foo  bar"'

The quotes in foo don't end the quotes in the command line, there's no word splitting:

$ printf "%s\n" "$foo"
"foo  bar"

And here, there is word splitting (because the expansion is not quoted), and quotes in foo do nothing to prevent it:

$ printf "%s\n" $foo
"foo
bar"

The latter one is similar to why the naive way to store a command in a variable doesn't work.


As for the eval, I don't understand why you'd add it, but still since it involves another round of expansions, you can just put a command substitution in there:

$ touch '$(uname -m)'
$ find .  -exec sh -c 'eval ls "$@"' sh {} \;
$(uname -m)
ls: cannot access './x86_64': No such file or directory

or a semicolon:

$ touch ';uname -m'
$ find .  -exec sh -c 'eval ls "$@"' sh {} \;
;uname -m
;uname -m
x86_64

(I'm not sure why that runs the ls twice, though.)


With a file called "; echo world ", the quotes expand as part of the "$@", where eval sees them, and since it runs the whole shell processing again, you have a quoted semicolon.

There's no single filename what would work to inject a command in all the vulnerable cases, it depends on the way the command is built.

ilkkachu
  • 138,973