Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:39747 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 66973 invoked from network); 7 Aug 2008 17:37:30 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 7 Aug 2008 17:37:30 -0000 Authentication-Results: pb1.pair.com smtp.mail=tstarling@wikimedia.org; spf=permerror; sender-id=unknown Authentication-Results: pb1.pair.com header.from=tstarling@wikimedia.org; sender-id=unknown Received-SPF: error (pb1.pair.com: domain wikimedia.org from 66.111.4.25 cause and error) X-PHP-List-Original-Sender: tstarling@wikimedia.org X-Host-Fingerprint: 66.111.4.25 out1.smtp.messagingengine.com Received: from [66.111.4.25] ([66.111.4.25:47900] helo=out1.smtp.messagingengine.com) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id 79/01-29324-6D23B984 for ; Thu, 07 Aug 2008 13:37:27 -0400 Received: from compute1.internal (compute1.internal [10.202.2.41]) by out1.messagingengine.com (Postfix) with ESMTP id 741F4155D3B for ; Thu, 7 Aug 2008 13:37:23 -0400 (EDT) Received: from heartbeat2.messagingengine.com ([10.202.2.161]) by compute1.internal (MEProxy); Thu, 07 Aug 2008 13:37:23 -0400 X-Sasl-enc: X3qfGpsWtys75gOzw18bOjkRt3k7DtWTd8yXsSEs08E/ 1218130642 Received: from [192.168.0.103] (unknown [121.209.184.45]) by mail.messagingengine.com (Postfix) with ESMTPSA id 7290E31BCF for ; Thu, 7 Aug 2008 13:37:22 -0400 (EDT) Message-ID: <489B32CF.7010203@wikimedia.org> Date: Fri, 08 Aug 2008 03:37:19 +1000 User-Agent: Thunderbird 2.0.0.16 (X11/20080724) MIME-Version: 1.0 To: PHP Internals List X-Enigmail-Version: 0.95.0 OpenPGP: id=BF976370 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit Subject: escapeshellcmd() security fix broken by common workaround? From: tstarling@wikimedia.org (Tim Starling) Further to my comment on http://bugs.php.net/bug.php?id=45132 Many servers appear to have LANG set to some non-UTF-8 character set. With ext/standard/exec.c version 1.127, released in PHP 5.2.6, this means that apps that try to send UTF-8 to shell arguments are broken. The "invalid" characters are removed. This is (probably) bug 45132. The standard workaround to this, promoted everywhere where I've seen this discussed, is to use something like setlocale(LC_CTYPE,'en_US.UTF-8'). This appears to break the security of escapeshellcmd(), back to how it was in PHP 5.2.5. This is because setlocale() does not set the LC_* environment variables, and so when the shell is spawned, it inherits the same locale as PHP had originally. I haven't found anything spelling out the attack that 1.127 was fixing, but I imagine it goes something like this: a sysadmin has Shift-JIS as their default character set in their terminal. They have a PHP/MySQL web app which stores arbitrary input from an attacker. They run a maintenance script from the command line which takes that arbitrary input, escapes it with escapeshellarg(), and passes it through as an argument to some command. Then an incomplete Shift-JIS character in the attacker's input eats the terminating quote from PHP, and the attacker has an arbitrary shell exploit. So if an application uses setlocale() without putenv("LC_CTYPE=..."), then they reopen the same vulnerability. A complete UTF-8 character can be an incomplete Shift-JIS character. Even if setlocale() set the environment variables by default, bug 45132 would still be a pain to work around. POSIX does not guarantee that en_US.UTF-8 is available, only that "C" is available. It would be nice if escapeshellarg/escapeshellcmd treated the single-byte character sets with an ASCII subset, such as the ISO 8859 family, as 8-bit clean, since there is no possibility in these character sets of either a partial character eating a metacharacter, or of a metacharacter being created during transcoding. Then applications wouldn't need any setlocale() hack in 99.9% of cases. And for watertight escapeshellcmd() operation in a nasty encoding like Shift-JIS, I would suggest something along the lines of: /* Save the old locale */ char * oldlocale = xstrdup(setlocale(LC_CTYPE, NULL)); /* Set the locale to match the LC_CTYPE environment variable */ setlocale(LC_CTYPE, ""); /* ... usual stuff ... */ /* Restore locale */ setlocale(LC_CTYPE, oldlocale); efree(oldlocale); If an application fiddles with LC_CTYPE between calling escapeshellcmd() and calling shell_exec(), they probably deserve what's coming to them. -- Tim Starling