Simple cross-site requests ovvero processare richieste in modo sicuro

MarcoGrazia

Utente Attivo
15 Dic 2009
686
8
18
58
Udine
www.stilisticamente.com
Ciao.
Prima di tutto spiego di cosa si tratta.
Come tutti sanno, quando da un browser si richiede una pagina web, si mette in moto un meccanismo complesso che permette al "sistema internet" di funzionare.
Il browser manda una serie di input sotto forma di messaggi codificati al server per richiedere quanto richiesto dall'utente e il server risponde in qualche modo.
Fin qui la base fin troppo semplificata :D

Saltando ogni precisazione, anche perché molti di voi già le conoscono, mi soffermo solo su due aspetti particolari della questione:
  1. mostrare la pagina
  2. inviare richieste GET o POST
La prima è perfino ovvia, ma mettiamo che questa pagina contenga un form di dati da inviare, per esempio una pagina di login, tanto per citare.
Se inseriamo all'inizio della nostra pagina, ovviamente scritta in PHP, qualcosa del genere:
PHP:
...
echo '<p>VARIABILE _SERVER</p><pre>';
var_dump($_SERVER);
echo '</pre>';
...
Otterremo qualche cosa del genere:
/home/server/upload.php:7:
array (size=32)
'HTTP_HOST' => string '192.168.56.1' (length=12)
'HTTP_CONNECTION' => string 'keep-alive' (length=10)
'HTTP_UPGRADE_INSECURE_REQUESTS' => string '1' (length=1)
'HTTP_USER_AGENT' => string 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36' (length=115)
'HTTP_DNT' => string '1' (length=1)
...
eccetera
Cioè indicazioni riguardo la richiesta che abbiamo fatto al server.
Fino a qui nulla di strano, anzi è la norma.
Ma che succede se dalla stessa pagina inviamo una richiesta di tipo GET o POST?
La risposta è in una piccola ma significativa risposta nella variabile _SERVER che poi andremo a sfruttare per rendere più sicura la nostra applicazione.
Ecco il punto 2: inviando una richiesta con dati al server, questo risponde inserendo l'origine di chi ha fatta la richiesta.
/home/server/upload.php:7:
array (size=37)
'HTTP_HOST' => string '192.168.56.1' (length=12)
'HTTP_CONNECTION' => string 'keep-alive' (length=10)
'CONTENT_LENGTH' => string '611' (length=3)
'HTTP_CACHE_CONTROL' => string 'max-age=0' (length=9)
'HTTP_ORIGIN' => string 'http://192.168.56.1' (length=19)
'HTTP_UPGRADE_INSECURE_REQUESTS' => string '1' (length=1)
'HTTP_DNT' => string '1' (length=1)
'CONTENT_TYPE' => string 'multipart/form-data; boundary=----WebKitFormBoundaryIbOZ8b9bSRcpmbve' (length=68)
A dire il vero ne inserisce diverse di cose, ma ORIGIN è ciò che ci serve per verificare la richiesta.
Essa contiene l'origine da cui è stata fatta la richiesta.
Qui si vede l'indirizzo IP ma si può trovare il dominio se questo è settato; io ho il mio serverino casalingo che non ha settato un dominio, quindi mostra l'IP.
Fa lo stesso, l'importante è il dato, e noi lo sfrutteremo.
Lo script che presento, riguarda proprio la verifica dell'origine della richiesta e serve a verificare se un invio tramite GET o POST provenga proprio dal dominio a cui si sta cercando di loggarci.
Lo script è commentato, quindi spero che basti questo, in ogni caso mi fermo qui per ora, dato che come al solito mi sono già dilungato abbastanza. :rolleyes:

Script per evitare (mitigare) l'accesso indesiderato alla nostra applicazione:
file: login.php
PHP:
<?php
/*
 * Copyright 2019 Marco Grazia
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 *
 *
 */
