Supervisar directorios con PHP

septiembre 14th, 2011 by admin

Si tenemos una web que permite a los visitantes subir archivos, debemos controlar que no se suba nada inadecuado.

Lo primero sería programar la web de forma segura, intentando que sea “imposible” que un usuario malintencionado suba ficheros maliciosos.

Si hemos realizado lo anterior correctamente es muy probable que no tengamos ningún problema, pero si deseamos un poco mas de seguridad (o control) podemos supervisar el directorio donde esta la web. De esta forma si detectamos cambios en ficheros o ficheros nuevos podemos saber cuales son y actuar en consecuencia.

En el caso de que la web permita subir imágenes, quizás debamos modificar este código para filtrar las imágenes, de lo contrario, cada imagen que se suba nos generará una alerta.

<?php
date_default_timezone_set('Europe/Madrid');
//Timezone soportados: http://www.php.net/manual/es/timezones.php
 
class FileLog
{
	private $_baseDir = ""; //Directorio que se controla
	private $_numSubDir = 0; //Número de subdirectorios
	private $_numFiles = 0; //Número de ficheros
	private $_fileMod = 0; //Número de archivos modificados
	private $_time = ""; //Fecha base desde la que se registran cambios
	private $_badFiles = array(); //Ficheros nuevos o modificados desde la fecha base
 
	public function __construct($dir)
	{
		$this->_baseDir = $dir;
	}
 
	/**
	 * Leer todos los ficheros de un directorio de forma recursiva.
	 * @param String $dir directorio base
	 * @param boolean $review indica si se estan buscando ficheros nuevos/modificados
	 */
	public function readDir($dir, $review = false) {
 
		$openDir = opendir($dir);
 
		while($file = readdir($openDir))
		{
			if($file == "." || $file == ".." || $file == "fileLog.txt") {continue; }			
 
			if(is_dir($dir . "/" . $file)) {
 
				if($review) {
					$this->readDir($dir . "/" . $file . "/", true);	
				} else {
					$this->readDir($dir . "/" . $file . "/");
					$this->_numSubDir++;
				}					
 
			} else { 
 
				if($review) {
					clearstatcache();
 
					$ftime = filemtime($dir . "/" . $file);				
 
					if($ftime > $this->_time) {
						$this->_badFiles[] = date("d/m/Y H:i:s", $this->_time) . " - " . date("d/m/Y H:i:s", $ftime) . " | " . $dir . "/" . $file;
						$this->_fileMod++;
					}
				} else {				
					$this->_numFiles++;
				}
 
			}
		}
	}
 
	/**
	 * 
	 * Crea un archivo (si no existe) y guarda la información de el directorio
	 * que se esta supervisando. La información se guarda con este formato:
	 * fecha&dir&file
	 * 
	 * fecha en formato unix
	 * dir el número de subdirectorios dentro de el directorio supervisado
	 * file el número de ficheros dentro del directorio supervisado
	 * 
	 * @return boolean
	 */
	private function _writeFile()
	{
		$path = dirname(__FILE__) . "/fileLog.txt";
		$data = time() .'&'. $this->_numSubDir .'&'. $this->_numFiles;
 
		$fopen = fopen($path, "w");
 
		if($fopen) {
			if(fwrite($fopen, $data)) {
				return true;
			} else {
				return false;
			}
		} else {
			return true;
		}
	}
 
	/** 
	 * Genera los avisos cuando hay nuevos ficheros o ficheros modificados
	 * @param int $dir número de directorios en el registro base
	 * @param int $files número de ficheros en el registro base
	 */
	private function _alert($dir, $files)
	{
		$log = "";
		$msg = "";
 
		//Avisar de los nuevos ficheros
		$msg = "Hay directorios o ficheros nuevos.\r\n";
		$msg .= "Directorios nuevos: " . ($this->_numSubDir - $dir) . "\r\n";
		$msg .= "Ficheros nuevos: " . ($this->_numFiles - $files);	
		$msg .= "\r\n";	
		$msg .= "En breve recibirá un email con los cambios en los ficheros";	
		$this->_send($msg);
 
		//Buscar fichero nuevos
		$this->readDir($this->_baseDir, true);
 
		//Avisar de los ficheros nuevos y modificados
		foreach($this->_badFiles as $file)
		{
			$log .= $file . "\r\n";
		}
 
		$msg = "Informe de cambios en ficheros\r\n";
		$msg .= "Directorios nuevos: " . ($this->_numSubDir - $dir) . "\r\n";
		$msg .= "Ficheros nuevos: " . ($this->_numFiles - $files) . "\r\n";
		$msg .= "Archivos modificados: " . ($this->_fileMod - ($this->_numFiles - $files)) . "\r\n";
		$this->_send($msg . $log);
	}
 
	/**
	 * Procesa los mensajes generados. En este ejemplo los muestra en pantalla.
	 * Pero si este fichero es ejecutado por un cron la mejor opción sería enviar
	 * estos mensajes por email on crear un archivo log.
	 * @param String $msg
	 */
	private function _send($msg)
	{
		echo nl2br($msg);
		echo "<hr>";
	}
 
	public function __destruct()
	{
		$pathFileLog = dirname(__FILE__) . "/fileLog.txt";
 
		if(file_exists($pathFileLog)) {
 
			$data = file_get_contents($pathFileLog);
			$data = explode("&", $data);
 
			$this->_time = intval(trim($data[0]));
			$dir = $data[1];
			$files = $data[2];
 
			if($this->_numSubDir > $dir || $this->_numFiles > $files) {
				$this->_alert($dir, $files);
			}
 
		} else {
			$this->_writeFile();
		}
	}
}
 
//Exec
$read = new FileLog(dirname(__FILE__));
$read->readDir(dirname(__FILE__));

Etiquetas: ,

blog comments powered by Disqus