MySql Http Tunneling

di  Antonio Coschignano, venerdì 30 gennaio 2009

Per ovvi motivi di sicurezza molti provider negano l'accesso ai databse MySql al di fuori della propria rete. Un esempio classico é Aruba, che permette l'accesso ai database MySql solo tramite gli script sul server oppure tramite PhpMyAdmin all' indirizzo mysql.aruba.it.

Esistono diverse tecniche per ovviare a questa limitazione, una di queste consiste nel creare uno script php che funga da tunnel (tunneling) e che lanci le query per conto nostro (Http Tunnel), oppure utilizzare dei software MySql già configurati per l' Http Tunneling (per maggiori informazioni vi rimando all'articolo MySql Http Tunnel con Ems Sql Manager).

In questo articolo decriveró uno script php che riceve una richiesta POST con all'interno incapsulata una qualsiasi query MySql e ci dá come risposta il risultato in una pagina HTML strutturata in tabelle oppure semplicemente in formato testo. Lo script deve essere protetto da password, quindi bisogna impostare una password di accesso allo script stesso.

La richiesta POST contiene 4 valori di cui due opzionali :

 $_POST["QUERY"]  //Obbligatorio, contiene la query
 $_POST["PWD"]  //Obbligatorio, contiene la password dello script
 $_POST["TYPE"]  //(TEXT, HTML) Opzionale, specifica il formato del risultato(HTML di default)
 $_POST["LIMIT"]  //Opzionale, specifica il limite di righe per tabella

Innanzitutto creiamo un file di configurazione con i parametri del nostro database, con il nome di dbms.conf.php :

   $db_host = 127.0.0.1; //L' ip del nostro database
   $db_name = example; // Lo schema a cui dobbiamo connetterci
   $db_user = root; //Nome utente del database
   $db_password = admin; //Password del database
   $pwd_script = 123456; //Password dello script


