Cheap VPS & Xen Server


Residential Proxy Network - Hourly & Monthly Packages

How To Automatically Scan Uploaded Files For Viruses With php-clamavlib


This guide describes how you can automatically scan files uploaded by users through a web form on your server using PHP and ClamAV. That way you can make sure that your upload form will not be abused to distribute malware. To glue PHP and ClamAV, we install the package php5-clamavlib/php4-clamavlib which is rather undocumented at this time. That package is available for Debian Etch and Sid and also for Ubuntu Dapper Drake and Edgy Eft, so make sure you use one of these platforms.

I want to say first that this is not the only way of setting up such a system. There are many ways of achieving this goal but this is the way I take. I do not issue any guarantee that this will work for you!

 

1 Preliminary Note

As I said before, your system must use Debian Etch, Sid, Ubuntu Dapper Drake or Ubuntu Edgy Eft, and you should already have Apache2 and PHP4 or PHP5 installed.

If you are on Debian Sarge, you can install the php-clamavlib package from backports.org: http://www.backports.org/debian/pool/main/p/php-clamavlib/

I assume that you use /var/www as the default document root. If you have multiple web sites on your server already, adjust the document root to your needs. Also, I use 192.168.0.100 as the IP address of my server in this example. Adjust this as well, and if you have multiple web sites with a name-based vhost configuration, you must use the respective domain/FQDN to access the web site instead of the IP address.

I do all the steps here as the root user. So make sure you’re logged in as root or, if you are on Ubuntu, prepend all commands with sudo, e.g.

apt-get update

would become

sudo apt-get update

 

2 Modify /etc/apt/sources.list

If you use Ubuntu Dapper Drake or Ubuntu Edgy Eft, you must modify /etc/apt/sources.list so that the universe repository is enabled. If you are on Debian Etch or Sid, then don’t edit /etc/apt/sources.list.

 

2.1 Ubuntu Dapper Drake

Edit /etc/apt/sources.list and make sure you have the line deb http://de.archive.ubuntu.com/ubuntu/ dapper universe in it (replace de.archive.ubuntu.com with your a Ubuntu mirror close to you):

vi /etc/apt/sources.list

[...]
deb http://de.archive.ubuntu.com/ubuntu/ dapper universe
[...]

Then run

apt-get update

to update the packages database.

 

2.2 Ubuntu Edgy Eft

Edit /etc/apt/sources.list and make sure you have the line deb http://de.archive.ubuntu.com/ubuntu/ edgy universe in it (replace de.archive.ubuntu.com with your a Ubuntu mirror close to you):

vi /etc/apt/sources.list

[...]
deb http://de.archive.ubuntu.com/ubuntu/ edgy universe
[...]

Then run

apt-get update

to update the packages database.

 

3 Install ClamAV And php-clamavlib

Next we install ClamAV (our virus scanner) and php-clamavlib, the package that provides the glue between PHP and ClamAV.

If you use PHP5, run:

apt-get install php5-clamavlib clamav clamav-freshclam clamav-docs arj unzoo

If you use PHP4, run:

apt-get install php4-clamavlib clamav clamav-freshclam clamav-docs arj unzoo

Then restart Apache:

/etc/init.d/apache2 restart

If you like you can take a look at your php.ini (/etc/php5/apache2/php.ini if you use PHP5, /etc/php4/apache2/php.ini if you use PHP4) to see if there are entries for php-clamavlib. These should look like this:

vi /etc/php5/apache2/php.ini

[...]
extension=clamav.so
[clamav]
clamav.dbpath=/var/lib/clamav
clamav.maxreclevel=0
clamav.maxfiles=0
clamav.archivememlim=0
clamav.maxfilesize=0
clamav.maxratio=0

vi /etc/php4/apache2/php.ini

[...]
extension=clamav.so
[clamav]
clamav.dbpath=/var/lib/clamav
clamav.maxreclevel=0
clamav.maxfiles=0
clamav.archivememlim=0
clamav.maxfilesize=0
clamav.maxratio=0

If you are on Debian Sarge, you can install the php-clamavlib package from backports.org: http://www.backports.org/debian/pool/main/p/php-clamavlib/

4 php-clamavlib Functions

There’s no documentation about the PHP functions provided by php-clamavlib, but I’ve found a script called clamav.php in the source code of php-clamavlib that shows which functions are available. We create the same script now in our /var/www directory:

