Page 1 of 1

download script

Posted: Wed Jan 03, 2007 2:15 pm
by matthijs
A simple download script used to let people download files of type pdf, doc or other. I use it by supplying the script an array of files which are downloadable. And then in the directory with the files I place a htaccess file to disallow access, so that all downloads pass this script.

Code: Select all

<?php

//download files
$files = array("some.pdf",
               "another.pdf",
               "yetanother.pdf",
               "some.doc",
               "another.doc",
               "yetanother.doc"
			   );
				   
if (isset($_GET['download_file']) && in_array($_GET['download_file'], $files)) 
{ 

  $file = basename($_GET['download_file']);
  
  $path = $_SERVER['DOCUMENT_ROOT'] . "/download/"; 
  $fullPath = $path . $file;

  if (!file_exists($fullPath) OR !is_readable($fullPath)) {
    echo "File doesn't exist or is unreadable!";
    exit;
  }
  
  if ($fd = fopen ($fullPath, "r")) {
      $fsize = filesize($fullPath);
      $path_parts = pathinfo($fullPath);
      $ext = strtolower($path_parts["extension"]);
  
      header("Pragma: public");
      header("Expires: 0");
      header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

      switch ($ext) {
          case "pdf":
          header("Content-type: application/pdf"); 
          header("Content-Disposition: attachment; filename=\"".$path_parts["basename"]."\""); 
          break;
          case "doc":
          header("Content-type: application/doc");
          header("Content-Disposition: attachment; filename=\"".$path_parts["basename"]."\"");
          break;
          default;
          header("Content-type: application/octet-stream");
          header("Content-Disposition: filename=\"".$path_parts["basename"]."\"");
      }
      header("Content-length: $fsize");
      header("Cache-Control: private",false);
      while(!feof($fd)) {
          $buffer = fread($fd, 2048);
          echo $buffer;
      }
  }
  fclose ($fd);
   exit;
}
elseif ( isset($_GET['download_file']) && !in_array($_GET['download_file'],$files  )) 
{
   die('Invalid File'); // or change to some more usefull error message ...
}
?>
Part of the reason I show the code here is the new PDF XSS vulnerability which has been discovered. Which makes any pdf file on any site useable as XSS vector. So I tried it with this script, but whatever I try to add to the URL it doesn't seem to be successful.

Any criticque is welcome. Thanks.

Posted: Wed Jan 03, 2007 2:34 pm
by Kieran Huggins
Not critique on your code (which I really haven't read), but couldn't you just use a mod_rewrite rule to strip the query string from a PDF request?

Posted: Wed Jan 03, 2007 2:56 pm
by matthijs
Kieran Huggins wrote:Not critique on your code (which I really haven't read), but couldn't you just use a mod_rewrite rule to strip the query string from a PDF request?
Could you elaborate on what you mean exactly? To strip out everything after the .pdf extension you mean?

Posted: Wed Jan 03, 2007 3:54 pm
by pickle
Pretty straightforward, but I noticed a couple things:

You first check that the file exists & is readable, you then do another check on whether fopen() succeeds. Don't they pretty much give you the same thing? You should be able to eliminate the file_exists()/is_readable() check.

Also, to simplify things, rather than using all the different file functions (feof(), fclose(), etc), just use fopen() to see if you can read the file, then use readfile() to dump the contents of the file.

Also, if this will be used to dump more than the 2 document types you've got, I'd be tempted not to use the switch, but rather set up an array of extension=>mime type. Then you won't have to repeat some of that header() code for each type.

Posted: Wed Jan 03, 2007 4:52 pm
by matthijs
Thanks pickle, your feedback is appreciated. I will look into those issues.

Posted: Wed Jan 03, 2007 5:03 pm
by Kieran Huggins
what I meant was use MOD_REWRITE in your .htaccess file change the request URL from something like:

Code: Select all

http://domain.com/documents/some.pdf?insert_XSS_code_here
to simply:

Code: Select all

http://domain.com/documents/some.pdf
Wouldn't that eliminate the possibility of the exploit?

Posted: Thu Jan 04, 2007 12:51 am
by matthijs
Yes you are right. That would be possible.

However, it seems that everything behind the .pdf is stripped by $_GET already (when # is the character used). So when visiting the URL /?download_file=somefile.pdf#anotherstring, #anotherstring is never passed through to the $_GET var. I'm not sure if the XSS vector works with anything else besides #.

I'm searching the manual now to see what is and what is not passed through. The section about $_GET is not too informative. I remember someone asking about this (the # not being available) because he wanted to use that.

And even if it would pass through, it would not pass this:

Code: Select all

<?php
//download files
$files = array("some.pdf",
               "another.pdf",
               "yetanother.pdf",
               "some.doc",
               "another.doc",
               "yetanother.doc"
                           );
                                  
if (isset($_GET['download_file']) && in_array($_GET['download_file'], $files)) ?>

Posted: Thu Jan 04, 2007 10:22 am
by Kieran Huggins
mod_rewrite would solve the issue in apache BEFORE php gets involved... in fact, if the issue is with apache serving PDFs then there's no reason for php to be invoked in the first place. Does that make sense?

Posted: Thu Jan 04, 2007 1:14 pm
by matthijs
Yes it does.

But to clarify: I didn't write this specific script in response to that vulnerability. I used this script just to make sure people would be offered a download window/prompt, instead of having the pdf opened in the same window the site is on (with all potential troubles of that).

And I (can) use this script to count the amount of downloads for example, so in that case it is useful as well.

Posted: Thu Jan 04, 2007 1:25 pm
by Kieran Huggins
oh - I missed that ;-)

Good on you then!