help-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Help-bash] avoiding shell variable expansion


From: Stephane Chazelas
Subject: Re: [Help-bash] avoiding shell variable expansion
Date: Fri, 4 Oct 2019 07:47:49 +0100
User-agent: NeoMutt/20171215

2019-10-03 19:47:42 +0000, Greg Silverman:
> In Python one  can spawn a child process and avoid bash expanding command 
> line arguments, e.g.
> 
> //file: ls.py
> import subprocess
> proc = subprocess.Popen(['/bin/ls','*'],shell=False)
> 
> then
> ./ls.py
> /bin/ls: cannot access '*': No such file or directory
> 
> As the shell argument is set to False, the ls command is not passed to bash 
> before being executed and the star is not expanded to ${PWD}.
> 
> Is there a way to launch a command from a bash script which, also, avoids 
> shell expansion? This question is for security, to avoid code injection.
[...]

First, note that python runs a POSIX "sh" with shell=True, which
on most systems is not bash (exceptions being current versions
of macOS and some GNU/Linux systems)

If I understand correctly, you would like to be able to run

['/bin/ls', var]

And have a shell do filename generation (aka pathname expansion
aka globbing) on the content of "var", but not other forms of
expansion like parameter expansion or command substitution, or
in general interpret the content of var as shell code, is that
right?

Note that if the current directory contains entries that start
with "-", those will be taken as option by ls. If some of those
files are directories or symlinks links to directories, ls will
list their contents. I suppose that's what you want as otherwise
there would be no point in calling ls at all.

Here, you could do:

import subprocess
var = '*'
proc = subprocess.Popen(['IFS=; exec /bin/ls -- $1', 'sh', var], shell=True)

In effect, that will call

sh -c 'IFS=; exec /bin/ls -- $1' sh <contents of var as one argument>

You could actually write it instead:

proc = subprocess.Popen(['/bin/sh', '-c', 'IFS=; exec /bin/ls -- $1', 'sh', 
var])

(assuming the POSIX sh is in /bin on your system).

We're leaving $1 unquoted, which means it will be subject to
split+glob, but because we set IFS to the empty string, there
will be no splitting, only globbing.

Note that if "var" is empty, that will list the current
directory as $1 will expand to 0 argument instead of 1 empty
argument.

Now, why call sh do the globbing when python can do it itself?

import glob
proc = subprocess.Popen(["/bin/ls", "--"] + glob.glob(var))

(note that if "var" doesn't match any file, that will result in
an empty list which again will cause ls to list the current
directory).

And if you just want to print the list of matching file names
(for which "ls" is *not* the command you would want to use),

for file in glob.glob(var):
  print file

(though beware that it will choke if file names contain sequence
of bytes that don't form valid characters in the locale, while
most "sh" and "ls" implementations would ignore the problem and
print the file names verbatim)

-- 
Stephane




reply via email to

[Prev in Thread] Current Thread [Next in Thread]