vi /var/www/clamav.php

<?php
if(!extension_loaded('clamav')) {
        dl('clamav.' . PHP_SHLIB_SUFFIX);
}
$module = 'clamav';
$functions = get_extension_funcs($module);
echo "Functions available in the test extension:<br>\n";
foreach($functions as $func) {
    echo $func."<br>\n";
}
echo "<br>\n";
$function = 'confirm_' . $module . '_compiled';
if (extension_loaded($module)) {
        $str = $function($module);
} else {
        $str = "Module $module is not compiled into PHP";
}
echo "$str\n";
?>

Now type in http://192.168.0.100/clamav.php in your browser. The output should look like this:

Functions available in the test extension:
cl_info
cl_scanfile
cl_scanbuff
cl_setlimits
cl_scanfile_ex
cl_scanbuff_ex
cl_pretcode
clam_scan_buffer
clam_scan_file
clam_get_version

Fatal error: Call to undefined function confirm_clamav_compiled() in /var/www/clamav.php on line 14

You can ignore the fatal error in the last line.

Now we know which functions are available, but we don’t know which parameters they need. I found this page: http://www.clamav.net/doc/0.88.4/html/node41.html that describes similar functions in the source code of ClamAV. By testing and by reading that page I found out how to use the functions cl_info(), cl_scanfile(), cl_setlimits(), and clam_get_version(). That are all the functions we need to scan uploaded files (in fact we’d need only cl_scanfile()). In the next chapter we will create a small HTML upload form and use these functions to scan uploaded files for viruses.

5 A Small Example

We will now create a small upload script, /var/www/upload.php, which contains an HTML upload form. If you submit the form, the script will call itself and use cl_info(), cl_scanfile(), cl_setlimits(), and clam_get_version() to scan the uploaded file for viruses. If the file is ok, it will be uploaded to /var/www/uploads, otherwise the script will display an error message (saying which virus/worm etc. it found) and delete the file on the server.

First, we must create the directory /var/www/uploads and make it writable by our Apache user, www-data:

mkdir /var/www/uploads
chown www-data:www-data /var/www/uploads

Then we create the file /var/www/upload.php:

vi /var/www/upload.php

<?php
$upload_dir = '/var/www/uploads/';

