note 123718 added to function.escapeshellarg

From: Date: Sat, 23 Mar 2019 02:32:07 +0000
Subject: note 123718 added to function.escapeshellarg
Groups: php.notes 
Request: Send a blank email to php-notes+get-210425@lists.php.net to get a copy of this message
On Windows, this function naively strips special characters and replaces them with spaces. The resulting string is always safe for use with exec() etc, but the operation is not lossless - strings containing " or % will not be passed through to the child process correctly. Escaping shell commands on Windows is not a simple matter. There are two distinct escape mechanisms which serve different purposes: 1) The convention used by the CommandLineToArgV() windows system function, used by the child process to interpret the command line arguments 2) The convention used by cmd.exe to escape meta-characters In almost all cases when creating a child process from PHP on Windows, it is done by invoking cmd.exe - this is to enable the use of shell functionality such as I/O redirection and environment variable substitution. As a consequence, values must be escaped first for CommandLineToArgV(), then for cmd.exe. If the executed command contains further indirect calls through cmd.exe, values must be escaped again for each level of indirection. The following functions can be used to correctly escape strings such that they are safely passed through to a child process: <?php /** * Escape a single value in accordance with CommandLineToArgV() * https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85) * * @param string $value * @return string */ function escape_win32_argv($value) { $expr = '( [\x00-\x20\x7F"] # control chars, whitespace or double quote | \\\\++ (?=("|$)) # backslashes followed by a quote or at the end )ux'; $quote = false; $replacer = function($match) use($value, &$quote) { switch ($match[0][0]) { // only inspect the first byte of the match case '"': // double quotes are escaped and must be quoted $match[0] = '\\"'; case ' ': case "\t": // spaces and tabs are ok but must be quoted $quote = true; return $match[0]; case '\\': // matching backslashes are escaped if quoted return $match[0] . $match[0]; default: throw new InvalidArgumentException(sprintf( "Invalid byte at offset %d: 0x%02X", strpos($value, $match[0]), ord($match[0]) )); } }; $escaped = preg_replace_callback($expr, $replacer, (string)$value); if ($escaped === null) { throw preg_last_error() === PREG_BAD_UTF8_ERROR ? new InvalidArgumentException("Invalid UTF-8 string") : new Error("PCRE error: " . preg_last_error()); } return $quote // only quote when needed ? '"' . $escaped . '"' : $value; } /** * Escape cmd.exe metacharacters with ^ * * @param string $value * @return string */ function escape_win32_cmd($value) { return preg_replace('([()%!^"<>&|])', '^$0', (string)$value); } // usage $badString = 'String with "C:\\quotes\\" or malicious %OS% stuff \\'; $cmdParts = ['php', '-r', 'echo $argv[1];', $badString]; // Always escape each argument individually $escaped = implode(' ', array_map('escape_win32_argv', $cmdParts)); // In most cases, escape for cmd.exe as well // The only exception is when using proc_open() with the bypass_shell option $cmd = escape_win32_cmd($escaped); $result = shell_exec($cmd); echo <<<STR Escaped: $escaped Cmd: $cmd Original: '$badString' Received: '$result' STR; ---- Server IP: 208.43.231.9 Probable Submitter: 185.85.3.194 (proxied: 95.148.99.216) ---- Manual Page -- http://php.net/manual/en/function.escapeshellarg.php Edit -- https://master.php.net/note/edit/123718 Del: integrated -- https://master.php.net/note/delete/123718/integrated Del: useless -- https://master.php.net/note/delete/123718/useless Del: bad code -- https://master.php.net/note/delete/123718/bad+code Del: spam -- https://master.php.net/note/delete/123718/spam Del: non-english -- https://master.php.net/note/delete/123718/non-english Del: in docs -- https://master.php.net/note/delete/123718/in+docs Del: other reasons-- https://master.php.net/note/delete/123718 Reject -- https://master.php.net/note/reject/123718 Search -- https://master.php.net/manage/user-notes.php

« previous php.notes (#210425) next »