Wie man hoch geladene Dateien automatisch auf Viren überprüft mit php-clamavlib

Version 1.0
Author: Falko Timme


Diese Anleitung veranschaulicht, wie man Dateien, die von Nutzern durch ein Web Formular auf Deinen Server geladen wurden, automatisch überprüfst mit PHP und ClamAV. So kannst Du sicherstellen, dass Dein Formular zum Hochladen von Dateien nicht dafür missbraucht wird, Schadprogramme zu verbreiten. Um PHP und ClamAV zu verbinden, installieren wir das Paket php5-clamavlib/php4-clamavlib welches zur Zeit nicht dokumentiert ist. Dieses Paket ist für Debian Etch und Sid, für Ubuntu Dapper Drake und Edgy Eft verfügbar, pass auf, dass Du eines dieser Plattformen verwendest.

Allerdings möchte ich an dieser Stelle darauf hinweisen, dass dies hier nicht der einzige Weg ist, ein solches System zu installieren. Es gibt viele Möglichkeiten - ich selbst habe mich für diese entschieden. Ich kann aber nicht garantieren, dass diese Lösung bei jedem funktioniert bzw. für jeden die richtige ist!

1 Vorbemerkung

Wie bereits erwähnt muss Dein System entweder Debian Etch, Sid, Ubuntu Dapper Drake oder Ubuntu Edgy Eft verwenden. Außerdem solltest Du bereits Apache2 und PHP4 oder PHP5 installiert haben.

Wenn Du die Plattform Debian Sarge verwendest, kannst Du das Paket php-clamavlib von diesem Ordner aus installieren backports.org: http://www.backports.org/debian/pool/main/p/php-clamavlib/
Ich gehe davon aus, dass /var/www als Standard Dokumenten-Root verwendest. Wenn Du bereits mehrere Web Seiten auf Deinem Server hast, richt den Dokumenten-Root nach Deinen Bedürfnissen aus. Ich verwende 192.168.0.100 als IP Adresse meines Servers. Richte dies auch ein. Wenn Du mehrere Web Seiten mit einer namensbasierten vhost Konfigration hast, musst Du die betreffende Domain/FQDN verwenden, um Zugriff auf die Web Seite anstelle der IP Adresse zu haben.

Alle Schritte unternehme ich als Root Benutzer. Vergewissere Dich also dass Du als Root Benutzer angemeldet bist. Wenn Du auf Ubuntu arbeitest, stelle allen Befehlen z.B. sudo voran.

apt-get update

würde zu dem werden

sudo apt-get update


2 Ändern von /etc/apt/sources.list

Wenn Du Ubuntu Dapper Drake oder Ubuntu Edgy Eft verwendest, musst Du /etc/apt/sources.list so ändern, dass die universe Paketdatenbank frei geschaltet ist. Wenn Du auf Debian Etch oder Sid arbeitest, ändere /etc/apt/sources.list nicht.

2.1 Ubuntu Dapper Drake

Bearbeite /etc/apt/sources.list und und gehe sicher, dass die Zeile deb http://de.archive.ubuntu.com/ubuntu/ dapper universe darin enthalten ist (ersetze de.archive.ubuntu.com mit Deinem dem Ubuntu Spiegel, der in Deiner Nähe ist):

vi /etc/apt/sources.list


[...]
deb http://de.archive.ubuntu.com/ubuntu/ dapper universe [...]
Führ Folgendes aus

apt-get update

um die Paketdatenbank zu aktualisieren.

2.2 Ubuntu Edgy Eft

Bearbeite /etc/apt/sources.list und gehe sicher, dass die Zeile deb http://de.archive.ubuntu.com/ubuntu/ edgy universe darin enthalten ist (ersetze de.archive.ubuntu.com mit dem Ubuntu Spiegel, der in Deiner Nähe ist):

vi /etc/apt/sources.list


[...]
deb http://de.archive.ubuntu.com/ubuntu/ edgy universe [...]
Führe Folgendes aus

apt-get update

um die Paketdatenbank zu aktualisieren.

3 Installation von ClamAV und php-clamavlib

Als Nächstes installieren wir ClamAV (unseren neuen Viren-Scanner) und php-clamavlib, das Paket, das die Verbindung zwischen PHP und ClamAV bereitstellt.

Wenn Du PHP5 verwendest, führe Folgendes aus:

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

Wenn D PHP4 verwendest, führe Folgendes aus:

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

Starte dann Apache neu:

/etc/init.d/apache2 restart

Wenn Du magst, kannst Du Dir Deine Datei php.ini (/etc/php5/apache2/php.ini wenn Du PHP5 verwendest, /etc/php4/apache2/php.ini wenn Du PHP4 verwendest) ansehen um zu sehen, ob Einträge für php-clamavlib enthalten sind. So sollte es aussehen:

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
Wenn Du auf Debian Sarge arbeitest, kannst Du das Paket php-clamavlib von backports.org aus installieren: http://www.backports.org/debian/pool/main/p/php-clamavlib/

4 php-clamavlib Funktionen

Es gibt keinerlei Dokumentation über die PHP Funktionen, die php-clamavlib bereitstellt, jedoch habe ich im Quelltext von php-clamavlib ein Skript gefunden namens clamav.php, das anzeigt, welche Funktionen verfügbar sind. Wir erstellen nun das gleiche Skript in unserem /var/www Verzeichnis:

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 "$strn"; ?>
Gib nun http://192.168.0.100/clamav.php in Deinem Browser ein. So sollte die Ausgabe aussehen:

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

