it-swarm-eu.dev

Il pirata informatico ha usato il caricamento delle immagini per ottenere PHP nel mio sito

Sto lavorando su un sito Web - in questo momento è nelle prime fasi dei test, non ancora lanciato e ha solo dati di test - per fortuna.

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

In secondo luogo, hanno utilizzato una casella di caricamento delle immagini per caricare un PHP. Ho effettuato un controllo rigoroso in modo che vengano accettati solo i file .jpg e .png - tutto il resto avrebbe dovuto essere rifiutato. Sicuramente lì non è possibile caricare un file .jpg e quindi modificare l'estensione una volta archiviato il file?

Per fortuna ho anche generato nuovi nomi di file quando mi viene inviato un file, quindi non credo che siano stati in grado di individuare il file per eseguire il codice.

Non riesco proprio a capire come il sito web ha lasciato passare un PHP. Cosa c'è di sbagliato nella mia sicurezza? Il codice della funzione di validazione è sotto:

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, uso il seguente codice per conservare l'estensione e generare un nuovo nome casuale. È un po 'disordinato, 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 provato 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. Quanto a logo.php/.jpg, Windows non consente tale nome 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 del database e di cPanel. Ho assicurato che le persone non possono nemmeno visualizzare la cartella e la struttura dei file del sito. Non posso letteralmente pensare di rinominare un'estensione .jpg o .png in .php su questo sito se non si hanno dettagli FTP.

117
Williamz902

Convalida lato client

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

La regola numero uno per proteggere le webapp è non fidarsi mai del client . Il client è sotto il pieno controllo dell'utente o, in questo caso, dell'attaccante. Non si può essere certi che qualsiasi codice inviato al client venga utilizzato per qualsiasi cosa e che nessun blocco messo in atto sul client abbia alcun valore di sicurezza. La convalida sul client serve solo a fornire un'esperienza utente fluida, non a imporre effettivamente vincoli di 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 invece 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 corretti digitare, qualcosa che il tuo codice attuale non ha.

Come farlo è un grosso problema che penso esuli dall'ambito della tua domanda, ma questa domanda sono dei buoni posti per iniziare a leggere. Potresti voler dai un'occhiata al tuo .htaccess file pure.

Ottenere l'estensione

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

$ext = pathinfo($filename, PATHINFO_EXTENSION);

Magia PHP

Quando conservo i dati dalle caselle di modifica, utilizzo tutte e tre le funzioni di PHP per pulirle:

$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 tu lo menzioni nel contesto della convalida delle estensioni di file mi fa preoccupare che stai solo lanciando un paio di funzioni relative alla sicurezza al tuo input nella speranza che risolva il problema.

Ad esempio, dovresti usare istruzioni preparate e non scappare per proteggerti da SQLi. L'escape di tag HTML e caratteri speciali per impedire XSS deve essere eseguita dopo che i dati sono stati recuperati dal database e come farlo dipende da dove vengono inseriti tali dati. E così via, e così via.

Conclusione

Non sto dicendo che questo è cattivo, ma sembra che tu stia facendo molti errori qui. Non sarei sorpreso se ci fossero altre vulnerabilità. Se la tua app gestisce informazioni riservate, ti consiglio vivamente di lasciare che qualcuno con esperienza di sicurezza le dia un'occhiata prima di metterle in produzione.

313
Anders

Prima di tutto, se qualcuno ha sfruttato una funzione di caricamento dei file, assicurati che stai verificando il file su entrambi i client e = server. Bypassare la protezione JavaScript lato client è banale.

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

Questo dovrebbe coprire la maggior parte dei tuoi problemi. Assicurati inoltre di non avere altri difetti non corretti sul tuo server (ad esempio plugin obsoleti, server sfruttabili, ecc.)

36
thel3l

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

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

Il problema storico che ciò 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 fiasco del plugin Wordpress "Tim Thumb".

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