if($_POST){
  $error = '';
  //print_r($_FILES);
  if($_FILES['file']['size'] == 0 || !is_file($_FILES['file']['tmp_name'])){
     $error .= 'Please select a file for upload!';
  } else {
    cl_setlimits(5, 1000, 200, 0, 10485760);
    if($malware = cl_scanfile($_FILES['file']['tmp_name'])) $error .= 'We have Malware: '.$malware.'<br>ClamAV version: '.clam_get_version();
  }
  if($error == ''){
    rename($_FILES['file']['tmp_name'], $upload_dir.$_FILES['file']['name']);
  }
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>File-Upload</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>

<body>
<form method="post" action="upload.php" name="fileupload" enctype="multipart/form-data">
<table width="100%" border="0" cellspacing="0" cellpadding="2">
  <tr><td><b>File Upload</b></td></tr>
  <tr><td> </td></tr>
  <?php
  if(isset($error)){
    if($error != ''){
  ?>
  <tr><td><?php echo cl_info(); ?></tr>
  <tr><td><b>Error:</b> <?php echo $error; ?></td></tr>
  <?php
    } else {
  ?>
  <tr><td><b>File <?php echo $_FILES['file']['name']; ?> has successfully been uploaded to <?php echo $upload_dir; ?>!</b></td></tr>
  <?php
    }
  }
  ?>
  <tr><td>
    <table width="500" border="0" cellspacing="0" cellpadding="2">
      <tr>
        <td width="126">File:</td>
        <td width="366"><input type="file" name="file" size="30" value="" maxlength="255"></td>
      </tr>
      <tr>
        <td> </td>
        <td><input name="Upload" type="submit" value="Upload"> <input name="Cancel" type="reset" value="Cancel"></td>
      </tr>
    </table>
  </td></tr>
</table>
</form>
</body>
</html>

It contains the form for uploading a file:

[...]
<form method="post" action="upload.php" name="fileupload" enctype="multipart/form-data">
<table width="100%" border="0" cellspacing="0" cellpadding="2">
  <tr><td><b>File Upload</b></td></tr>
  <tr><td> </td></tr>
  <?php
  if(isset($error)){
    if($error != ''){
  ?>
  <tr><td><?php echo cl_info(); ?></tr>
  <tr><td><b>Error:</b> <?php echo $error; ?></td></tr>
  <?php
    } else {
  ?>
  <tr><td><b>File <?php echo $_FILES['file']['name']; ?> has successfully been uploaded to <?php echo $upload_dir; ?>!</b></td></tr>
  <?php
    }
  }
  ?>
  <tr><td>
    <table width="500" border="0" cellspacing="0" cellpadding="2">
      <tr>
        <td width="126">File:</td>
        <td width="366"><input type="file" name="file" size="30" value="" maxlength="255"></td>
      </tr>
      <tr>
        <td> </td>
        <td><input name="Upload" type="submit" value="Upload"> <input name="Cancel" type="reset" value="Cancel"></td>
      </tr>
    </table>
  </td></tr>
</table>
</form>
[...]

After you’ve selected a file and clicked on the Submit button, the script will call itself (action=”upload.php”) and execute the PHP code at the top:

[...]
if($_POST){
  $error = '';
  //print_r($_FILES);
  if($_FILES['file']['size'] == 0 || !is_file($_FILES['file']['tmp_name'])){
     $error .= 'Please select a file for upload!';
  } else {
    cl_setlimits(5, 1000, 200, 0, 10485760);
    if($malware = cl_scanfile($_FILES['file']['tmp_name'])) $error .= 'We have Malware: '.$malware.'<br>ClamAV version: '.clam_get_version();
  }
  if($error == ''){
    rename($_FILES['file']['tmp_name'], $upload_dir.$_FILES['file']['name']);
  }
}
[...]

Details about the uploaded file are saved in the $_FILES array, so we use that to check the file (you can uncomment the print_r($_FILES); line to see what is stored in the array).

I use the cl_setlimits() function to set limits for the virus scanning process in order to prevent DOS attacks (where the virus scanning process could eat up all resources on the system). The usage is like this:

cl_setlimits($maxreclevel, $maxfiles, $maxratio, $archivememlim, $maxfilesize)

  • $maxreclevel: integer value /* maximal recursion level */
  • $maxfiles: integer value /* maximal number of files to be scanned within archive */
  • $maxratio: integer value /* maximal compression ratio */
  • $archivememlim: boolean /* limit memory usage for bzip2 (0/1) */
  • $maxfilesize: long integer /* archived files larger than this value (in bytes) will not be scanned */

Basically, these values define the behaviour of ClamAV if archives (zip files, tar.gz files, bz2 files, etc.) are scanned. If you don’t use the cl_setlimits() function, the respective values from the php.ini are taken.

The main function is the cl_scanfile() function which takes the path to the file to scan as argument. Uploaded files are temporarily saved (usually in /tmp; depends on your php.ini settings) before they are processed. That temporary file is saved in $_FILES[‘file’][‘tmp_name’] so we pass that variable to the cl_scanfile() function. If no virus is found, it gives back FALSE, otherwise the name of the virus it found.

clam_get_version() doesn’t take any argument. That function gives back the version of the installed ClamAV (like 0.88.4).

Finally I use the function cl_info() which – like clam_get_version() – doesn’t take any argument. It displays more detailed information about ClamAV, for example ClamAV version 0.88.4 with 85917 virus signatures loaded.

Now let’s test. Type in http://192.168.0.100/upload.php in your browser. You should see this:

1

Click on Browse… and select a file to upload from your hard disk (a file that is no virus and not bigger than 2MB – that’s the default maximum upload size which again can be changed in your php.ini):

2

The upload should succeed, and you should see something like this:

3

Now we need to get a virus to test. Fortunately there’s the Eicar test virus, it’s a file that doesn’t do any harm, but its signature was included in all virus scanners out there so that you can test if your virus scanner is working by using that test virus. Go to http://www.eicar.org/anti_virus_test_file.htm and download the eicar.com, the eicar_com.zip, and the eicarcom2.zip files to your hard disk. Then test the upload with each of them:

4

If all goes well, php-clamavlib should recognize the virus and refuse the upload:

5

  • php-clamavlib: http://packages.debian.org/unstable/source/php-clamavlib
  • PHP: http://www.php.net
  • ClamAV: http://www.clamav.net
  • Apache: http://httpd.apache.org
  • Eicar Test Virus: http://www.eicar.org/anti_virus_test_file.htm

Comments

comments