Adesso creiamo la classe in un file che chiamiamo QueryRequest.php (oppure un nome a vostro piacimento) :

        class QueryRequest {
              //Funzione che controllo la correttezza della richiesta POST
              //e controlla anche la password dello script
              function control() {
                     if (!isset($_POST["QUERY"])) return false;
                     if (!isset($_POST["PWD"])) return false;
                     include("dbms.conf.php");
                     $pwd = $_POST["PWD"];
                     if (strcmp($pwd,$pwd_scipt)!=0) return false;
                     return true;
              }

              //Questa è la funzione che compie la query vera e
              //propria contenuta nella richiesta

              function action() {
                     $rows = array();
                     $query = $_POST["QUERY"];

                     //Esclude i caratteri eventuali caratteri escaped
                     $query = str_replace('\\',"", $query); 

                     include("dbms.conf.php");//Includo i parametri del database

                     $db = mysql_pconnect($db_host, $db_user, $db_password);
                     if (!$db) {
                        //Errore nel file dbms.conf.php
                         echo "Parametric del dbms non validi";
                        return;
                     }
                     if (!mysql_select_db($db_name, $db)) {
                        //Errore nel file dbms.conf.php
                        echo "Parametric del dbms non validi";
                        return;
                     }
                     $result = mysql_query($query, $db);

                     //In caso dierrore restituisce una tabella con 
                     //il codice e messaggio di errore Sql

                     if(mysql_error()){
                         $rows[0] = array(0 => "CODE",1 => "MESSAGE");
                         $rows[1] = array(0 => mysql_errno(),1 => mysql_error());
                         $this->writeHtmlResponse($rows,"error");
                         return;
                         mysql_close();
                     }

                     $arr = @mysql_num_rows($result);

                     //Caso in cui non ci sono risultati
                     if ($arr==0) {
                            $rows[0] = array(0 => "RESULT");
                            $rows[1] = array(0 => "-1");
                            $this->writeHtmlResponse($rows);
                            return;
                     }

                     $row = mysql_fetch_row($result);
                     $j = count($row);
                     $arr_name = array();
                     
                     //Copio i nomi delle colonne
                     for ($r =0; $r < $j; $r++)
                            $arr_name[$r] = mysql_field_name($result, $r);

                     $rows[0] = $arr_name;
                     $i = 1;
                     //Copio in una matrice il risultato della query
                     while ($row!=null) {
                            $array = array();
                            $k = 0;
                            foreach($row as $item) {
                                   $array[$k] = $item;
                                   $k++;
                            }
                            $rows[$i] = $array;
                            $row = mysql_fetch_row($result);
                            $i++;
                     }

                     //Se nella richiesta POST è specificato
                     //il valore TYPE a TEXT, il risultato viene restuito testualmente
                     //altrimenti in una pagina HTML strutturata in tabella.
                     //Se il campo non viene specificato, viene restituto il risultato
                     //in HTML di default
                     
                     if (isset($_POST["TYPE"])) {
                            if (strcasecmp($_POST["TYPE"],"TEXT")==0) {
                                   $this->writeTextResponse($rows);
                            } else {
                                 $this->writeHtmlResponse($rows,"result");
                            }
                     } else {
                            $this->writeHtmlResponse($rows,"result");
                     }
              }


              function writeTextResponse($array) {
                     $rows = count($array);
                     $columns = count($array[0]);
                     echo "$rows,$columns\n";//Semplicemente stampo il numero di righe e colonne

                     //Stampo la matrice, uso la virgola per separare le colonne
                     //il return per le righe

                     foreach($array as $row) {
                            foreach($row as $col) {
                                   if ($col=="") $col="null";
                                   echo "'$col',";
                            }
                            echo "\n";
                     }
              }

Adesso vediamo la funzione che restituisce la query in una pagina HTML che é un pó piú complessa :

 function writeHtmlResponse($array, $type) {
        //L' header http
              ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
<HTML> <HEAD> <title>Query Result</title> <meta name="author" content="Antonio Coschignano"/> <meta http-equiv="Content-Type" content="charset=iso-8859-1"/> </HEAD> <BODY>
<?php $rows = count($array); $columns = count($array[0]); $count = 0; $page = 0; echo "<TABLE ID=\"HEADER\" BORDER=\"1\" CELLPADDING=\"3\" COLS=\"$columns\">"; echo "<tr>"; //Stampo in una tabella a parte il nome //delle colonne foreach($array[0] as $col) { if ($col=="") $col="-"; echo "<td>$col</td>"; } echo "</tr></TABLE>"; //Stampo le righe della matrice echo "<TABLE ID=\"RESULT_$page\" BORDER=\"1\" CELLPADDING=\"3\" COLS=\"$columns\">"; $limit = 4000; unset($array[0]); if (isset($_POST["LIMIT"])) $limit = $_POST["LIMIT"]; //Se è impostato il valore limit foreach($array as $row) { echo "<tr>"; foreach($row as $col) { if ($col=="") $col="null"; echo "<td>$col</td>"; } echo "</tr>"; $count++; if ($count==$limit) { echo "</TABLE>"; $page++; echo "<TABLE ID=\"RESULT_$page\" BORDER=\"1\" CELLPADDING=\"3\" COLS=\"$columns\">\n"; $count=0; } } ?> </TABLE> </BODY> </HTML> <?php exit(); }//Fine metodo }//Fine classe

Adesso creiamo il file del modulo che riceve la richiesta POST (in questo caso da un form HTML) ed esegue la query. Chiamiamo il file service.php :

        <?php
        include_once("QueryRequest.php");
        if ($_POST) {

            $queryRequest = new QueryRequest();

            if($queryRequest->control())
                $queryRequest->action();
            else {
                //Stampa un eventuale messaggio di errore
            }
        } 

        ?>

    

Per ultimo un semplice form HTML che utilizza il tutto :

  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>Form Query</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body><br>
   <form method="POST" action="service.php">
       <table>
       <tr><td>	Query  :</td><td> <input type="text" name="QUERY" size="100"> </td></tr>
       <tr><td>	Password : </td><td> <input type="password" name="PWD" size="20"> </td></tr>
       <tr><td>      Limite : </td><td><input type="text" name="LIMIT" size="20"> </td></tr>
       <tr><td>	Formato risultato : </td><td> <select size="1" name="TYPE">
                <option>HTML</option>
                <option>TEXT</option>

            </select></td></tr>
             <tr><td colspan="2"> <input type="submit" value="Esegui query" name="B1"></td></tr>
       </table>
    </form>

  </body>
</html>
     

    

Conclusioni
Per concludere voglio precisare che lo script non è molto curato in termini di sicurezza, che in questo caso è una cosa molto fondamentale. Può essere migliorato, controllando scrupolosamente l' input, gestendo la password dello script con funzioni di crittografia etc... Un possibile utilizzo può essere sviluppato lato client in un qualsiasi linguaggio di programmazione. Il client in questo caso può essere una qualsiasi applicazione che utilzza lo script tramite una richiesta HTTP ed elabora il risultato. In java, tramite i socket, per esempio possiamo emulare in un certo senso i contesti di persistenza degli EJB, creando ad hoc dei Bean che rappresentano le tabelle del nostro database e magari interfacciarli con una applicazione SWING in grado di gestire i nostri dati. Di questo ne parleremo magari in un prossimo articolo.

Scarica l' esempio (ZIP)


12 Commenti per "MySql Http Tunneling"

Autore: Lucio

Ciao, scusami non ho capito una cosa ma come faccio da locale a lanciare le query???

domenica 15 marzo 2009 ore 18:24

Autore: Antonio Coschignano

Ciao Lucio, questo script semplicemente apre una porta verso il database sfruttando il protocollo http. In poche parole bisogna scrivere una applicazione ad hoc da zero che sfrutta questo script, crea la richiesta ed alabora il risultato. Prossimamente pubblicherò un nuovo articolo con una applicazione in java che fa tutto questo, provvisto anche di interfaccia grafica. Purtroppo è un po lungo come lavoro cmq è in fase di preparazione. Spero di essere stato abbastanza chiaro. Ciao.

giovedì 19 marzo 2009 ore 11:07

Autore: Christian

Antonio sei un grande! Io è tutto il giorno che mi scervello per cercare di far connettere una pagina PHP residente su server Aruba ad un DB MySQL remoto ma non ci riesco! Io ho provato il tuo script ma non funziona, alla fine ottengo lo stesso risultato di quando provo a lanciare le query direttamente dalla pagina PHP senza nè tunnel nè niente... Nella parte iniziale dell'articolo dicevi che ci sono molti modi per ovviare a questi problemi, ti prego, me ne diresti almeno uno..? Sto impazzendo! Avevo pensato di creare una pagina PHP sul server Aruba ed una sul server remoto (dove riside fisicamente anche il DB per intenderci) e di far passare in qualche modo i dati delle query come parametri tra queste due pagine ma mi sa che non è una cosa molto furba... dovrei creare una specie di PROXY.. Boh, spero di trovare una soluzione prima di impazzire del tutto ed iniziare a tirare testate al monitor.. Ciao

giovedì 19 marzo 2009 ore 17:44

Autore: Antonio Coschignano

Ciao Christian, non ho capito bene cosa vuoi dire cmq purtroppo non puoi utilizzare questo script con un MySqlQuery Browser oppure con altre applicazioni Desktop che si connettono direttamente con il DB. Esiste un software per l'http tunnel già bello e pronto che penso usa uno script suo (o forse un web service) che dovresti caricare sul tuo server e si chiama Ems MySQL Manager, ed è gratuito. Non posso dirti niente a riguardo perchè non l'ho mai provato. Esiste un altro metodo, il tunnel con l' SSH ma su aruba l'SSH mi sa che non è supportato. Cmq se finisco la mia di applicazione oppure trovo qualcos'altro ti farò sapere. Ciao

giovedì 19 marzo 2009 ore 22:44

Autore: Antonio Coschignano

Ciao Christian, segui bene, scaricati EMS MySQL Manager. Dopo averlo installato, controlla nella directory principale dell'applicazione, ci trovi un file emsproxy.php, fai l'upload ftp di questo file sul tuo server aruba. Fatto questo lancia il programma Ems MySQL Manager, nel menu Database selezione Register Database. Si apre la finestra di configurazione, spunta l' opzione use Http tunneling, poi nel campo host scrvi l'ip del tuo database,e i dati di accesso al tuo db aruba, poi pigia next, inserisci l'url dello script che hai uploadato sul server, tipo http://www.tuosito.it/emsproxy.php e dopo il resto viene tutto da se. L'ho provato è funziona alla grande, è un po lento, ma questo dipende anche dalla connessione. Ciao e fammi sapere.

venerdì 20 marzo 2009 ore 03:34

Autore: Fabio

Ciao Antonio, congratulazioni per la guida. ascolta .. ho una mio web service in php su un server, e diversi db (2-3-4 .. n) per semplicità mettiamo 2 db su altri server esterni a dove ho il sito. Come faccio a collegarmi ai db esterni al mio server? sono in fase di tesi .. aiuutooo ...grazie !

mercoledì 04 novembre 2009 ore 18:03

Autore: Antonio Coschignano

Ciao Fabio, il discorso nel tuo caso è un po complesso. Posso darti solo delle indicazioni. Ti devi collegare al db dal server su cui è residente il web service, e i db si trovano su un altro server giusto?? Lo script che vedi in questo articolo deve essere residente su i server del database, dove non è possibile accedere da un altro server. Quindi dovresti implementare questo script, sul server DB, e richiamarlo con il web service. Sicuramente dovrai modificarlo, per selezionare il db dato che è piu di uno. Poi nel web service, dovrai anche implementare dei parser semplicemente con xml DOM, per leggere le tabelle HTML. Purtroppo è difficile risponderti in maniera esaustiva, richiederebbe un altra pagina :-) e non ho elementi su cui darti ulteriori informazioni. Spero di esserti stato di aiuto. Ciao

giovedì 05 novembre 2009 ore 13:08

Autore: giovanni

Ho messo i file su aruba. Per comunicare con il db che è sempre su aruba cosa devo mettere su $db_host??

giovedì 18 febbraio 2010 ore 23:00

Autore: Antonio Coschignano

Ciao Giovanni, allora $db_host devi mettere l'ip del database di aruba

lunedì 22 febbraio 2010 ore 15:22

Autore: Rob

Ciao, molto chiara la guida, ho capito cos'è quello che mi serve (l'http tunneling), ma non con questo metodo.. Io voglio installare un software (InvoiceX) sul mio computer, ma farlo collegare ad un DB MySql esterno su Aruba, che non supporta la gestione del DB da remoto. C'è il modo di mettere in comunicazione il software con il DB su Aruba? EMS MySql manager credo che funzioni solo se ci sono io a gestire il DB, invece volevo qualcosa di automatico per il software... Grazie, ciao

giovedì 01 aprile 2010 ore 23:52

Autore: Joe

Ciao Antonio...grazie di esistere :) E dopo le loccornie passiamo alla richiesta :) Ho un sito su Aruba dove faccio una specia di e-commerce. Purtroppo non riesco a dialogare con la banca a causa che questa dovrebbe passarmi dei dati (chiave e altro) che dovrei scrivere sul db e come ormai sappiamo aruba non permette. Esiste una possibilità per far si che riesca a scrivere questi benedetti dati. Attendo fiducioso tuo gentile riscontro Joe

mercoledì 20 luglio 2011 ore 19:06

Autore: Antonio

potresti darci qualche suggerimento per migliorare la sicurezza del codice? Magari con qualche link che rimanda a qualche esempio/tutorial/spiegazione su come implementare queste migliorie?

domenica 25 settembre 2011 ore 00:29

Lascia un commento

Nome :
E-mail :
Commento :

Tutti i commenti inseriti devono essere approvati da un amministratore prima di essere visualizzati al pubblico. Si tratta di una misura preventiva contro spam e pubblicità e non è necessario reinviare il commento. Si prega di scrivere commenti in tema. Spam e messaggi promozionali non vengono approvati.