Den Fehler in der letzten Zeile kannst Du ignorieren.

Nun wissen wir welche Funktionen verfügbar sind, aber wir wissen nicht, welche Parameter wir benötigen. Ich bin auf diese Seite gestoßen: http://www.clamav.net/doc/0.88.4/html/node41.html die ähnliche Funktionen im Quelltext von ClamAV beschreibt. Durch Lesen und Testen habe ich herausgefunden, wie man die Funktionen cl_info(), cl_scanfile(), cl_setlimits(), und clam_get_version() verwendet. Das sind alle Funktionen, die wir brauchen, um hochgeladene Dateien zu überprüfen (eigentlich bräuchten wir nur cl_scanfile()). Im nächsten Kapitel werden wir ein kleines HTML Formular zu Hochladen von Dateien erstellen, um hochgeladene Dateien auf Viren zu überprüfen.

5 Ein kleines Beispiel

Nun werden wir ein kleines Skript zum Hochladen von Dateien erstellen, /var/www/upload.php, welches ein HTML Formular zum Hochladen von Dateien beinhaltet. Wenn Du das Formular absendest, wird sich das Skript selbst aufrufen und die Funktionen cl_info(), cl_scanfile(), cl_setlimits(), und clam_get_version() verwenden um die hochgeladene Datei auf Viren zu überprüfen. Wenn die Datei in Ordnung ist, wird sie in das Verzeichnis /var/www/uploads hoch geladen, anderenfalls zeigt das Skript eine Fehlermeldung (die angibt, welcher Virus/Wurm etc. gefunden worden ist) und löscht die Datei auf dem Server.

Zunächst müssen wir das Verzeichnis /var/www/uploads erstellen und es für den Apache Benutzer www-data beschreibbar zu machen:

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

Dann erstellen wir die Datei /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>
Es beinhaltet das Formular für die hochgeladene Datei:
[...]
<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> [...]
Nachdem Du eine Datei ausgewählt und auf Submit geklickt hast, wird sich das Skript selbst aufrufen und (action="upload.php") und den PHP Code zu Beginn ausführen:
[...]
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']); } } [...]
Angaben über die hochgeladene Datei sind gespeichert in $_FILES array, also nutzen wir dies um die Datei zu überprüfen (Du kannst die Zeile print_r($_FILES); aktivieren um zu sehen, was im array abgelegt ist).

Ich verwende die cl_setlimits() Funktion um die Limits für die Virenüberprüfung einzurichten, um DOS Angriffe zu verhindern (der Virenüberprüfungsprozess kann alle Systemressourcen aufbrauchen). So sieht die Verwendung aus:

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 */
Im Grunde definieren diese Werte das Verhalten von ClamAV wenn die Archive (zip files, tar.gz files, bz2 files, etc.) überprüft werden. Wenn Du die cl_setlimits() Funktion nicht verwendest, werden die entsprechenden Werte aus der Datei php.ini genommen.

Die Hauptfunktion ist die cl_scanfile() Funktion, die als Argument den Pfad zur zu überprüfenden Datei übernimmt. Hochgeladene Dateien werden vorübergehend gespeichert bevor sie verarbeitet werden (für gewöhnlich in /tmp; das hängt von Deinen php.ini Einstellungen ab). Diese vorläufige Datei wird gespeichert in $_FILES['file']['tmp_name'] also übergeben wir diese Variable an die cl_scanfile() Funktion. Wenn keine Virus gefunden wird, gibt sie FALSE zurück, sonst wird der Name des Virus' angezeigt.

clam_get_version() benötigt keine Parameter. Diese Funktion gibt die Version des installierten ClamAV (wie 0.88.4) zurück.

Letztenendes verwende ich die Funktion cl_info() welche - wie clam_get_version() - keine Parameter benötigt. Sie gibt nähere Informationen über ClamAV an, zum Beispiel ClamAV version 0.88.4 with 85917 virus signatures loaded.

Lass es uns nun testen. Gib http://192.168.0.100/upload.php in Deinem Browser ein. Dies solltest Du nun sehen:


Klicke auf Browse... und wähle eine Datei aus, die Du von Deiner Festplatte hochladen möchtest (eine Datei, die kein Viruas und nicht größer als 2 MB ist - das ist die maximale Standardgröße zum Hochladen, die in Deiner php.ini Datei geändert werden kann):


Der Ladevorgang sollte gelingen und etwas Ähnliches müsstest Du jetzt sehen:


Nun müssen wir einen Virus bekommen um es zu testen. Glücklicherweise gibt es einen Eicar Testvirus, eine Datei, die keinen Schaden anrichtet, aber dessen Signatur von allen Virenscannern erkannt wird, damit Du testen kannst, ob Dein Virenscanner funktioniert. Gehe zu http://www.eicar.org/anti_virus_test_file.htm und lade die Dateien eicar.com, eicar_com.zip, und the eicarcom2.zip files auf Deine Festplatte. Teste dann das Hochladen mit jeder von ihnen:


Wenn alles klappt, sollte php-clamavlib den Virus erkennen und sich weigern ihn hochzuladen:

6 Links