Hacker ha utilizzato il caricamento delle immagini per ottenere il codice PHP nel mio sito

115

Sto lavorando su un sito web - proprio ora è nelle prime fasi di test, non ancora lanciato e ha solo dati di test - grazie al cielo.

Prima di tutto, un hacker ha capito la password per accedere alle pagine di amministrazione dei siti web * . Penso che abbiano usato un keylogger sul computer di un amico che ha effettuato l'accesso al sito per darmi un feedback.

In secondo luogo, hanno usato una casella di caricamento delle immagini per caricare un file PHP. Ho effettuato controlli rigorosi in modo che solo i file .jpg e .png siano accettati, tutto il resto dovrebbe essere stato rifiutato. Sicuramente non c'è modo di caricare un file .jpg e quindi modificare l'estensione una volta archiviato il file?

Per fortuna anch'io generi nuovi nomi di file quando mi viene inviato un file, quindi non penso che siano riusciti a localizzare il file per eseguire il codice.

Non riesco proprio a capire come il sito web abbia consentito il passaggio di un file PHP. Cosa c'è che non va nella mia sicurezza? Il codice della funzione di convalida è riportato di seguito:

function ValidateChange_Logo(theForm)
{
   var regexp;
   if (theForm.FileUpload1.value == "")
   {
      alert("You have not chosen a new logo file, or your file is not supported.");
      theForm.FileUpload1.focus();
      return false;
   }
   var extension = theForm.FileUpload1.value.substr(theForm.FileUpload1.value.lastIndexOf('.'));
   if ((extension.toLowerCase() != ".jpg") &&
       (extension.toLowerCase() != ".png"))
   {
      alert("You have not chosen a new logo file, or your file is not supported.");
      theForm.FileUpload1.focus();
      return false;
   }
   return true;
}

Una volta che il file arriva al server, utilizzo il seguente codice per conservare l'estensione e generare un nuovo nome casuale. È un po 'complicato, ma funziona bene.

// Process and Retain File Extension
$fileExt = $_FILES[logo][name];
$reversed = strrev($fileExt);
$extension0 = substr($reversed, 0, 1);
$extension1 = substr($reversed, 1, 1);
$extension2 = substr($reversed, 2, 1);
$fileExtension = ".".$extension2.$extension1.$extension0;
$newName = rand(1000000, 9999999) . $fileExtension;

Ho appena testato con un nome come logo.php;.jpg e sebbene l'immagine non possa essere aperta dal sito web, ha cambiato correttamente il nome in 123456.jpg . Per quanto riguarda logo.php/.jpg , Windows non consente questo tipo di file.

* Pagine protette sul sito web che consentono funzioni semplici: come caricare un'immagine che diventa quindi un nuovo logo per il sito web. I dettagli FTP sono completamente diversi dalla password utilizzata per accedere alle pagine protette sul sito web. Come le credenziali di database e cPanel. Mi sono assicurato che le persone non possano nemmeno visualizzare la cartella e la struttura del file del sito. Non c'è letteralmente alcun modo per pensare di rinominare un'estensione .jpg o .png in .php su questo sito se non si dispone di dettagli FTP.

    
posta Williamz902 04.01.2017 - 11:04
fonte

11 risposte

315

Convalida lato client

Il codice di convalida che hai fornito è in JavaScript. Ciò suggerisce che è il codice che usi per eseguire la validazione sul client.

La regola numero uno di protezione delle applicazioni web è quella di non fidarsi mai del client . Il cliente è sotto il pieno controllo dell'utente - o, in questo caso, dell'attaccante. Non puoi essere certo che qualsiasi codice che invii al client sia usato per qualcosa, e nessun blocco che hai posto sul client ha un valore di sicurezza come mai. La convalida sul client serve solo a fornire un'esperienza utente fluida, per non applicare effettivamente alcun vincolo relativo alla sicurezza.

Un utente malintenzionato potrebbe semplicemente modificare quel pezzo di JavaScript nel proprio browser, o disattivare completamente gli script, o semplicemente non inviare il file da un browser, ma creare la propria richiesta POST con uno strumento come curl.

Devi riconvalidare tutto sul server. Ciò significa che il tuo PHP deve verificare che i file siano del tipo giusto, cosa che il tuo codice corrente non ha.

Come fare questo è un ampio problema che penso sia al di fuori della portata della tua domanda, ma questa domanda è un buon punto di partenza per iniziare a leggere. Potresti voler dare un'occhiata al tuo .htaccess di file.

