PHP, why do you do this to me?

Posted by Eric Stein - January 14, 2007 CE @ 12:33:05 UTC
This is sort of a circuitous story, so sit tight. Quite frequently I find myself trying to use xargs to GET SOMETHING USEFUL DONE. This is not very easy to do if the strings going in are really unpleasant, such as those with spaces, quotes, and unicode.

An idea has been sitting on the back burner of my mind for awhile; a program that accepts a stream on standard in and converts each line of input into an escaped string ready to be treated as an argument. Not knowing the exact little details of how to do that with weird unicode things, I figured that php's escapeshellarg() function would probably have the exact code I wanted. PHP is open source, so this should be really easy to do, right?

It takes me all of 5 minutes to download the sourcecode (php-5.2.0.tar.bz2) and grep what I'm looking for. I find this:
/*
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2006 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Author: Rasmus Lerdorf <rasmus@php.net>                              |
   |         Ilia Alshanetsky <iliaa@php.net>                             |
   +----------------------------------------------------------------------+
*/

/* {{{ php_escape_shell_arg
*/
char *php_escape_shell_arg(char *str) {
        int x, y, l;
        char *cmd;

        y = 0;
        l = strlen(str);

        cmd = safe_emalloc(4, l, 3); /* worst case */

#ifdef PHP_WIN32
        cmd[y++] = '"';
#else
        cmd[y++] = '\'';
#endif

        for (x = 0; x < l; x++) {
                switch (str[x]) {
#ifdef PHP_WIN32
                case '"':
                case '%':
                        cmd[y++] = ' ';
                        break;
#else
                case '\'':
                        cmd[y++] = '\'';
                        cmd[y++] = '\\';
                        cmd[y++] = '\'';
#endif
                        /* fall-through */
                default:
                        cmd[y++] = str[x];
                }
        }
#ifdef PHP_WIN32
        cmd[y++] = '"';
#else
        cmd[y++] = '\'';
#endif
        cmd[y] = '\0';
        return cmd;
}
/* }}} */

Someone, somewhere (by the looks of things, Rasmus Lerdorf or Alia Alshanetsky) thought it was a good idea to do it all wrong if there's a null in the string. I go boldly forward to find out if php does indeed act weird when confronted with nulls in strings. My preliminary checks reveal the ugly truth:
eastein@glamdring:/home/eastein$ cat test.php
<?php

$a = "testing " . chr(0) . " 123";

echo $a . "\n";
echo escapeshellarg($a) . "\n";

?>
eastein@glamdring:/home/eastein$ php test.php
testing  123
'testing '
eastein@glamdring:/home/eastein$ php test.php |tr "\0" f
testing f 123
'testing '

Yep, that code isn't really going to work out so well for me. PHP strings can contain nulls and it's quite possible to echo them to the terminal, but the escapeshellarg() function can't handle it. While I pondered this code, I thought I saw a buffer overflow (turns out there isn't) but it encouraged me to check PHP bugs for an already reported buffer overflow in escapeshellarg(). Turns out there's another even more serious security issue in this very function. Oh wait, that bug isn't correct either. When the string is delimited with single rather than double quotes, backticks do not run commands and are treated as normal characters:
eastein@glamdring:/home/eastein$ touch file
eastein@glamdring:/home/eastein$ ls file
file
eastein@glamdring:/home/eastein$ echo 'what`rm file`?';
what`rm file`?
eastein@glamdring:/home/eastein$ ls file
file
eastein@glamdring:/home/eastein$ echo "what`rm file`?";
what?
eastein@glamdring:/home/eastein$ ls file
ls: cannot access file: No such file or directory
eastein@glamdring:/home/eastein$

Essentially, both security issues don't exist, but the proper functionality is nonetheless broken.

Comments

In retrospect..
#3890 - Eric Stein, May 5, 2008 CE @ 17:40:37 UTC
The null character isn't valid on the command line anyway. This is pretty much a useless post.

Post a Comment

Name
Email (not published, I won't spam you)
URL (optional)
Title
Comment
2 + 2?
Worth Saying
Valid XHTML, CSS, RSS | 2ms | Copyright 2004-2024 Eric Stein