I have a daemon daemon
running in Server A.
There, there's an argument based script to control the daemon daemon_adm.py
(Server A). Through this script I can insert "messages" to daemon
coming from user input. Free text, whatever you like it to be.
Then, there's a web interface in Server B for daemon_adm.py
in PHP using the phpseclib's SSH2 class.
I know that is strongly discouraged to pass user input to the command line, but well, there's must be a way to pass the text from the web Server B to daemon_adm.py
in Server A.
How can I securely pass a text as argument to a command line utility?
Even if I echo the arguments and pipe them to daemon_adm.py
like this:
<?php
$command = '/path/to/daemon_adm.py "'.$text.'"';
ssh->exec($command);
// or whatever other library or programming language
?>
since this command is executed by a ssh interface with a formatted string, code could be injected
<?php
$text = 'safetext"; echo "hazard"';
$command = '/path/to/daemon_adm.py "'.$text.'"';
ssh->exec($command);
// command sent: /path/to/daemon_adm.py "safetext"; echo "hazard"
?>
My current option in mind is encoding every user input to base64 (which as far as I know doesn't use quotes and spaces in its character set) and decode it inside daemon_adm.py
like this:
<?php
$text = 'safetext"; echo "hazard"';
// Enconding it to base64
$command = '/path/to/daemon_adm.py '.$encoded_text;
ssh->exec($command);
// command sent: /path/to/daemon_adm.py c2FmZXRleHQiOyBlY2hvICJoYXphcmQi
?>
Is this safe enough or convoluted?
-- EDIT --
One indirect solution as indicated by Barmar would be to made daemon_adm.py
accept the text data from stdin, and not as a shell parsable argument.
3 Answers 3
ssh2::exec()
returns a stream, which is connected to the stdin
, stdout
, and stderr
of the remote command. So you can do:
$command = '/path/to/daemon_adm.py';
$stream = $ssh->exec($command);
fwrite($stream, "$text\n");
If you don't want to pass the parameters via stdin, you can use escapeshellarg()
:
$command = '/path/to/daemon_adm.py ' . escapeshellarg($text);
$ssh->exec($command);
-
That would be with php's ssh2 library and
ssh2_shell()
. Still using your example, let$command="/bin/ls "
and$text="-l; echo 'hazard';"
then reading$stream
will show me thels -l
ouput and the word "hazard".$text
contents should never be executed by the shell.Sdlion– Sdlion2014年10月08日 20:44:32 +00:00Commented Oct 8, 2014 at 20:44 -
In my answer
$text
is sent to the command's standard input, it's not a command line argument.Barmar– Barmar2014年10月08日 20:52:07 +00:00Commented Oct 8, 2014 at 20:52 -
Since
/bin/ls
doesn't do anything with its standard input, it will ignore$text
.Barmar– Barmar2014年10月08日 20:53:19 +00:00Commented Oct 8, 2014 at 20:53 -
Then
daemon_adm.py
should not accept any user input as an argument and instead use stdin to accept any user data? That's the preferred method to accept user input?Sdlion– Sdlion2014年10月08日 20:57:13 +00:00Commented Oct 8, 2014 at 20:57 -
That's how you have to do it if you want to hide the input. And it looks like what you're doing in the question, since you use
echo ... | daemon_adm.py
.Barmar– Barmar2014年10月08日 21:01:57 +00:00Commented Oct 8, 2014 at 21:01
To insert a string in a shell snippet and arrange for the shell to interpret the string literally, there are two relatively simple approaches:
- Surround the string with single quotes, and replace each single quote
'
by the 4-character string'\''
. - Prefix each ASCII punctuation character with
\
(you may prefix other characters as well), and replace newlines with''
or""
(newline between single or double quotes).
When invoking a remote command over SSH, keep in mind that the remote shell will expand the command, and in addition, if you're invoking SSH via a local shell, the local shell will also perform expansion, so you need to quote twice.
PHP provides the escapeshellarg
function to escape shell special characters. Snce exec
performs expansion, call it twice on the string you want to protect.
Note that this is fine for text strings, but not for byte strings. Most shells won't let null bytes through.
Another approach which is less error-prone and allows arbitrary byte strings through, but requires changing what runs at the other end, is to pass the string on the remote command's standard input.
-
He's using phpseclib - not a local shell - to perform the commands - so he only needs one round of expansion.neubert– neubert2014年10月12日 15:20:01 +00:00Commented Oct 12, 2014 at 15:20
You could do something like...
$ssh->enablePTY();
$ssh->exec('/path/to/daemon_adm.py');
$ssh->write('...');
echo $ssh->read();
"$*"
? It's just your flattened shell arg array delimited with double quotes - it can be passed as a single argument to another process. You might avoidssh
doing anything with it if you put in a heredoc on<&[3-9]
- the shell will expand it to fill the heredoc and pass it off as a file descriptor - but its contents arent interpreted. Your target process can then read it in at leisure. You might do the same w/env
as well, but that requires more care. Anyway, it can also be passed as a single arg of course.