Web Hacking: Cómo usar múltiples instancias de Tor desde un programa escrito en PHP y renovar sus IPs constantemente.

En la anterior entrada os conté como instalar múltiples instancias de Tor en una misma máquina Linux, ya que era una técnica que venía utilizando desde hace mucho tiempo cuando creaba aplicaciones en PHP que escaneaban webs de forma masiva.

Esas 5 instancias de Tor tenían circuitos distintos, por lo que tenía a mi disposición 5 IPs diferentes a mi IP real. Además, a través del puerto de control de cada instancia podía renovar el circuito de cada una y conseguir así nuevas IPs

La entrada anterior la podéis encontrar aquí.

En esta entrada os voy a mostrar un pequeño script a modo de ejemplo en PHP que utiliza esas 5 instancias de Tor y que a su vez, por cada petición HTTP en cada instancia, ésta renovará el circuito y tendremos una IP diferente cuando volvamos a utilizarla de nuevo.

Además, como extra, en este script he incluido otra técnica que utilizo, que es cambiar los “headers” de manera aleatoria por cada petición HTTP, de tal manera que la web que escanee pensará que se trata de clientes con distintos navegadores, idiomas, etc.. diferentes.

El código fuente a continuación, o si lo preferíis, alojado en Github aquí.

<?php
        /*
         * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
         * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
         * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
         * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
         * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
         * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
         * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
         * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
         * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
         * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
         * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
         *
         * This software is licensed under the MIT license. For more information,
         * see <https://www.vispo.org>.
         *
         * @license http://www.opensource.org/licenses/mit-license.html  MIT License
         * @author: Daniel Vispo
         * URL: https://www.vispo.org
         * Date: 2017/08/21
         * Desc: How to use 5 Tor proxies at the same time from PHP and
         *       how to renew the Tor circuits by using the Control Ports
         */
        define('__TORPROXIES__',array(
                array(
                        'strHost'=>'127.0.0.1',
                        'intSOCKSPort'=>8000,
                        'intControlPort'=>9000
                ),
                array(
                        'strHost'=>'127.0.0.1',
                        'intSOCKSPort'=>8001,
                        'intControlPort'=>9001,
                ),
                array(
                        'strHost'=>'127.0.0.1',
                        'intSOCKSPort'=>8002,
                        'intControlPort'=>9002,
                ),
                array(
                        'strHost'=>'127.0.0.1',
                        'intSOCKSPort'=>8003,
                        'intControlPort'=>9003,
                ),
                array(
                        'strHost'=>'127.0.0.1',
                        'intSOCKSPort'=>8004,
                        'intControlPort'=>9004,
                )
        ));
        
        define('__CURLHEADERS__',array(
                array(
                        'Accept-Encoding: gzip, deflate',
                        'Connection: keep-alive',
                        'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                        'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8',
                        'Accept-Language: ja-jp',
                        'Referer: https://www.google.co.jp/',
                        'Dnt: 1'
                ),
                array(
                        'Accept-Encoding: gzip, deflate, br',
                        'Connection: keep-alive',
                        'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
                        'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36',
                        'Accept-Language: en-US,en;q=0.8',
                        'Cache-control:no-cache',
                        'Referer: https://www.google.com/'
                ),
                array(
                        'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246',
                        'Accept: text/html,application/xhtml+x…lication/xml;q=0.9,*/*;q=0.8',
                        'Accept-Language: es-ES,en;q=0.5',
                        'Accept-Encoding: gzip, deflate, br',
                        'Referer: https://www.google.es/',
                        'Connection: keep-alive',
                        'Upgrade-Insecure-Requests: 1',
                        'Cache-Control: max-age=0'
                )
        ));
        $intCurrentTorUsedIdx=-1;
        
        function HTTPGetTor($strURL,$booTorEnabled=TRUE,$booTorRenew=TRUE){
                $intReturn=-1;
                try{
                        if (is_null($strURL) || strlen(trim($strURL))==0){
                                throw new Exception('Param $strURL is 0 chars length or is null.');
                        }else{
                                $hndCurl = curl_init($strURL);
                                if ($booTorEnabled){
                                        $intCurrentTorUsedIdx=$GLOBALS['intCurrentTorUsedIdx']=$GLOBALS['intCurrentTorUsedIdx']+1;
                                        if ($intCurrentTorUsedIdx==(count(__TORPROXIES__)))$intCurrentTorUsedIdx=$GLOBALS['intCurrentTorUsedIdx']=0;
                                        curl_setopt($hndCurl, CURLOPT_PROXY, __TORPROXIES__[$intCurrentTorUsedIdx]['strHost'].':'.__TORPROXIES__[$intCurrentTorUsedIdx]['intSOCKSPort']);
                                        curl_setopt($hndCurl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
                                }
                                curl_setopt($hndCurl, CURLOPT_HEADER, FALSE);
                                curl_setopt($hndCurl, CURLINFO_HEADER_OUT, TRUE);
                                curl_setopt($hndCurl, CURLOPT_VERBOSE, TRUE);
                                curl_setopt($hndCurl, CURLOPT_FOLLOWLOCATION, TRUE);
                                curl_setopt($hndCurl, CURLOPT_HTTPHEADER, __CURLHEADERS__[array_rand(__CURLHEADERS__,1)]);
                                curl_setopt($hndCurl, CURLOPT_ENCODING, 1);
                                curl_setopt($hndCurl, CURLOPT_RETURNTRANSFER, TRUE);
                                curl_setopt($hndCurl, CURLOPT_TIMEOUT,60);
                                curl_setopt($hndCurl, CURLOPT_FRESH_CONNECT, TRUE);
                                $strResponse=curl_exec($hndCurl);
                                $intSize=curl_getinfo($hndCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
                                $strHTTPReqHeaders=curl_getinfo($hndCurl, CURLINFO_HEADER_OUT);
                                $arrHTTPReqHeaders=array_filter(array_filter(explode(PHP_EOL,$strHTTPReqHeaders),'trim'),'strlen');
                                $strContentType=curl_getinfo($hndCurl, CURLINFO_CONTENT_TYPE);
                                $intHTTPCode=curl_getinfo($hndCurl, CURLINFO_HTTP_CODE);
                                $intReturn=array('intHTTPCode'=>$intHTTPCode,'strContentType'=>$strContentType,'strResponse'=>$strResponse,'intSize'=>$intSize,'arrHTTPReqHeaders'=>$arrHTTPReqHeaders);
                                curl_close($hndCurl);
                                if ($booTorEnabled && $booTorRenew){
                                        $hndControlPort=fsockopen(__TORPROXIES__[$intCurrentTorUsedIdx]['strHost'],__TORPROXIES__[$intCurrentTorUsedIdx]['intControlPort'],$errNo,$errStr,30);
                                        if (!$hndControlPort){
                                                throw new Exception('Cannot Connect to Control Port. Tor Proxy '.__TORPROXIES__[$intCurrentTorUsedIdx]['strHost'].':'.__TORPROXIES__[$intCurrentTorUsedIdx]['intControlPort']);
                                        }else{
                                                fputs($hndControlPort,"AUTHENTICATE \r\n");
                                                $strResponse=fread($hndControlPort,1024);
                                                list($strCode,$strText)=explode(' ',$strResponse,2);
                                                if ($strCode!='250'){
                                                        fclose($hndControlPort);
                                                        throw new Exception('Cannot Authenticate. Tor Proxy '.__TORPROXIES__[$intCurrentTorUsedIdx]['strHost'].':'.__TORPROXIES__[$intCurrentTorUsedIdx]['intControlPort']);
                                                }else{
                                                        fputs($hndControlPort,"SIGNAL NEWNYM\r\n");
                                                        $strResponse=fread($hndControlPort,1024);
                                                        list($strCode,$strText)=explode(' ',$strResponse,2);
                                                        if ($strCode!='250'){
                                                                fclose($hndControlPort);
                                                                throw new Exception('Cannot Renew Circuit. Tor Proxy '.__TORPROXIES__[$intCurrentTorUsedIdx]['strHost'].':'.__TORPROXIES__[$intCurrentTorUsedIdx]['intControlPort']);
                                                        }else{
                                                                fclose($hndControlPort);
                                                        }
                                                }
                                        }
                                }
                        }
                }catch(Exception $e){
                        throw new Exception('('.__FUNCTION__.') - '.$e->getMessage());
                }
                return $intReturn;
        }
        
        function getIPGeo($booTorEnabled=TRUE,$booTorRenew=TRUE){
                try{
                        $arrHTTPResponse=HTTPGetTor('http://ip-api.com/php/',$booTorEnabled,$booTorRenew);
                        if ($arrHTTPResponse['intHTTPCode']!=200){
                                throw new Exception('HTTP Response Code isn\'t 200');
                        }else{
                                if ($arrHTTPResponse['intSize']==0){
                                        throw new Exception('HTTP Response Size is zero bytes');
                                }else{
                                        if ($arrHTTPResponse['strContentType']!='text/plain; charset=utf-8'){
                                                throw new Exception('HTTP Response Content-type isn\'t valid');
                                        }else{
                                                $strResponse=unserialize($arrHTTPResponse['strResponse']);
                                                if (!is_array($strResponse)){
                                                        throw new Exception('HTTP Response isn\t array after unserialize');
                                                }else{
                                                        if (!isset($strResponse['query'])){
                                                                throw new Exception('HTTP Unserialized Response doesn\'t have query property');
                                                        }else{
                                                                echo "\t- IP: \033[32m".$strResponse['query']."\033[30m".PHP_EOL;
                                                                if (!isset($strResponse['city'])){
                                                                        throw new Exception('HTTP Unserialized Response doesn\'t have city property');
                                                                }else{
                                                                        echo "\t- City: ".$strResponse['city'].PHP_EOL;
                                                                        if (!isset($strResponse['country'])){
                                                                                throw new Exception('HTTP Unserialized Response doesn\'t have country property');
                                                                        }else{
                                                                                echo "\t- Country: ".$strResponse['country'].PHP_EOL;
                                                                                if (count($arrHTTPResponse['arrHTTPReqHeaders'])==0){
                                                                                        throw new Exception('HTTP Response arrHTTPReqHeaders is an empty array');
                                                                                }else{
                                                                                        for ($i=0;$i<count($arrHTTPResponse['arrHTTPReqHeaders']);$i++){ echo "\t- HTTP Request Header ".$i.": ".$arrHTTPResponse['arrHTTPReqHeaders'][$i].PHP_EOL; } } } } } } } } } }catch(Exception $e){ throw new Exception('('.__FUNCTION__.') - '.$e->getMessage());
                }
        }
        //MAIN
        try{
                echo "\033[30m";
                echo "\033[35m+ Get Public IP and its Geolocation\033[30m".PHP_EOL;
                getIPGeo(FALSE);
                for ($i=0;$i<count(__TORPROXIES__);$i++){ $intIdx=$intCurrentTorUsedIdx+1; if ($intIdx>count(__TORPROXIES__)-1)$intIdx=0;
                        echo "\033[34m+ Get IP and its Geolocation through Tor Proxy ".__TORPROXIES__[$intIdx]['strHost'].":".__TORPROXIES__[$intIdx]['intSOCKSPort']."\033[30m".PHP_EOL;
                        getIPGeo();
                }
                echo "\033[30m";
        }catch(Exception $e){
                echo "\033[31m\t- ERROR: ".$e->getMessage()."\033[30m".PHP_EOL;
                echo "\033[30m";
        }

ENTENDIENDO EL CÓDIGO

Este script está pensado para utilizar la “SAPI” “PHP-CLI”, o dicho de otro modo, para la línea de comandos. PHP es un excelente lenguaje para la web. pero es igual de potente para realizar scripts de todo tipo que se ejecuten directamente en la shell de Linux, Mac o Windows. De hecho, personalmente utilizo más scripts creados para “PHP-CLI” que e”bash”, por poner un ejemplo.

Este programa utiliza las 5 instancias de Tor que expliqué como instalar en la anterior entrada. Para ello, he definido un array de 5 elementos que contienen a su vez, un array asociativo con la IP del host, el puerto SOCKS5 y el puerto de Control por cada instancia de Tor existente

define('__TORPROXIES__',array(
        array(
                'strHost'=>'127.0.0.1',
                'intSOCKSPort'=>8000,
                'intControlPort'=>9000
        ),
        array(
                'strHost'=>'127.0.0.1',
                'intSOCKSPort'=>8001,
                'intControlPort'=>9001,
        ),
        array(
                'strHost'=>'127.0.0.1',
                'intSOCKSPort'=>8002,
                'intControlPort'=>9002,
        ),
        array(
                'strHost'=>'127.0.0.1',
                'intSOCKSPort'=>8003,
                'intControlPort'=>9003,
        ),
        array(
                'strHost'=>'127.0.0.1',
                'intSOCKSPort'=>8004,
                'intControlPort'=>9004,
        )
));

El script además, esta conformado por 2 funciones más el cuerpo del código que se ejecutará en primer lugar (“main”). Probablemente la función principal y motivo de esta entrada es la función “HTTPGetTor”.

Esta función acepta 3 parámetros de entrada. “$strURL” es el parámetro donde se le pasa la URL a la cual vamos a hacer una petición “HTTP Get”, “$booTorEnabled” permite especificar si se utlizará una instancia Tor proxy de las 5 disponibles y “$booTorRenew” que especifica si el parámetro anterior (“$booTorEnabled”) fue activado, el circuito de la instancia Tor que se utilice será renovado con una nueva IP de salida tras obtener la respuesta HTTP de la URL que le pasamos al primer parámetro.

La función en sí, utiliza el módulo “cURL” de PHP para hacer peticiones HTTP y cabe destacar que para utilizar proxies SOCKS5 se especifican varias opciones de cURL para que las peticiones se enruten a través de los proxy Tor.

curl_setopt($hndCurl, CURLOPT_PROXY, __TORPROXIES__[$intCurrentTorUsedIdx]['strHost'].':'.__TORPROXIES__[$intCurrentTorUsedIdx]['intSOCKSPort']);
curl_setopt($hndCurl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);

A la primera opción “CURLOPT_PROXY” se le pasa la IP y el puerto socks de la instancia Tor seleccionada para usar y con la opción “CURLOPT_PROXYTYPE” se especifica que el tipo de Proxy será “SOCKS5”.

Tras hacer la petición HTTP y recibir los datos, en el caso que la variable “$booTorRenew” estuviera a “TRUE”, la función se conecta al puerto de control, se autentifica y después se envía la señal “NEWNYM” para que se cree un nuevo circuito.

$hndControlPort=fsockopen(__TORPROXIES__[$intCurrentTorUsedIdx]['strHost'],__TORPROXIES__[$intCurrentTorUsedIdx]['intControlPort'],$errNo,$errStr,30);

Nos conectamos al host a través del puerto de control de la instancia Tor seleccionada

fputs($hndControlPort,"AUTHENTICATE \r\n");

Nos autenticamos. Para este tutorial no utilizamos ningún tipo de “password” para acceder al puerto de control, por lo que podéis poner “AUTHENTICATE \r\n” o “AUTHENTICATE \”password\”\r\n” como puse en el ejemplo del articulo anterior. Al no tener “password” podéis poner lo que queráis que os autentificaréis sin mayores problemas.

fputs($hndControlPort,"SIGNAL NEWNYM\r\n");

Tras autenticarnos satisfactoriamente, se le envía la señal “NEWNYM” que ya he explicado antes que hace.

EJECUTANDO EL CÓDIGO

Como he comentado al principio, el script esta pensado para ser ejecutado desde la línea de comandos, asi que al poner

php example1.php

éste se ejecutará y en la salida podréis ver como para empezar, obtiene la IP real pública que tengáis, y su geolocalizacion (color lila). Después mostrará una por una las IPs de las 5 instancias Tor instaladas.

Para comprobar que los circuitos de las instancias Tor se han renovado, podéis volver a ejecutar el script y éste os debería mostrar IPs diferentes. Es decir, IPs diferentes geolocalizadas en otras ciudades del mundo que las que obtuvisteis en la ejecución anterior.

(EXTRA) SELECCIONANDO HTTP REQUEST HEADERS AL AZAR

En el código del script podéis ver lo siguiente

define('__CURLHEADERS__',array(
        array(
                'Accept-Encoding: gzip, deflate',
                'Connection: keep-alive',
                'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8',
                'Accept-Language: ja-jp',
                'Referer: https://www.google.co.jp/',
                'Dnt: 1'
        ),
        array(
                'Accept-Encoding: gzip, deflate, br',
                'Connection: keep-alive',
                'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
                'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36',
                'Accept-Language: en-US,en;q=0.8',
                'Cache-control:no-cache',
                'Referer: https://www.google.com/'
        ),
        array(
                'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246',
                'Accept: text/html,application/xhtml+x…lication/xml;q=0.9,*/*;q=0.8',
                'Accept-Language: es-ES,en;q=0.5',
                'Accept-Encoding: gzip, deflate, br',
                'Referer: https://www.google.es/',
                'Connection: keep-alive',
                'Upgrade-Insecure-Requests: 1',
                'Cache-Control: max-age=0'
        )
));

Esto es un array con cabeceras HTTP que selecciono de manera aleatoria cada vez que se hace una petición HTTP, de manera que aunque al utilizar Tor, cada petición proviene de una IP diferente, sea más difícil para la web que estamos rastreando encontrar un patrón con el que “banearnos” a través de los headers HTTP Request, ya que también serán diferentes por cada petición HTTP. Para el ejemplo, he utilizado solo 3, pero lo que tengo en producción es un array de mas de 10000 cabeceras HTTP que he ido coleccionando a través de los logs de una web que tracea todas las cabeceras procedientes de clientes reales con navegadores diferentes. Sólo tener cuidado con que no haya cabeceras que puedan afectar la respuesta de la petición HTTP, como por ejemplo, estar esperando el código de una página web para escritorio y le enviéis un “User-Agent” de móvil.

Por cierto, las cabeceras para la petición HTTP se hacen de manera aleatoria y se le pasan como opción a cURL en la función “HTTPGetTor”

curl_setopt($hndCurl, CURLOPT_HTTPHEADER, __CURLHEADERS__[array_rand(__CURLHEADERS__,1)]);

¡ Y nada más !, vuelvo a repetir que ¡ no hagáis maldades con ésto !

Si te ha gustado y quieres compartir esta página..