Ottenere l'estensione

Forse non è un problema di sicurezza, ma questo è un modo migliore per farlo:

$ext = pathinfo($filename, PATHINFO_EXTENSION);

Funzioni PHP magiche

When I store data from edit boxes, I use all three of PHP's functions to clean it:

$cleanedName = strip_tags($_POST[name]); // Remove HTML tags
$cleanedName = htmlspecialchars($cleanedName); // Allow special chars, but store them safely. 
$cleanedName = mysqli_real_escape_string($connectionName, $cleanedName);

Questa non è una buona strategia. Il fatto che ne parli nel contesto della convalida delle estensioni di file mi fa preoccupare che tu stia solo lanciando un paio di funzioni relative alla sicurezza al tuo input sperando che risolva il problema.

Ad esempio, dovresti utilizzare istruzioni preparate e non eseguire l'escape per proteggerti da SQLi. La fuga di tag HTML e caratteri speciali per evitare che XSS debba essere eseguito dopo che i dati sono stati recuperati dal database e come farlo dipende da dove vengono inseriti i dati. E così via, e così via.

Conclusione

Non sto dicendo questo per essere cattivo, ma sembra che tu stia facendo molti errori qui. Non sarei sorpreso se ci fossero altre vulnerabilità. Se la tua app gestisce informazioni sensibili, ti consiglio vivamente di consentire a qualcuno con esperienza di sicurezza di dargli un'occhiata prima di portarlo in produzione.

    
risposta data 04.01.2017 - 11:12
fonte
36

Innanzitutto, se qualcuno ha sfruttato una funzione di caricamento file, assicurati che tu stia verificando il file su entrambi i client e server . Bypassare la protezione JavaScript lato client è banale.

In secondo luogo, assicurati di passare e implementare queste idee da OWASP .

Questo dovrebbe coprire la maggior parte dei tuoi problemi. Assicurati inoltre di non avere altre imperfezioni prive di patch sul tuo server (ad esempio plug-in obsoleti, server sfruttabili, ecc.)

    
risposta data 04.01.2017 - 11:12
fonte
26

È molto importante notare che PHP è stato progettato per essere incorporato in altri contenuti . In particolare, è stato progettato per essere incorporato all'interno di pagine HTML, completando una pagina in gran parte statica con bit minori di dati aggiornati dinamicamente.

E, semplicemente, non gli interessa cosa sia incorporato in . L'interprete PHP eseguirà la scansione di qualsiasi tipo di file arbitrario ed eseguirà qualsiasi contenuto PHP correttamente contrassegnato con tag di script.

Il problema storico che questo ha causato è che, sì, i caricamenti di immagini possono contenere PHP. E se il web server è disposto a filtrare quei file attraverso l'interprete PHP, allora quel codice verrà eseguito. Vedi il plugin di Wordpress "Tim Thumb" fiasco.

La soluzione corretta è assicurarsi che il tuo server web non tratti mai il contenuto non affidabile come qualcosa che dovrebbe essere eseguito tramite PHP. Il tentativo di filtrare l'input è un modo per risolverlo, ma come hai trovato, limitato e soggetto a errori.

Molto meglio verificare che sia la posizione del file che l'estensione del file siano utilizzate per definire cosa processerà PHP e per separare i caricamenti e altri contenuti non attendibili in posizioni ed estensioni che non verranno elaborate. (Il modo in cui lo fai varia da server a server e soffre del fatto che "le cose funzionano di solito significa allentare la sicurezza in un modo in cui non sei a conoscenza".)

    
risposta data 04.01.2017 - 18:44
fonte
22

Puoi disattivare PHP nella directory di caricamento di .htaccess in modo che il tuo server non esegua alcun codice PHP nella directory e nelle sue sottodirectory.

Crea un file .htaccess all'interno della directory di caricamento con questa regola:

php_flag engine off

Tieni presente che la regola .htaccess funzionerà solo se il tuo server PHP è in esecuzione in modalità mod_php .

Modifica: Naturalmente, ho dimenticato di menzionare che .htaccess funziona solo con apache.

    
risposta data 05.01.2017 - 19:20
fonte
11

Stai solo controllando l'estensione, questo non è necessariamente correlato al tipo di file effettivo. Lato server è possibile utilizzare qualcosa come exif_imagetype per verificare che il file sia un'immagine. utilizzo del tipo di immagine exif: link

    
risposta data 04.01.2017 - 14:19
fonte
6

Sono totalmente d'accordo con la risposta accettata, ma suggerirei di fare un po 'di più che limitarsi a giocare con il nome del file. Devi ricomprimere il file originale / caricato con PHP utilizzando GD o Imagick e utilizza la nuova immagine . In questo modo, distruggi qualsiasi codice iniettato (per essere onesti, il 90% delle volte, ci sono modi per far sopravvivere il codice alla compressione, ma è molto lavoro).

Inoltre, puoi usare i file .htaccess per impedire che la directory di caricamento esegua il codice PHP (non so su IIS e probabilmente c'è un file .webconfig equivalente).

<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>

In questo modo, il file caricato verrà eseguito solo se "l'estensione finale" è PHP, quindi image.php.jpg non verrà eseguito.

Alcune risorse:

risposta data 06.01.2017 - 13:24
fonte
6

Una possibile parte di una soluzione qui è di mantenere l'immagine, ma anche per creare una copia ridimensionata con PHP tale da garantire che si ottenga un'immagine e ridurre le dimensioni dell'immagine originale. Questo ha diversi effetti positivi:

  • Non consente mai di utilizzare l'immagine originale per la visualizzazione, quindi è più sicura
  • Risparmia spazio sul tuo server (che può essere o non essere un problema)
  • Maggiore tempo di caricamento per i visitatori del sito, poiché non sarebbero in attesa di caricare un'immagine di circa 10 MB. Invece hanno un'immagine di 300 KB a un caricamento di dimensioni ragionevoli

Il punto principale è che non ci si può fidare dei dati utente, mai. Convalidare tutto, caricare file, input, cookie per garantire che i dati non possano causare problemi.

    
risposta data 04.01.2017 - 22:14
fonte
2

Ho solo una piccola cosa da aggiungere a ciò che non è stato toccato: usando la strategia delle white list.

Come altri già hanno sottolineato, affidarsi esclusivamente alla validazione lato client non è certamente una buona cosa. Hai anche utilizzato una strategia di lista nera che significa che stai controllando le cose che sai / pensi siano cattive e che permetta tutto il resto. Una strategia molto più solida è quella di verificare le cose che sai essere ok e rifiutare tutto il resto.

A volte questo è un compromesso con il feedback degli utenti, come lasciare che gli utenti inesperti sappiano cosa devono fare per caricare correttamente un'immagine.

Nella mia esperienza, le due strategie non sono necessariamente mutuamente esclusive purché vengano utilizzate per scopi diversi: convalida black-list lato client per il feedback degli utenti e validazione della white-list lato server per la sicurezza.

    
risposta data 06.01.2017 - 11:00
fonte
2

Autorizzo il caricamento delle immagini ma presumo che ognuna sia caricata con il codice trojan. Posiziono temporaneamente l'immagine caricata sopra la radice Web al momento del caricamento, quindi utilizzo ImageMagick per convertirlo in BMP, quindi in JPG, quindi spostarlo dove l'app Web può utilizzarlo. Questo rimuove efficacemente qualsiasi codice PHP incorporato.

Come @Sneftel sottolinea nel commento qui sotto, l'esecuzione di questa conversione su un JPG comporterà un degrado dell'immagine. Per il mio scopo, questo è un compromesso accettabile.

    
risposta data 08.01.2017 - 14:04
fonte
1

Verifica i tag EXIF di un'immagine o usa una funzione di strpos per trovare un codice PHP nel contenuto dell'immagine. Esempio:

$imageFile = file_get_contents($your_image_temp_path); // ATTENTION! Temporary path for images is really needed for security reasons!
$dangerousSyntax = ['<?', '<?php', '?>'];
$error = '';
foreach($dangerousSyntax as $value) {
  $find = strpos($value, $imageFile);
  if( $find !== true ) {
    unlink($your_image_temp_path);
    $error = 'We found a dangerous code in your image';
  }
}

if(!empty($error)) { 
  die($error);
}
    
risposta data 07.01.2017 - 20:46
fonte
0

Oltre alla risposta accettata, suggerirei di utilizzare la funzione getImageSize integrata in PHP per ottieni il tipo MIME e assicurati che un file PHP non si nasconda sotto l'estensione di un file immagine.

    
risposta data 06.01.2017 - 09:37
fonte

Leggi altre domande sui tag