Molto meglio verificare che sia l'ubicazione del file sia l'estensione del file vengano utilizzate per definire cosa PHP e per separare i caricamenti e altri contenuti non attendibili in posizioni ed estensioni che non verranno elaborate. (Come lo fai varia da server a server e soffre del problema "far funzionare le cose di solito significa allentare la sicurezza in modi che non conosci".)

26
gowenfawr

Puoi disabilitare PHP nella tua directory di upload tramite .htaccess in modo che il tuo server non esegua alcun PHP nella directory e nelle sue sottodirectory.

Crea un file .htaccess nella 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 mod_php modalità.

Modifica: Certo, ho dimenticato di menzionare che .htaccess funziona solo in Apache.

23
medskill

Stai solo verificando l'estensione, questo non è necessariamente correlato al tipo di file effettivo. Sul lato server potresti usare qualcosa come exif_imagetype per verificare che il file sia un'immagine. utilizzo del tipo di immagine exif: http://php.net/manual/en/function.exif-imagetype.php

11
iainpb

Sono totalmente d'accordo con la risposta accettata, ma suggerirei di fare un po 'di più che limitarmi a giocare con il nome del file. Dovresti ricomprimere il file originale/caricato con PHP usando Gd o Imagick e usare la nuova immagine . In questo modo, distruggi qualsiasi codice iniettato (a dire il vero, il 90% delle volte, ci sono modi per far sopravvivere il codice alla compressione, ma è un sacco di lavoro).

Inoltre, potresti utilizzare i file .htaccess per impedire l'esecuzione della directory di caricamento PHP (non so IIS e probabilmente esiste un equivalente .webconfig).

<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:

6
akman

Una possibile parte di una soluzione qui è quella di conservare l'immagine, ma anche di fare una copia ridimensionata con PHP in modo da poter essere certi di ottenere un'immagine e ridurre le dimensioni dell'immagine originale. Ciò ha diversi effetti positivi:

  • Non consente mai di utilizzare l'immagine originale per la visualizzazione in modo che sia più sicura
  • Risparmia spazio sul tuo server (che potrebbe essere o meno un problema)
  • Migliore tempo di caricamento per i visitatori del sito in quanto non sarebbero in attesa di caricare un'immagine di circa 10 MB. Invece hanno un'immagine di 300 KB con un caricamento di dimensioni ragionevoli

Il punto principale è che non puoi mai fidarti dei dati degli utenti. Convalida tutto, upload di file, input, cookie per garantire che i dati non possano causare problemi.

6
LostBoy

Ho solo una piccola cosa da aggiungere a questo che non è stato toccato: usare la strategia della lista bianca.

Come altri hanno già sottolineato, basarsi esclusivamente sulla convalida lato client non è certamente una buona cosa. Hai anche utilizzato una strategia nella lista nera, il che significa che stai controllando le cose che sai/pensi siano cattive e che ti consentono 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 far sapere agli utenti inesperti cosa devono fare per caricare correttamente un'immagine.

Nella mia esperienza, le due strategie non si escludono necessariamente a vicenda purché vengano utilizzate per scopi diversi: convalida della lista nera sul lato client per il feedback degli utenti e convalida della lista bianca sul lato server per la sicurezza.

2
Daniel

Autorizzo il caricamento di immagini ma suppongo che ognuno sia caricato con un codice trojan. Posiziono temporaneamente l'immagine caricata sopra la radice Web al momento del caricamento, quindi utilizzo ImageMagick per convertirla in BMP, quindi su JPG, quindi la sposto dove l'app Web può utilizzarla. Ciò rimuove efficacemente qualsiasi codice incorporato PHP.

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

2
a coder

Verifica i tag EXIF ​​di un'immagine o usa una funzione 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);
}
1

Oltre alla risposta accettata, suggerirei di utilizzare la funzione PHP built-in getImageSize per ottenere il tipo MIME e assicurarsi che un file PHP non sia nascosto sotto un'estensione di file di immagine.

0
Grey