session_start();
ob_start();
try {

$errore = '';  //    Un flag per mostrare un eventuale errore nella pagina.
//  Semplicemente la paginetta di login. Inserite la vostra
$htmlFile =<<<EOF
<!DOCTYPE html>
<html lang="it">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Login</title>
    </head>
 
    <body>
        <h1>Sign in</h1>
        $errore
        <form  action="login.php" method="POST">
            <p><label for="account">Account:</label><br>
            <input name="account" type="text" id="account" required value="" placeholder="Il tuo account"></p>
            <p><label for="password">Password:</label><br>
            <input name="password" type="password" id="password" required value=""></p>
            <p><input type="submit" value="Sign  in" name="invia"></p>
        </form>
    </body>
</html>\n
EOF;

//    Anti Cross-Origin (Se la richiesta viene da GET o POST, e da questo sito, prosegue)
//    Se è settato HTTP_ORIGIN all'interno della variabile _SERVER e se è settata con il nome del mio sito, proseguo...
if (isset($_SERVER['HTTP_ORIGIN']) === true && $_SERVER['HTTP_ORIGIN'] == 'http://mio_sito') {

                //  Invio gli header che dispongono l'accettazione del sito, ovvero della pagina.
                header ('Access-Control-Allow-Origin: http://tuo_sito');
                header ('Content-Type: text/html; charset=UTF-8');

                //  Quindi processo la richiesta, ovvero l'accesso al mio sito:
               if (filter_has_var(INPUT_POST, 'invia') && filter_has_var(INPUT_POST, 'invia') == 'Sign in')  {
                 
                    //  Qui, se i dati del modulo corrispondono, avviene il login.
                    //  Ma non è questo l'argomento di discussione, quindi evito di mostrare altro

               } else {
                   //    Se si verifica una bad request mostro un messaggio nella pagina di login,
                  //    settando la variabile $login.
                   $errore = '<h3 style="colore: red;">Si è verificato un errore processando i tuoi dati, riprova.</h3>';
                   echo $htmlFile;  //    Mostro la pagina con la minaccia :-)
              }
              //    Se invece la fase di login è andata a buon fine indirizzo l'utente alla pagina che cercava.
             header('location: http:://mio_sito/' . $pagina_destinazione);
             exit();
} elseif (isset($_SERVER['HTTP_ORIGIN']) === false) {
            //  E se arrivo qui direttamente dal sito?
           //  Secondo lo script mi rimanderebbe indietro, quindi processo pure il caso in cui HTTP_ORIGIN non settato.
           //  In questo caso, semplicemente mostro la pagina di login.
          header('Content-Type: text/html; charset=UTF-8');
          echo $htmlFile;
          //  E attendo fiducioso che l'utente svolga la fase di login.
} else {
    //    Questo è decisamente il caso peggiore: HTTP_ORIGIN esiste, ma il sito di provenienza non corrisponde all'origine.
    //    A questo punto bisogna dire al client browser che le cose non funzioneranno.
    header('Content-Type: text/html; charset=UTF-8');
    //    Creo al volo una paginetta di risposta.
    echo <<<EOF
<!DOCTYPE html>
<html lang="it">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Login</title>
    </head>
 
    <body>
        <h1>Sign in problem</h1>
        <h2>There is a problem with the Cross-Origin method</h2>
        <p>You are probably trying to log in from a domain other than <a href="http://mio_sito">mio_sito</a>.</p>
        <p>Bye!</p>
    </body>
</html>\n[/I]
EOF;
}
 //  Fine fase di controllo.
}  //  Fine try
catch (Exception $e) {
    do {
        printf("%s:%d %s (%d) [%s]\n", $e->getFile(), $e->getLine(), $e->getMessage(), $e->getCode(), get_class($e));
    } while($e = $e->getPrevious());
}
?>
Lo script è tutto qui, in pratica come si può vedere non fa altro che processare la richiesta del server HTTP_ORIGIN, se c'è ne verifica l'autenticità.
Ovvio che http://mio_sito va modificato al bisogno.
Io ci ho giocato per un po' inserendo nell'url anche l'indirizzo di localhost e mi ha bloccato perché non corrispondeva all'IP del server.
Ah! Quasi dimenticavo, è importante considerate anche il protocollo di destinazione, ovvero http://mio_sito e https:://mio_sito, non sono uguali ;-)
Non sarà ne perfetta e ne definitiva dato che per bloccare Cross-Site Request Forgery (CSRF) serve altro, ma è un inizio.
Lo script è ovviamente embrionale, quì è rilasciato sotto licenza, gradirei chi lo usa di mantenerla, grazie.
:confused:
 
Ultima modifica:

marino51

Utente Attivo
28 Feb 2013
2.682
135
63
Lombardia
ma ORIGIN è ciò che ci serve per verificare la richiesta
si incontrano subito due problemi,
il primo problema é generato dai browser che non gestiscono "origin"
il secondo problema é conseguente all'uso di un dato praticamente statico e in chiaro, quindi facilmente intercettabile da malintenzionati
ti chiedo quindi, perché hai adottato questa soluzione (poco sicura ?)
invece di usare una soluzione con token
PHP:
 <input type="hidden" name="token" value="<?php print GenerateToken(); ?>" />
che funziona con i vari browser e che puoi "personalizzare" legando la generazione del token all' utente specifico (se necessario per migliorare la sicurezza) ?

ps, modifica lo script, manca apice di chiusura
T, 'invia) &&
 

MarcoGrazia

Utente Attivo
15 Dic 2009
686
8
18
58
Udine
www.stilisticamente.com
Ciao, scusa il ritardo con cui rispondo.
In realtà cercavo un modo per identificare l'utente all'interno della LAN in cui lavoro.
E' un vizio brutto, ma qui gli indirizzi assegnati, lo sono dal server centrale che non cambia mai, quindi viene facile stare certi che la macchina è quella, ma è un vizio in effetti.
Il tuo sistema è più generico e quindi sicuramente applicabile al "mondo di fuori".

PS: modificato ;)
 

MarcoGrazia

Utente Attivo
15 Dic 2009
686
8
18
58
Udine
www.stilisticamente.com
con qualche attenzione,
in caso di ripartenza del server o del servizio dhcp, gli indirizzi cambiano
Non puoi sapere se e quando ripartee il server, e dato che se riparte il server viene anche azzerata la memoria assegnata al PHP, persino il metodo da te segnalato andrebbe a farsi benedire, con blocco della pagina.
 

marino51

Utente Attivo
28 Feb 2013
2.682
135
63
Lombardia
Non puoi sapere se e quando ripartee il server, e dato che se riparte il server viene anche azzerata la memoria assegnata al PHP, persino il metodo da te segnalato andrebbe a farsi benedire, con blocco della pagina.
non mi sembra che il problema sia un blocco del server per ragioni "casuali"
ma il controllo degli indirizzi ip assegnati a specifici pc

se "cade" il server, non ce n'é per nessuno, lo sappiamo
ma se viene fatto ripartire un server o il dhcp, che azzera gli ip su cui viene fatto un controllo di validità,
ebbene qualche problema potrebbe esserci
la ripartenza di un server e/o dhcp é operazione rara, ma va messa in conto ….