Calling sh with script on stdin and passing command line arguments

5,039

Solution 1

This script will ask for a password, decrypt the encrypted script and run it with the first command line option passed to the main script:

#!/bin/bash

bash <(
    openssl enc -base64 -d -aes-256-cbc <<<'U2FsdGVkX1/m01Eg0CvZ7DiBi5Nf81+JrCWfKIDI9WtbkTZIVLhZskkKIioVfbSe'
) "$1"

This does not use the #!-line of the decrypted script but assumes it will run okay under bash (and that it is always decrypted successfully). The decrypt script is passed to a bash interpreter by means of a process substitution on the openssl invocation, along with the first command line argument.

To write the decrypted script to disk, run it only if it was decrypted properly (using its intended interpreter), and then delete it:

#!/bin/bash

tmpscript=$(mktemp)
trap 'rm -f "$tmpscript"' EXIT

if ! openssl enc -base64 -d -aes-256-cbc <<<'U2FsdGVkX1/m01Eg0CvZ7DiBi5Nf81+JrCWfKIDI9WtbkTZIVLhZskkKIioVfbSe' >$tmpscript
then
    exit 1
fi

chmod +x "$tmpscript"
"$tmpscript" "$1"

Would you want to pass all the command line parameters of the main script to the decrypted script, you should change "$1" in the above examples to "$@". The double quotes are important as they ensure that the individual arguments are passed without getting split on whitespaces and without undergoing filename globbing.

Solution 2

You had it in an earlier version of the question — pass a -s option to sh:

echo "U2FsdGVkX1/m01Eg0CvZ7DiBi5Nf81+JrCWfKIDI9WtbkTZIVLhZskkKIioVfbSe" | openssl enc -base64 -d -aes-256-cbc | sh -s "$1"

This tells the shell to read commands from the standard input, while accepting arguments on its command line (after the -s) as positional parameters for the script code coming from the standard input.  Of course shell variables should always be quoted, unless you have a good reason not to, and it's probably more useful in the long run to pass "@" (all the arguments) to the shell.

So it would also be wise to quote all references to shell variables in test1.sh; e.g.,

ls -l "$1"

The first solution (presented above) seems to work well for "normal" arguments.  But, to handle the case of arguments that begin with -, it's better to do sh -s -- "$1" (or "$@").  E.g.,

#!/bin/sh
code="U2FsdGVkX1/m01Eg0CvZ7DiBi5Nf81+JrCWfKIDI9WtbkTZIVLhZskkKIioVfbSe"
echo "$code" | openssl enc -base64 -d -aes-256-cbc | sh -s -- "$@"

(where I put the encrypted script into a variable just to make the command line shorter, so it would fit into the Super User display column).

Share:
5,039
Jack Brown
Author by

Jack Brown

Updated on September 18, 2022

Comments

  • Jack Brown
    Jack Brown over 1 year

    This works of course. I can run ./test1.sh cats and the argument cats is passed correctly to ls.

    $ cat test1.sh
    #!/bin/sh
    ls -l $1
    

    Can you tell me how do I make this second example — the same content as the first one, but encrypted — work the same way? The password is "password". openssl outputs the above script, and ls -l runs, but how do I pass the command line argument to it?

    $ cat test2.sh
    #!/bin/sh
    echo "U2FsdGVkX1/m01Eg0CvZ7DiBi5Nf81+JrCWfKIDI9WtbkTZIVLhZskkKIioVfbSe"|openssl enc -base64 -d -aes-256-cbc|sh $1
    

    To clarify, test2.sh is equivalent to

    $ cat test3.sh
    #!/bin/sh
    printf '%s\n' '#!/bin/sh' 'ls -l $1' | sh $1
    
    • Scott - Слава Україні
      Scott - Слава Україні over 5 years
      What do you want the script to do with the argument?
    • Jack Brown
      Jack Brown over 5 years
      Same as in test1.sh, pass it to ls -l.
    • muru
      muru over 5 years
      There's no ls -l in test2.sh
    • Kusalananda
      Kusalananda over 5 years
      @muru There is, inside the encrypted script in test2.sh.
  • Jack Brown
    Jack Brown over 5 years
    Thanks, the 1st works also with my mysql script where command line arguments are in the middle.
  • Kusalananda
    Kusalananda over 5 years
    Using bash -s or sh -s may confuse things in the script that uses read. Also, it would ignore the interpreter of the embedded script (which would matter if it was later switched out for something written in another language).
  • G-Man Says 'Reinstate Monica'
    G-Man Says 'Reinstate Monica' over 5 years
    @Kusalananda: (1) Interesting.  Does your answer do better? (2) Your answer does the same.
  • Kusalananda
    Kusalananda over 5 years
    The first part of my answer ignores the hashbang, but the second one doesn't. I never pass the decrypted script as data on stdin, so standard input will behave as expected.
  • Jack Brown
    Jack Brown over 5 years
    Works. Thank you. I realised that I need to provide wildcard using \* even to my original script. Example test1.sh cat* would show only one file, while test1.sh cat\* shows all cat* files.
  • Jack Brown
    Jack Brown over 5 years
    Yes, I had -s in my original question. Somehow I edited it many times, it didn't work anyway. It is indeed difficult to figure out that missing part -- "$@" at the end, still is.