Skip to content
Advertisement

Download abuse with php Content-Disposition: attachment and readfile

I’m having a download abuse issue with php Content-Disposition: attachment and readfile. It seems that my problem is with readfile, because although this script works, whether or not the client closes their browser, readfile reads the entire contents of the mp4, setting up the possibility of abuse with scripts initiating the download and immediately closing the progress. Something, somewhere, is running a script which clicks this link hundreds of times per second, running my php script and immediately cancelling their download, but my server is preparing that entire file to be offloaded each time.

Here’s the script I’m running, when the user/abuser clicks a download link:

<?php
// get MP4 address
$MP4Address = $_GET["MP4Address"];

// We'll be outputting a MOV
header( 'Content-Type: application/octet-stream' );

$filename = basename($MP4Address);

// Name file
header('Content-Disposition: attachment; filename="'.$filename.'"');

// Source file
readfile($MP4Address);
?>

I suspect that readfile is the culprit here, but without it, the client will receive an empty file. There must be a more modern, proper way of doing this, but I’m not sure what it could be.

Advertisement

Answer

Unless you’ve called ignore_user_abort(true) PHP should get the signal that the connection has been aborted and cease execution. But it’s possible that once you’re inside the readfile() call PHP is not able to watch for that signal since it’s busy doing low-level IO.

I would say you’ve got 2 options, the first being to simply write some code to detect and block the person that’s abusing your service. You downloads are already backed by a PHP script, so adding in a bit of checking and filtering should be relatively simple.

The other would be to replace the readfile() call with a bit of [admittedly less efficient] code that should give PHP some breathing space to look for user aborts.

function read_file($filename, $chunksize=4096) {
  if( ! $fh = fopen($filename, 'rb') ) {
    throw new Exception('Failed to open file');
  }
  while($chunk = fread($fh, $chunksize)) {
    echo $chunk;
  }
  fclose($fh);
}
User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement