Skocz do zawartości
Zaloguj się, aby obserwować  
m3ntor

Zabezpiecznie skryptu uploadu obrazków.

Polecane posty

Poszukuję wiedzy od bardziej doświadczonych kolegów, na temat tego, czy stosowane przeze mnie zabezpieczenia skryptu uploadu obrazków będą skuteczne przed atakami typu code injection i innymi.

 

Mamy htmlowski standardowy formularz z opcją wyboru pliku do uploadu.

 

Następnie PHP leci następującymi kodami (poza sprawdzaniem czy plik został wybrany i rozmiar maksymalny pliku):

IF($_FILES['plik']['type'] == 'image/png' or $_FILES['plik']['type'] == 'image/jpeg' or $_FILES['plik']['type'] == 'image/gif'){
	$x = getimagesize($_FILES['plik']['tmp_name']);
	IF(!is_array($x) or $x[0] < 2){
		$blad++ ; echo 'Nie kombinuj, proszę.';
		}	}
else
	{
	$blad++; echo 'Tylko obrazki.';
	}

Sprawdzanie rozszerzenia oraz sprawdzanie czy zawartość to obrazek czy np. plik test.php z podmienionym rozszerzeniem na test.jpg. Jeżeli jest coś nie tak, wywala zmienną $blad++;.

 

I teraz jeżeli nie ma $blad to:

if (!$blad){
move_uploaded_file($_FILES['plik']['tmp_name'],$uploaded);

$ext2=getimagesize($uploaded);
switch (strtolower($ext2[2])) {
			case '2': $img2  = imagecreatefromjpeg ($uploaded);
			break;
			case '1' : $img2  = imagecreatefromgif  ($uploaded);
			break;
			case '3' : $img2  = imagecreatefrompng  ($uploaded);
			break;
			default    : $stop = true;
			break;
		}
		
  

list($OriginalWidth, $OriginalHeight) = GetImageSize($uploaded);

	
	$ResampledImage2 = ImageCreateTrueColor($OriginalWidth, $OriginalHeight);  
	ImageCopyResampled($ResampledImage2, $img2, 0, 0, 0, 0, $OriginalWidth, $OriginalHeight, $OriginalWidth, $OriginalHeight);  


imagejpeg($ResampledImage2, "/var/www/IH/obrazki/".$numer."", 100); 


$ext=getimagesize($uploaded);
switch (strtolower($ext[2])) {
			case '2': $img  = imagecreatefromjpeg ($uploaded);
			break;
			case '1' : $img  = imagecreatefromgif  ($uploaded);
			break;
			case '3' : $img  = imagecreatefrompng  ($uploaded);
			break;
			default    : $stop = true;
			break;
		}
		
$Width = 300;  
$Height = 300;  



list($OriginalWidth, $OriginalHeight) = GetImageSize($uploaded);
if ($OriginalWidth > $Width || $OriginalHeight > $Height)  
  {  
  $ResampleRatio = $OriginalWidth / $OriginalHeight;  
  
  if ($Width / $Height > $ResampleRatio)  
    $Width = $Height * $ResampleRatio;  
  else  
    $Height = $Width / $ResampleRatio;  
  }  
else  
  {  
  $Width = $OriginalWidth;  
  $Height = $OriginalHeight;      
  }  
	
	$ResampledImage = ImageCreateTrueColor($Width, $Height);  
	ImageCopyResampled($ResampledImage, $img, 0, 0, 0, 0, $Width, $Height, $OriginalWidth, $OriginalHeight);  


imagejpeg($ResampledImage, "/var/www/IH/obrazki/".$numer."_thumb", 80);

W skrócie. Przesuwam zauploadowany plik do odpowiedniego katalogu, nadaję mu nazwę z funkcji TIMESTAMP+kilkucyfrowy losowy numer, zapisując bez rozszerzenia, chwilę później GD tworzy miniaturkę _thumb, a następnie znowu za pomocą GD zapisuję ten sam obraz w miejsce tego pliku, który przed chwilą przesunąłem funkcją move_uploaded_file.

 

Innymi słowy - na serwerze w katalogu z obrazkami są dwa nowe pliki, obydwa "przerobione" przez GD.

 

Wg tego artykułu, zuploadowanych w ten sposób plików nie da się otworzyć przez include() (wywala błąd serwera)

http://php.webtutor.pl/pl/2011/04/11/code-injection-czyli-prosty-wirus-php-w-obrazku-jpeg/, zaś zanim wprowadziłem zabezpieczenie z GD2 pliki bezproblemowo przechodziły na serwer i dało się je otworzyć z funkcji include().

 

Wiem, że pewnie wyważam otwarte drzwi, ale zależy mi, żeby skrypt był szczelny. Jakieś rady?

Edytowano przez m3ntor (zobacz historię edycji)

Udostępnij ten post


Link to postu
Udostępnij na innych stronach

Wnioskuję po tym co napisałeś, że mój sposób zabezpieczenia jest dobry?

 

A innymi słowy, jeżeli zabronię bezpośredniego dostępu do obrazków w katalogu a będę je wyświetlał jedynie z poziomu www np.:

if (($_GET['view']) && ($_GET['view'] != 0)){
$numer = mysql_escape_string($_GET['view']);
echo '<img src="adres/$numer">';
}

Pytanie czy jeżeli zabronię bezpośredniego dostępu do obrazków, to czy będzie się dało je udostępniać na innych stronach czy forach?

Udostępnij ten post


Link to postu
Udostępnij na innych stronach

Ja bym to zrobił podobnie jak @samu. Wyrzucił obrazki na osobnego vhosta typu images.domena.pl w nginxie i wyrzucił jakikolwiek most do php'ka. Nginx sam z siebie serwuje wyłącznie statykę i nie umie nic zrobić ze skryptami .php, a zatem co najwyżej zwróci plaintext do usera.

 

BTW, jeśli GD ma jakąś funkcję, która zwraca error jak nie może przemielić obrazka to również można wyczekać errora i wyrzucić błąd, ale w sumie sprawdzasz rozmiar obrazka już wcześniej więc nie jest to potrzebne.

 

Dobrym pomysłem jest również zgrepowanie pliku po common tagach chociażby <?php ?>, ale to ponownie nie jest wymagane w przypadku twojej aktualnej implementacji.

 

Tak czy siak jak dla mnie nginx bez php będzie tu najbardziej odpowiedni. Nawet jak ktoś jakimś cudem sfake'uje tagi i zuploaduje php'ka to i tak nic go nie otworzy.

Edytowano przez Archi (zobacz historię edycji)

Udostępnij ten post


Link to postu
Udostępnij na innych stronach

Zrobiłem to jeszcze inaczej, niż proponowaliście, a mianowicie (skrypt znajduje się w var/www/skrypt/

 

Plik view.php.

<?php
@header('Content-type: image/jpg');

$numer = mysql_escape_string($_GET['view']);
$plik = @fopen('../../2WyVYRzriftxyiHhzkcO/'.$numer.'', 'rb');
$obrazek = @fread($plik, filesize('../../2WyVYRzriftxyiHhzkcO/'.$numer.''));
fclose($plik);

echo $obrazek;
?>

Następnie obrazek można wyświetlić adresem view.php?view=numer_obrazka.

 

Żeby jeszcze utrudnić sprawę, ustawiam phpem tym plikom chmod 0600.

 

Czy to jest stuprocentowo bezpieczna metoda?

Edytowano przez m3ntor (zobacz historię edycji)

Udostępnij ten post


Link to postu
Udostępnij na innych stronach

Nie jest

 

bo ktoś za $numer może podać '../../../jakisplik.php' i sobie np. odczytac twoje haslo do bazy

 

najlepsza metoda to tak jak pisal poprzednik - obrazki na osobnym vhost, ktory nie ma php

Udostępnij ten post


Link to postu
Udostępnij na innych stronach


<?php
@header('Content-type: image/jpg');

$numer = (int)$_GET['view'];

if(file_exists('../../2WyVYRzriftxyiHhzkcO/'.$numer) === false) {
return false;
}

echo file_get_contents('../../2WyVYRzriftxyiHhzkcO/'.$numer);
?>

 

Edytowano przez Macsch (zobacz historię edycji)

Udostępnij ten post


Link to postu
Udostępnij na innych stronach

@elcct

 

Sprawdziłem to na przykładzie prostego pliku php z phpinfo (view.php?view=test) i plik się nie wykonał, domyślam się, że pliki spoza /var/www/ nie są czytane przez parser php, ale mogę się mylić. Zarówno przy próbie wrzucenia go z formularza (plik jpg z ukrytym kodem php, phpinfo dokładnie) jak i ręcznego wrzucenia do katalogu. Nie ma możliwości, żeby ktoś wrzucił plik z jakimkolwiek rozszerzeniem, bo skrypt usuwa nazwę i rozszerzenia, zostawiając jedynie losowo wygenerowany numer, więc o view=test.php nie ma mowy.

 

Czy może jest coś o czym nie wiem?

 

@macsch

 

 

W kodzie, który zaproponowałeś nie rozumiem tylko tej linijki:

echo file_get_contents('../../2WyVYRzriftxyiHhzkcO/'.$numer);

Jak to różni się od kodu użytego przeze mnie?

 

Udostępnij ten post


Link to postu
Udostępnij na innych stronach

@elcct

 

Sprawdziłem to na przykładzie prostego pliku php z phpinfo (view.php?view=test) i plik się nie wykonał, domyślam się, że pliki spoza /var/www/ nie są czytane przez parser php, ale mogę się mylić. Zarówno przy próbie wrzucenia go z formularza (plik jpg z ukrytym kodem php, phpinfo dokładnie) jak i ręcznego wrzucenia do katalogu. Nie ma możliwości, żeby ktoś wrzucił plik z jakimkolwiek rozszerzeniem, bo skrypt usuwa nazwę i rozszerzenia, zostawiając jedynie losowo wygenerowany numer, więc o view=test.php nie ma mowy.

 

 

Musisz tak skonfigurować vhost albo dany katalog aby tam nie uruchamiał się interpreter php.

 

Jaki serwer używasz?

 

Np. na apache chyba możesz dać plik .htaccess to katalogu gdzie masz obrazki z zawartością:

 

php_flag engine off

 

Wtedy w tym katalogu żadne skrypty nie powinny się wykonywać

Udostępnij ten post


Link to postu
Udostępnij na innych stronach

echo file_get_contents('../../2WyVYRzriftxyiHhzkcO/'.$numer);

Jak to różni się od kodu użytego przeze mnie?

 

 

Różni się tym że zamiast użytych 4 funkcji jest 1.

Udostępnij ten post


Link to postu
Udostępnij na innych stronach
Np. na apache chyba możesz dać plik .htaccess to katalogu gdzie masz obrazki z zawartością:

 

php_flag engine off

 

Wtedy w tym katalogu żadne skrypty nie powinny się wykonywać

 

I to prosty i całkiem niezły sposób imho

Udostępnij ten post


Link to postu
Udostępnij na innych stronach

Bądź aktywny! Zaloguj się lub utwórz konto

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto

Zarejestruj nowe konto, to proste!

Zarejestruj nowe konto

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się

Zaloguj się, aby obserwować  

×