通过shell_exec将二进制数据发送到php-cgi

I have a script that sends a post request to /usr/bin/php-cgi. The script is working fine when dealing with plain text, but fails when the data is binary:

$data = file_get_contents('example.jpg');
$size = filesize('example.jpg') + 5; 
$post_data = 'file='.$data;
$response = shell_exec('echo "'.$post_data.'" | 
                        REDIRECT_STATUS=CGI
                        REQUEST_METHOD=POST
                        SCRIPT_FILENAME=/example/script.php 
                        SCRIPT_NAME=/script.php 
                        PATH_INFO=/ 
                        SERVER_NAME=localhost 
                        SERVER_PROTOCOL=HTTP/1.1 
                        REQUEST_URI=/example/index.html 
                        HTTP_HOST=example.com 
                        CONTENT_TYPE=application/x-www-form-urlencoded
                        CONTENT_LENGTH='.$size.' php-cgi');

I get the following error:

sh: -c: line 1: unexpected EOF while looking for matching `"'
sh: -c: line 5: syntax error: unexpected end of file

I guess this is because the data I'm trying to send is binary and must be encoded/escaped somehow.

Like I said the above code works if the data is plain text:

$post_data = "data=sample data to php-cgi";
$size = strlen($post_data);

I also tried to encode the data using base64_encode() but then I face another problem; the data must be decoded from within the receiving script. I was thinking that perhaps I could encode the data in base64 and then add some content or mime type header to force the php-cgi binary to make the conversation?

One other problem is that I like to send the data as an attachment and therefore I think we must set CONTENT_TYPE to multipart/form-data; boundary=<random_boundary> and CONTENT_DISPOSITION to form-data, but I'm not sure how to set these headers from the commandline.

Finally I got this working, the solution was to send a base64 encoded request which also contained a constant named field like ORIGINAL_QUERY_URI to a sort of gateway file that in turn would decode the request and bounce it to it's original destination.

Basically, the server does this:

  1. encode any received file data in base64
  2. add a form-data field named ORIGINAL_REQEUST_URI with the original url as value
  3. assemble a valid http request body encoded as multipart/form-data based on above
  4. send this data using shell_exec to a gateway file that will decode the content

Here is the command I used to send everything to php-cgi:

shell_exec('echo "' . $body . '" | 
            HOST=localhost 
            REDIRECT_STATUS=200 
            REQUEST_METHOD=POST 
            SCRIPT_FILENAME=/<path>/gate.php 
            SCRIPT_NAME=/gate.php 
            PATH_INFO=/ 
            SERVER_NAME=localhost 
            SERVER_PROTOCOL=HTTP/1.1 
            REQUEST_URI=/example.php 
            HTTP_HOST=localhost
            CONTENT_TYPE="multipart/form-data; boundary=' . $boundary . '"  
            CONTENT_LENGTH=' . $size . ' php-cgi');

Then inside the gate.php file I decoded the data and included the file pointed to by theORIGINAL_REQUEST_URI field.

// File: gate.php
if (isset($_FILES)) {
  foreach ($_FILES as $key => $value) {
     // decode the `base64` encoded file data
     $content = file_get_contents($_FILES[$key]['tmp_name']);
     $content = base64_decode($content);
     file_put_contents($_FILES[$key]['tmp_name'], $content);
  }
}
// bounce to original destination
include_once($_POST['ORIGINAL_REQUEST_URI']);  

You are trying to upload binary files through shell_exe to post the contents. shell_exe doesn't accept the binary encoding. If you change the image data to base64 then you problem would be solved. But you will get into another problem i.e. how to identify the submitted text/string i.e. text or image. Presently, I find no solution to identify the submitted value is image or text.

Since, you want to post the image and data, I would suggest you to use CURL and providing the way to submit the image and data through CURL which is used by me also:

$local_directory=dirname(__FILE__).'/local_files/';

$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_VERBOSE, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible;)");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_URL, 'http://localhost/curl_image/uploader.php' );


//most importent curl assues @filed as file field
$post_array = array(
    "my_file"=>"@".$local_directory.'filename.jpg',
    "upload"=>"Upload"
);

curl_setopt($ch, CURLOPT_POSTFIELDS, $post_array);

$response = curl_exec($ch);

echo $response;

You can also store all post data in a temporary file and then cat that file into php-cgi:

char file[20] = "/tmp/php"; //temp post data location
char name[10];
webserver_alpha_random(name, 10); //create random name
strcat(file, name);             
int f = open(file, O_TRUNC | O_WRONLY | O_CREAT);
write(f, conn->content, conn->content_len); //post data from mongoose
close(f);
/* cat temp post data into php-cgi */
/* this function also takes care of all environment variables */
/* but the idea is understandable */
output = webserver_shell("cat %s | php-cgi %s", conn, request, file, request);    
unlink(file);