3

I use the Exiv2 command line tool on Linux to edit image metadata like so:

exiv2 -M"set Iptc.Application2.Caption String This is my caption....." modify IMG.jpg

I want to execute this from PHP, using a caption provide by a user. This will work if the user enters no special characters:

exec('/usr/local/bin/exiv2 -M"set Iptc.Application2.Caption String '.$caption.'" modify IMG.jpg');

I need to allow the user special characters such as single and double quotes. I would like to use escapeshellcmd() to prevent malicious data. How can I correctly escape the command and the argument so that it works? I have tried many options but I can't get it right.

Liam
  • 19,819
  • 24
  • 83
  • 123

3 Answers3

5

Yes, this is a hard problem, because the command is using non-standard shell arguments (like it's own little meta-language). ImageMagick has the same issues.

If you simply use escapeshellarg() within a double-quoted string, it is rendered useless. escapeshellcmd() does escape all special characters, and is safe for use in a double-quoted string. So you need to hard code single quotes around it to make it work right.

exec('/usr/local/bin/exiv2 -M"set Iptc.Application2.Caption String \'' . escapeshellcmd($caption) . '\'" modify IMG.jpg');

The reason escapeshellarg() will not work in a single quoted string is:

# for this input:
The smith's "; rm -rf *; echo "went to town

# after escapeshellarg()
'The smith\'s "; rm -rf *; echo "went to town'

# Works fine if left as a top-level argument
/usr/bin/command 'The smith\'s "; rm -rf *; echo "went to town'

# BUT if put in a double-quoted string:
/usr/bin/command "subcommand1 'The smith\'s "; rm -rf *; echo "went to town'"

# it is broken into 3 shell commands:
/usr/bin/command "something and 'The smith\'s ";
rm -rf *;
echo "went to town'"

# bad day...
user5071535
  • 1,312
  • 8
  • 25
  • 42
gahooa
  • 131,293
  • 12
  • 98
  • 101
  • Thanks, this was really helpful in understanding the problem. It looks like exiv2 got its non-standard shell arguments by allowing exiv2 commands (designed to be written in a file) to also be written as shell arguments as a shortcut. – Liam Aug 11 '09 at 11:44
  • Oh I tried this and it did not preserve double quotes entered by the user. Kudos for the single quotes though, far more common in my input! – Liam Aug 11 '09 at 11:52
0

What about using heredoc?

$str = <<<'EOD'
/usr/local/bin/exiv2 -M "set Iptc.Application2.Caption String $options" modify IMG.jpg
EOD;
exec($str);

To modify this to use excapeshellcmd():

$options = excapeshellcmd($caption);
$command = <<<'EOD'
/usr/local/bin/exiv2 -M"set Iptc.Application2.Caption String $options" modify IMG.jpg
EOD;
exec($command);
stereoscott
  • 13,309
  • 4
  • 33
  • 34
0

Because of Exiv2's non-standard shell arguments, it is not easy to reach a simple and robust solution to handle user-supplied quotes correctly. There is another solution that is likely to be far more reliable and easy to maintain with a small performance penalty.

Write the Exiv2 instructions to a file cmds.txt, then call:

exiv2 -m cmds.txt IMG.jpg

to read the instructions from the file.

Update: I have implemented this method and it requires no escaping of the user-supplied data. This data is written directly to a text file which is read in by Exiv2. The Exiv2 command file format is very simple and newline-terminated, allow no escaping within values, so all I need to do is prevent newlines from passing through, which I was not allowing anyway.

Liam
  • 19,819
  • 24
  • 83
  • 123