Alters

Aventuras de DoHITB: parte III

Buenas!

Empezamos la parte III de mi curioso relato. Hoy con energías renovadas!

Antes de empezar, me gustaría hacer un pequeño inciso... han contactado conmigo desde una de las editoriales donde envié uno de mis libros, ¡y me han dicho que les intereso! Espero que esto sea el comienzo de algo bueno...

En fin, iré informando :-P

Sigamos con el blog... jejeje

Como comentaba en la entrada anterior, voy a explicar cómo hice para agrandar el efecto de la brecha.

Supongo que pensaréis que una brecha que consiste nada más que en un poco de SPAM no es demasiada cosa... pero, ¿Qué pasa si indagamos un poco más?

Vamos allá.

Primero, hice lo que creí correcto, así que intenté contactar con el webmaster para avisarle de la brecha... y no me dijo nada. Así que decidí investigar un poco más por puro placer...

Así, me dirijo a la página de inicio de sesión, y abro el explorador de elementos (F12 en Chrome).

Voy a ver qué hace el botón "submit" del formulario de inicio de sesión...

onclick="return check();"

Ajá! hace una pequeña comprobación mediante JS. Busco la función en el archivo externo, y veo que hace una llamada AJAX para recuperar ciertos datos. Concretamente llama a un archivo llamado "acciones.php", al que envía, como si fuera una línea de comandos, varios parámetros; concretamente "accion=login&u=email&p=pass".

Así, modifico un poco mi VB.net para desviar la llamada HTML. Ahora llamaré a "acciones.php" con algunos datos de prueba. Dejo una pequeña referencia de lo que hice:

  • accion=login;u=mi email;p=mi pass
    • Retorno HTML: "correcto"
  • accion=asdf
    • Retorno HTML: "error"
  • accion=login;u=mi email;p=""
    • Retorno HTML: "falta pass"
  • accion=login;u=mi email;p=otro pass
    • Retorno HTML: "mal pass"
Es decir, este archivo "acciones.php" manda un código "estándar" de respuesta para cada posible acción. Traducido en PHP, y partiendo de las investigaciones que hice, podría tener esta estructura:

if($_GET['accion'] == 'login'){
  if(isset($_GET['u']) && isset($_GET['p'])){
    //select para comprobar el email
    if(**email está bien**){
      //select para comprobar el pass
      if(**pass está bien**){
        return "correcto";
      }else{
        return "mal pass";
      }
    }else{
      return "mal email";
    }
  }else{
     if(isset($_GET['u']){return "falta pass";}
     else{return "falta email";}
  }
}else{
  return "error";
}

En este caso en particular veo un error que es "de manual".

NUNCA DEBES DECIR SI LO QUE ESTÁ MAL ES EL EMAIL O EL PASSWORD.

¿Porqué?

Fácil. Imaginad que no conozco ninguno de los dos datos; puedo ir probando emails hasta que me diga que el email está bien pero no el password. Siempre se tiene que decir algo genérico, para evitar estas cosas...

Pero bueno, en este caso no importa mucho, ya que yo se que mi email sirve.

En este caso, yo se que el password lo asigna la propia web, y que además tiene 4 caracteres de longiutd.

Aquí veo otro fallo... 4 caracteres de longitud es bastante poco. Deberían ser 6 caracteres mínimo, incluyendo símbolos.

Y a todo esto... ¿Cómo combinar la vulnerabilidad anterior?

Sencillo: se puede usar la primera herramienta para hacer SPAM masivo a todas las publicaciones, y así conseguir emails válidos, con los que usaremos nuestro conocimiento...



Pues, vamos a hacer algo sencillo: fuerza bruta.

Creo un archivo con todas las posibilidades de 4 caracteres (14.773.663 entradas). Éste lo parto en 160 archivos llamados CC_X (x es un contador, de 0 a 159). Esto ya veremos de que sirve.

Así, vamos a ver qué hara nuestro programa de fuerza bruta:

  • Obtiene una clave del archivo
  • Llama a acciones.php?accion=login&u=email&p=clave
  • Si sale bien sabemos qué HTML retornará. Paramos la ejecución
  • Sino, siguiente código.
¿Y repetir esto casi 15 millones de veces? Bueno, para eso usaremos los Threads de JAVA.
Crearemos 160 hilos paralelos (un por cada archivo), y cada uno hará este proceso de manera paralela. Por lo que podemos aprovechar más el rendimiento de nuestro PC.

Bien, de momento dejamos la entrada en suspense (para el código JAVA). En la próxima entrada veremos:

 - Cómo hacer los Thread
 - Cómo hacer peticiones HTTP desde JAVA
 - Cómo manejar los errores HTTP

Como veis, hay errores HTTP (lanzar 160 peticiones simultáneas tiene su cosa). Esto quiere decir que a veces la petición HTTP fallará, por lo que tenemos que tener algún sistema que vaya comprobando todo.

A modo de información, dejaré unas estadísticas:

Cuando acabé el proyecto JAVA, dejé el programa 2 horas mientras salí a pasear, y al volver me encontré lo siguiente:

  • Peticiones lanzadas correctamente: 72.489
  • Peticiones falladas: 29.473
  • Tiempo: 120 minutos
  • Total peticiones: 101.962
  • Porcentaje de acierto: 71,1%
  • Peticiones/segundo: 14.16 peticiones/segundo
  • Tiempo estimado para recorrer los 14773663 registros: 12 días

Bien, como comprenderéis es un tiempo largo... pero el aspecto "aleatorio" de acceder a 160 registros de claves diferentes aumenta la probabilidad de encontrar la clave antes de finalizar todos los ficheros, con lo que dificilmente tardaría 12 días.

De todas maneras, si tenéis una contraseña de 4 dígitos y alguien quiere vuestra contraseña... será cuestión de tiempo que os la pillen... si es algo "importante", 12 días es tiempo que se puede esperar.

Con esta reflexión espero que algunos piensen en las contraseñas que usan en su vida cotidiana....

Saludos, y

¡Hasta la próxima!

Aventuras de DoHITB: parte II (c)

Buenas!

Hoy por fin acabamos la parte II de esta pequeña aventura... os comento de paso que tengo otra paranoia escéptica lista para mostraros.

Así, veremos bastante código útil en VB.net.

Para recordar un poco (y hacer algo de preámbulo), veamos qué haremos:

  • Conexión FTP
  • Conexión HTTP
    • Con GET
    • Con POST

Empecemos con ello:

Primero debemos conocer (obviamente) nuestros datos de login para nuestro FTP. Aquí usaré los siguientes:

host = mihost.com
user = miUser
pass = David

Ahora, empezando con VB.net, crearemos una clase para facilitar todo un poco.

Para empezar, nuestra clase tendrá tres atributos:

Dim host, user, pass As String

También debemos hacer unos imports (muy importante)

Imports System.Net.FtpWebRequest
Imports System.Net
Imports System.IO

Bien, ahora vayamos a ver el constructor de nuestra clase:

Public Sub New(ByVal host As String, ByVal user As String, ByVal pass As String)
    Me.host = host
    Me.user = user
    Me.pass = pass
End Sub

Simplemente seteamos los valores pasados. Sigamos a cosas más importantes:

Vamos a crear una función que baja un archivo dado:

Public Function bajarArchivo(ByVal fichero As String, ByVal destino As String) As Boolean
    Dim peticionFTP As FtpWebRequest
    Dim localfile As String = destino
    Dim b As Boolean = False

    ' Creamos una peticion FTP con la dirección del fichero que vamos a subir
    peticionFTP = CType(FtpWebRequest.Create(New Uri(fichero)), FtpWebRequest)
    ' Fijamos el usuario y la contraseña de la petición
    peticionFTP.Credentials = New NetworkCredential(user, pass)

    peticionFTP.KeepAlive = False
    peticionFTP.UsePassive = False

    peticionFTP.Method = WebRequestMethods.Ftp.DownloadFile

    Try
        Using response As System.Net.FtpWebResponse = CType(peticionFTP.GetResponse, System.Net.FtpWebResponse)
            Using responseStream As IO.Stream = response.GetResponseStream
                Using fs As New IO.FileStream(localfile, IO.FileMode.Create)
                    Dim buffer(2047) As Byte
                    Dim read As Integer = 0
                    Do
                        read = responseStream.Read(buffer, 0, buffer.Length)
                        fs.Write(buffer, 0, read)
                    Loop Until read = 0

                    responseStream.Close()
                    fs.Flush()
                    fs.Close()
                End Using

                responseStream.Close()
            End Using

            response.Close()
        End Using

         b = True
    Catch ex As Exception
        MsgBox(ex.Message)
    End Try

    Return b
End Function

Esta función recibe dos parámetros: el fichero a bajar (tipo ruta completa, como ftp://host/directorio/archivo.ext), y el lugar donde se guardará (tipo ruta completa, como "C:\Temporal\Archivo.ext").

Primero, creamos una petición FTP vacía, la cual rellenamos con algunos datos, como la ruta, las credenciales (user y pass) y el método (download, en este caso).

El siguiente paso es obtener una respuesta FTP (response) usando nuestra petición (peticionFTP). Si todo va bien, creamos un Stream a partir de esta respuesta.

Si esto ha ido bien, abriremos un archivo mediante un stream. Este archivo será el destino, mientras que el anterior será el origen.

Ahora, haremos un pequeño buffer, en el cual iremos guardando información del origen y escribiéndola en el destino, mientras se pueda.

Cuando tenemos hecho el traspaso de información, vaciamos (flush) los streams y los cerramos.

Si todo ha salido bien, "b" valdrá true; en caso contrario, "b" valdrá false.

Ahora, ya podemos descargar nuestro archivo generado con PHP. Solo tendríamos que pasar los datos necesarios a esta función, y... voliá!

Bien, ahora deberíamos desmenuzar el archivo descargado, para cargar una lista en VB.net. Para ello, supondremos que tenemos un elemento llamado listBox1 y otro llamado listBox2. También doy por supuesto (en este y todos los ejemplos) que todas las funciones están en un módulo aparte. Así, deberíamos hacer algo así:

Bajar archivo
Abrir archivo
Leer archivo
Desmenuzar archivo
Colocarlo en listBox1

Bien, ahora se tendría que hacer lo siguiente (por razones de espacio no lo haré aquí): tomamos todas las funciones anteriores de PHP y las adaptamos a VB.net; de esta manera, ya tendremos la función que nos destripe el archivo.

Así, podemos hacer así:

Dim ftp as new Ftp("mihost.com", "miUser", "David")

ftp.bajarArchivo("BIG_INDEX.dat", "cat.dat")
Form1.ListBox1.Items.AddRange(Module1.getIndexList("cat.dat", vbLf))

Y ya está, tenemos la lista cargada.

Si no sabes de dónde vienen estas funciones, lee este artículo de mi blog.

Ahora viene el siguiente paso: añadir unos handler a nuestras listas para poner o quitar ítems a la lista definitiva. Esto lo haremos así:

Private Sub ListBox1_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles ListBox1.DoubleClick
    Module1.addItemFromList(Me.ListBox1, Me.ListBox2)
End Sub

Private Sub ListBox2_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles ListBox2.DoubleClick
    Module1.addItemFromList(Me.ListBox2, Me.ListBox1)
End Sub

Ahora, la función addItemFromList():

Public Function addItemFromList(ByVal fromL As ListBox, ByVal toL As ListBox) As Boolean
    Try
        toL.Items.Add(fromL.SelectedItem)
        fromL.Items.Remove(fromL.SelectedItem)
    Catch ex As Exception
        Return False
    End Try

    Return True
End Function

Como veis el manejo es simple; ponemos el ítem en la lista de destino y lo quitamos de la lista origen. Por si acaso, hemos añadido un control de errores (try/catch), así como un retorno, por si queremos controlar la ejecución.

Ahora nos falta generar el archivo definitivo donde buscar, obtener las publicaciones adecuadas y responder vía email.

Para ello, debemos investigar un poco más...

Nos dirigimos a paginaweb.es/buscar/X/Y/1.


Allí vemos las publicaciones de la sección "X", subsección "Y", página 1.

Bajo cada publicación, hay una serie de opciones, una de la cual es "responder". Pulso, a ver qué pasa. Me sale otra ventana (pop-up).

Siendo un poco off-topic, comentar que aquella ventana muestra un montón de información privada a USUARIOS NO REGISTRADOS. Entre ellos, destaco el número de teléfono...

Más abajo, veo que hay una opción de "enviar correo". Voy a esa opción... el contenido de la ventana cambia, y ahora me pide que introduzca datos, a saber:
  • Nombre
  • Email
  • Repetir Email
  • Asunto
  • Texto
Pinta bien... así que decido hacer un pequeño test; voy a paginaweb.es y busco cómo hacer una publicación.

Una vez encontrada, hago una en la sección X, subsección Y, y accedo a paginaweb.es/X/Y/1. Ahí está mi publicación.

Vuelvo a andar el camino a enviar el correo, relleno los datos y le doy a enviar...

Al momento me dice que se ha enviado el correo satisfactoriamente, y me llega un email.

Es de paginaweb.es... lo abro, y para mi sorpresa, no es el típico email diciendo "La persona X se ha puesto en contacto contigo a través de paginaweb.es, ve a este enlace..." sino que directamente han enviado un correo a mi dirección con toda la información que he puesto antes...

Perfecto, ¿no?

Vuelvo a la página para enviar correo, y (desde chrome) hago "inspeccionar elemento" (F12). Allí busco el forumlario (form) de envío de correo, y veo que pasa los siguientes inputs:
  • Nombre
  • Email
  • Repetir Email
  • Asunto
  • Texto
  • ID
¿Y ese ID?... es el ID de la publicación. Esto parece que podría detener mi tarea, pero vamos a investigar un poco más.

Vuelvo a paginaweb.es/X/Y/1, y inspecciono el enlace que lleva a los datos personales (que éste lleva a enviar el correo, recordemos). Allí el enlace es una llamada JavaScript, que incluye el ID.

Bien, tenemos un punto de apoyo, de momento.

Abro la ventana de contacto, y busco el enlace de enviar correo... allí reza el siguiente enlace:

paginaweb.es/correo.php?id=XXXXX

Ok... sigamos. El formulario de envío de correo va hacia (action) paginaweb.es/envio.php

Entonces, no tengo más que tomar el ID, añadirlo a los datos POST, y llamar a envio.php... y tendré el correo enviado, de parte de paginaweb.es

El problema es el ID... pero me fijo en una cosa: el ID aparece al lado de cada publicación. ¿Qué? Me han alegrado el día...

Entonces, solo falta establecer un sistema mediante el cual obtener los códigos. Yo establecí la siguiente ruta:

  • Genero el archivo final (con los ítems de listBox2)
  • Obtengo "X" e "Y" del fichero final generado con VB.net
  • Abro un fichero de códigos
  • Por cada ítem del fichero final, obtengo los códigos de las 10 primeras páginas de "X" e "Y"
  • Guardo dichos códigos en el fichero de códigos
  • Voy al siguiente ítem
  • Cuando acabo con los items, voy al fichero de códigos
  • Por cada código, hago una petición HTTP a paginaweb.es/envio.php con los datos necesarios
  • Termina la tarea

Bien, vamos a ver cómo hacer esto:

Para generar el archivo final, la cosa es tan sencilla como escribir los ítems de listbox2:

Public Sub prepareSend(ByVal code As Integer)
    Dim f As String = "BIG_INDEX"

    If code <> 0 Then
        Dim sw As New StreamWriter("BIG_INDEX_AUX.dat", False)

        For i = 0 To Form1.ListBox2.Items.Count - 1
            sw.Write("[" + i.ToString + "] = " + Form1.ListBox2.Items(i).ToString + "\n")
        Next

        sw.Close()
        f = "BIG_INDEX_AUX"
    End If

    Module1.send(f)
End Sub

Esta función recibe un parámetro (code) de entrada: en nuestra llamada, será el número de ítems de listBox2. Así, si es 0, enviaremos a todo BIG_INDEX; en caso contrario, escribiremos en el fichero auxiliar los ítems necesarios.

Finalmente, enviamos el nombre del archivo a "send".

Vamos a ver ahora la función "send":

Public Sub send(ByVal path As String)
    Dim f As New StreamWriter("GLOBAL_CODES.dat", False)

    Module1.getURL(Module1.getIndexList(path), f)
    f.Close()
    Module1.sendMessages()
End Sub

Creamos el archivo de códigos (GLOBAL_CODES), obtenemos las URL (getURL()), cerramos el archivo y enviamos los mensajes (sendMessages()).

Ahora, explicaré cómo funciona getURL, ya que getIndexList ya está visto:

Public Sub getURL(ByVal list() As String, ByVal file As StreamWriter)
    For i = 0 To list.Length - 1
        Module1.setURLList(list(i), file)
    Next
End Sub

Por cada elemento de la lista (retornada por getIndexList()), añadimos en "file" una lista de URL, mediante la función setURLList(), que a continuación vemos:

Public Sub setURLList(ByVal item As String, ByVal file As StreamWriter)
    For i = 0 To 10
        Module1.getItemList("div", Module1.GetHTML("http://www.paginaweb.es/" + item + "/?pagina=" + (i + 1).ToString), file)
    Next
End Sub

Bien, ésta también delega en otras funciones (la lista parece interminable, pero tened paciencia... el código está muy estructurado), pero por contra la explicación es sencilla:

Obtiene el HTML de la página mandada ("http://www.paginaweb.es/item/?pagina=...), el cual será parseado mediante "div", y será guardado en el archivo. Esto lo hace para las 10 primeras páginas del ítem pasado.

La función getHTML la explicaré al final; vayamos ahora a getItemList:

Public Sub getItemList(ByVal item As String, ByVal src As String, ByVal file As StreamWriter)
    Dim list() As String = Module1.reGenere(Split(src, "<"), "<")
    Dim ret(0) As String
    Dim rc As Integer = 0

    For i = 0 To list.Length - 1
        If Module1.isItem(item, list(i)) Then
            ReDim Preserve ret(rc)
            ret(rc) = list(i)
            rc += 1
        End If
    Next

    list = Nothing

    Dim clD() As String = Module1.getContent(Module1.getValue(Module1.getAttribute("class", ret)), "X>")

    For i = 0 To clD.Length - 1
        file.Write(clD(i).Substring(1) + "\n")
    Next
End Sub

Vamos a explicar: primero parte el texto por el carácter "<", y le añade al principio de cada ítem de nuevo el carácter "<" (esta es la función reGenere: añade al principio el carácter que se le pasa).

Seguidamente iteramos sobre nuestra regenerada lista, y por cada elemento comprobamos si es del tipo especificado ("div", en nuestro caso) mediante la función isItem(toCheck, item).

Si es del tipo, lo añadimos a nuestra lista final.

Tras tratar la lista, la borramos (para ahorrar memoria), y nos quedamos con ret (la lista purgada).

Acto seguido, creamos un nuevo array cuyo contenido es el resultado de obtener el texto que hay tras los "div" de "class" "X", mediante las funciones getContent y getAttribute.

Con esta nueva lista, escribimos los códigos, y estamos listos para otro ítem.

No he querido poner la definición de todas las funciones para no cargar demasiado la entrada y para que os estrujéis el cerebro para encontrar la solución :-P

El siguiente paso es el tema de hacer peticiones HTTP. Para ello, haremos una función sobrecargada:


Public Function GetHTML(ByVal strUrl As String) As String

    Dim WR As System.Net.WebRequest
    Dim Rsp As System.Net.WebResponse
    Try
        WR = System.Net.WebRequest.Create(strUrl)
        Rsp = WR.GetResponse()
        Return New StreamReader(Rsp.GetResponseStream()).ReadToEnd()
    Catch ex As System.Net.WebException
        MsgBox(ex.Message)
        Throw ex
    End Try
End Function

Public Function GetHTML(ByVal strUrl As String, ByVal post() As String) As String

    Dim HttpWRequest As System.Net.WebRequest
    Dim data As StringBuilder = New StringBuilder()
    Dim byteData() As Byte
    HttpWRequest = System.Net.WebRequest.Create(strUrl)
    HttpWRequest.Method = "POST"
    HttpWRequest.ContentType = "application/x-www-form-urlencoded"
    HttpWRequest.Credentials = CredentialCache.DefaultCredentials
    data.Append(String.Join("&", post))
    byteData = UTF8Encoding.UTF8.GetBytes(data.ToString())
    HttpWRequest.ContentLength = byteData.Length
    HttpWRequest.GetRequestStream().Write(byteData, 0, byteData.Length)

    Return New StreamReader(HttpWRequest.GetResponse().GetResponseStream()).ReadToEnd()
End Function
 
Podemos llamarla como GetHTML(url) o bien como GetHTML(url, post). Vamos a explicarlas por separado:

La primera crea una petición web (WebRequest), y una respuesta (WebResponse). Acto seguido crea la petición con la URL pasada, e intenta obtener una respuesta.

Si todo ha salido bien, retornaremos todo el HTML leído en forma de String. En el caso que algo falle, nos avisará.

La segunda función crea una petición (HttpWRequest), un StringBuilder, y un array de Byte.

El siguiente paso es crear la petición con la URL pasada, y además, le añadimos algo de información de cabecera, como el método (POST), el tipo de contenido, las credenciales...

Ahora vamos a formatear los datos POST: juntamos los parámetros del array post() (tienen que estar en formato v=p) con el carácter "&". Seguidamente lo pasamos todo a UTF8.

Ahora, lo añadimos a HttpWRequest, y devolvemos, como antes, un String con todo el HTML resultante de la petición.

Bien, ahora solo falta una cosa: leer los códigos y emular el mensaje... vamos allá:


Public Sub sendMessages(Optional ByVal dir As String = "")

        Dim d As String = "http://www.paginaweb.es/enviado.php"

        If dir <> "" Then
            d = dir
        End If

        Dim f As New StreamWriter("GLOBAL_LOG.dat", False)
        fwrite(f, "EMPIEZA MAILS" + vbLf + vbLf)

        Dim data() As String = Module1.getData()
        Dim codes() As String = Split(Module1.getFContent("GLOBAL_CODES"), "\n")
        Dim max As Integer = codes.Length
        Dim id As Integer = 5

        data(0) = "nombre=" + System.Web.HttpUtility.UrlEncode(data(0).Trim)
        data(4) = "repemail=" + System.Web.HttpUtility.UrlEncode(data(1).Trim)
        data(1) = "email=" + System.Web.HttpUtility.UrlEncode(data(1).Trim)
        data(2) = "asunto=" + System.Web.HttpUtility.UrlEncode(data(2).Trim)
        data(3) = "mensaje=" + System.Web.HttpUtility.UrlEncode(data(3).Trim)

        fwrite(f, "DATOS OBTENIDOS:" + vbLf)
        fwrite(f, "Nombre: " + data(0) + vbLf)
        fwrite(f, "Email: " + data(1) + ", (rep: " + data(2) + ")" + vbLf)
        fwrite(f, "Asunto: " + data(3) + vbLf)
        fwrite(f, "Mensaje [inicio]:" + data(4) + vbLf)
        fwrite(f, ":[fin]" + vbLf + vbLf + vbLf)
        fwrite(f, "____________________" + vbLf + vbLf + vbLf)
        fwrite(f, "OBTENIENDO CÓDIGOS DE FICHERO GLOBAL_CODES.DAT:" + vbLf + vbLf)
        fwrite(f, "ENCONTRADOS " + max.ToString + " CÓDIGOS:" + vbLf + vbLf)

        For i = 0 To max - 1
            Dim h As New StreamWriter("codes(i) + "_" + i.ToString + ".html", False)
            data(id) = "id=" + System.Web.HttpUtility.UrlEncode(codes(i))
            fwrite(f, "Mensaje " + i.ToString + " de " + max.ToString + ":" + vbLf)
            fwrite(f, "Código: " + codes(i) + vbLf)

            h.Close()
        Next

        fwrite(f, "FINALIZA ENVÍO DE MENSAJES" + vbLf)
        f.Close()
        MsgBox("Tarea Terminada")
        Form1.ListBox2.Items.Clear()
    End Sub


Vamos a ver qué hace esto:

Primero definimos la ruta; si "dir" contiene texto, ésta será la URL que usaremos, sino, usamos la de "por defecto".

Seguidamente, creamos un pequeño log, y escribimos en él (con una función definida fwrite) una cabecera.

Usando el fichero de datos de email (con formato conocido), usamos el método getData para obtener los datos (el método getData accede al fichero de datos de email y lo "parsea" siguiendo el formato establecido [clave] = valor).

El siguiente paso es obtener un array con los códigos de los cuales responderemos. Para ello usamos getFContent (función definida, similar a file_get_contents() de PHP), y sobre este String partimos a partir de cada "\n". Obtenemos también la longitud del array resultante.

Creamos, además, un índice auxiliar con valor 5, que usaremos después para los datos POST. El siguiente paso es ordenar los datos POST dentro de "data".

Escribimos algo más en el LOG, y empezamos a iterar el array de códigos.

Para cada elemento, abrimos un StreamWriter. Usamos el índice 5 para poner el último dato POST (el id del elemento).

Escribimos algo más de info. en el LOG, y en el fichero abierto volcamos el HTML obtenido de la llamada (para saber si ha salido bien).

Finalmente, escribimos que hemos terminado, y mostramos un mensaje por pantalla.

Y esto es todo respecto el encargo; así es como aproveché la pequeña brecha de la página web...

En la próxima entrega, un poco más de investigación (¡parte III ya!), en la que descubrí que podía usar este método en combinación con otro para agrandar la brecha de seguridad...

La tercera parte sera en JAVA, y usaremos (mucho) el tema de Thread...

Como siempre, os espero...

¡Hasta la próxima!

Aventuras de DoHITB: parte II (b)

Buenas!

Seguimos con la parte técnica del proyecto en el que estuve metido...

En la anterior entrada definimos unas cuantas funciones:


  • getContent(String $url)
  • getMarkList(String $fgc, String $token)
  • createBigIndex(Array $list)

Y justo acabó la entrada con nuestro proyecto en un "checkpoint" en el que teníamos las rutas de todos los archivos comprimidos.

El siguiente paso es obtener dichos archivos, descomprimirlos y procesarlos de manera similar al fichero anterior, para obtener todas las categorías.

Para ello, partimos del fichero en el que guardamos los enlaces.

Haremos una función que lea el fichero, y retorne en un array los enlaces; además, trataremos el fichero de manera que toda línea que empiece por "#" se tome como comentario (es decir, no se procesa).

function getIndexList($path){
$ret = array();
$content = explode("\n", getContent($path));
for($i=0;$i<count($content);$i++){
$aux = explode("#", $content[$i]);
if(count($aux) == 1){
$aux = explode(" = ", $content[$i]);
if(count($aux) == 2)
$ret[] = trim($aux[1]);
}
}
return $ret;
}

Explicaré cómo funciona este código:

Primero obtenemos el contenido de $path, y lo partimos por los saltos de línea ("\n"). Seguidamente, iteramos el array obtenido, y creamos un arreglo auxilar ($aux), resultado de partir la línea actual por el carácter "#". Si dicho arreglo es una única pieza (es decir, no contiene "#"), se tratará la línea; en caso contrario lo tomaremos como comentario y seguiremos.

En el caso que tratemos la línea, la separamos por el "=", y guardamos en el array de resultados el segundo índice del array resultante.

Al final del archivo, retornamos el array con las direcciones.

Es decir, si tenemos esta línea:

[1] = paginaweb.es/buscar/Hola

Al partirla por "=", obtendremos:

array("[1]", " paginaweb.es/buscar/Hola")

Con la función trim(), eliminamos todos los espacios y saltos de línea al principio y fin del String que le pasamos (si os fijáis, hay un espacio en blanco después del =, que llega al array).


Ahora tenemos la lista de elementos (comprimidos) que debemos obtener.

Para obtenerlos, tenemos que hacer exactamente lo mismo que hicimos con el índice general, solo que añadiremos una cláusula a la función get_file_contents(), de manera que, arreglando nuestra función getContent(), podríamos hacer:

function getContent($url, $isCompressed = false){
        $extra = "";

        if($isCompressed)
                $extra = "compress.zlib://";

        return file_get_contents($extra.$url);
}

Hemos añadido un parámetro nuevo ($isCompressed), que por defecto sera false. De esta manera, para archivos no comprimidos, llamaremos a la función así:

getContent($url) ó getContent($url, false)

Ya que hemos establecido que por defecto $isCompressed será false (y por tanto, siempre que sea "false" podemos no indicarlo).

Para un archivo comprimido, enviaríamos:

getContent($url, true)

Y la función añadiría la cláusula "compress.zlib://", para indicar que es un fichero comprimido.

Ahora, solo haría falta obtener con getMarkList() la lista de "<loc>"... pero aquí me surgió un problema:

Resulta que estos archivos comprimidos no tenían saltos de línea, por lo que no tuve más remedio que modificar la función getMarkList(), para que, dependiendo de la ocasión, partiera por los saltos de línea ("\n") o por la apertura de marca ("<").

De esta manera, nuestra función getMarkList quedará así:

function getMarkList($fgc, $token, $pre = false){
$t = "\n";
$e = "<";
if($pre){
$t = "<";
$e = "";
}
$splitted = explode($t, $fgc);
$ret = array();
for($i=0;$i<count($splitted);$i++){
$aux = explode($e.$token.">", $splitted[$i]);
if(count($aux) == 2){
$aux = explode($e."/".$token.">", $aux[1]);
$ret[] = $aux[0];
}
}
return $ret;
}

De manera que añadimos un nuevo parámetro ($pre), para indicar si hay o no salto de línea.

En caso que $pre sea omitido o false (nuestro primer caso, que quedaría con la misma sintaxis) se procedería de la misma manera. En caso contrario, se cambiarían las variables $t y $e por "<" y "", respectivamente.

Luego, el sistema sería el mismo, solo que el contenido de "explode" sería generado dinámicamente usando las variables $t y $e.

Para nuestros archivos comprimidos, llamaríamos así a la función:

getMarkList($fgc, 'loc', true);

Pero nos estamos adelantando... volvamos hacia atrás:

De momento tenemos seguro el método de obtener los archivos que debemos descomprimir y analizar, devueltos como un array. Para procesar el array y almacenar las categorías tenemos que descomprimir cada archivo (lo hemos visto), trocearlo (también visto), analizarlo, e insertar las categorías en un archivo.

Vamos pues, a ver cómo analizar el archivo descomprimido, y en forma de array:

Para ello, lo que hice fue mostrar por pantalla el array que me llegaba, y vi lo siguiente:
  • Algunas posiciones del array venían vacías, por lo que tendría que "limpiar" el array antes de procesarlo
  • Las URL que me interesaban tenían esta forma: "texto/texto/texto/texto/texto"
  • Dentro de esta forma, las "URL buenas" (que llevaban al índice de una categoría", tenían la última parte vacía ("texto/texto/texto/texto/")
  • Dentro de estas "URL buenas", me interesaba el índice 3 (es decir, "texto/texto/texto/texto/texto")
Después de este análisis, podemos ver la necesidad de dos funciones: una que limpie, y otra que analice. Vamos primero con la de limpiar:

function clean($list){
$ret = array();
for($i=0;$i<count($list);$i++)
if($list[$i] != "")
$ret[] = $list[$i];
return $ret;
}

El funcionamiento es de lo más sencillo: recibimos un array, lo iteramos; si el índice revisado tiene texto, lo agregamos a un nuevo array, que devolveremos al final.

Ahora veremos cómo analizar la línea actual, siguiendo el patrón anteriormente descrito:

function getCat($item){
$pieces = explode("/", $item);
if(count($pieces) == 5)
if($pieces[4] == "")
return $pieces[3];
return "";
}

Partimos la línea por "/", y si tiene 5 piezas y la pieza de índice 4 está vacía, retornamos la pieza de índice 3.
En caso contrario, devolvemos un String vacío.

Ahora, solo nos falta una función que vaya escribiendo en un archivo las categorías. Para ello, haremos tres funciones:

La primera recibirá un parámetro (array), que será la lista de archivos que debe descomprimir y analizar.
Abrirá un archivo global de categorías, iterará el array que le llega como parámetro para analizarlo, y finalmente cerrará el archivo.

function setSitemapFromList($list){
$m=0;
$f = fopen("categorias.dat", "w");
for($i=0;$i<count($list);$i++)
$m=setSitemap($list[$i],$f,$m);
fclose($f);
}

Explicamos un poco la función: creamos un índice $m, y un acceso a archivo $f.
Luego, iteramos sobre $list, y por cada archivo a procesar (setSitemap()), recogeremos el nuevo resultado de $m (ya veremos para qué sirve). Finalmente cerramos $f.

Vamos con la segunda función; setSitemap():

function setSitemap($toFill, $f, $i){
return setCategoriesFromList(getMarkList(getContent($toFill, true), 'loc', true), $f, $i);

Como vemos, es un simple puente, que hace lo siguiente:
  • Obtiene el contenido del archivo ($toFill), indicando que es comprimido.
  • Obtiene, del resultado anterior, todos los "loc", indicando que parta el contenido por "<"
  • Llama a la tercera función, setCategoriesFromList(), usando el resultado anterior, el fichero $f y el entero $i.
Ahora veamos la tercera función, setCategoriesFromList():

function setCategoriesFromList($list, $f, $m){
$clean = clean($list);
for($i=0;$i<count($clean);$i++)
if(($gCat = getCat($clean[$i])) != "")
fwrite($f, "[".$i."] = ".$gCat."\n");
return ($m+$i);
}

Esta función recibe una lista, un fichero y un enteros.

Primero limpia la lista (clean()), luego la itera, y si la línea actual contiene una categoría (llamando a getCat() sabemos si hay categoría porque devolverá algo diferente a "") la escribimos en el fichero, con el formato de siempre:

[N] = Categoría

Finalmente devolvemos el índice actual, para preservar la cuenta en todo momento (que será el nuevo valor de $m en la primera función).

De esta manera, para obtener el fichero de categorías, debemos llamar a la siguiente función:

setSitemapFromList(getIndexList("BIG_INDEX.dat"));

Esta entrada y la anterior os pueden servir para tratar ficheros con estructuras simples.

En la siguiente entrada, veremos cómo conseguir el archivo de categorías, y cómo crearlo a partir de un programa hecho con vb.NET

El código útil abarcará los siguientes dos temas:
  • Envío de peticiones HTTP (con GET, con POST, y simples)
  • Envío de peticiones FTP
Una vez visto esto, tendremos un .exe capaz de cargar las categorías de paginaweb.es. Lo siguiente será generar la lista de categorías en modo visual, y dejar que el usuario seleccione las que quiera. A partir de esa lista generaremos un nuevo archivo, que será la lista de categorías en la que hemos de buscar las publicaciones a responder.

El cómo conseguir las publicaciones lo veremos junto a cómo enviar el correo electrónico (veremos primero cómo enviar el correo electrónico).

Cuando finalicemos esto, os contaré otra pequeña investigación que hice, y cómo ligarla a la anterior. Además, veremos código útil que nos serviría para ahondar en la brecha de paginaweb.es

Pero iremos con la calma.

Saludos, y 

¡Hasta la próxima!

Aventuras de DoHITB: parte II

Buenas!

Seguimos con mis pequeñas aventuras...

Hoy toca explicar un poco de qué iba el encargo que me mandaron, y como me las arreglé para hacerlo.

Al explicar cómo me las apañé, veremos código y algoritmos que nos podrán servir para otros proyectos.

Empecemos:
  • Descripción del encargo
Acceder a una web (la llamaré paginaweb.es), especializada en, digamos, intercambios (no penséis mal ahora! No son exactamente intercambios, pero si digo exactamente lo que es, se podría averiguar muy rápido qué web es...), y contestar a una selección de publicaciones (es decir, aplicando un filtro).

Es, por así decirlo, una especie de contestador automático (como el AT-5000 de los simpson jajajaja), pero para una web en exclusivo y para una pequeña porción de esta.

Yo, desde luego, solo me encargué de prepararlo todo; el contenido del mensaje lo dejé "configurable" para que quien me pidió el encargo pusiera lo que creyera conveniente.

Bien, se puede advertir que no es del todo "limpio" este tema, como ya comenté en la entrada anterior (de todas maneras, como "excusa", puedo decir que esa web tiene ya mucho spam, y por un poco más...)

Entonces, veamos un pequeño algoritmo (muy general) de lo que tenía que hace mi programa:

Configurar correo
Buscar publicaciones de las secciones indicadas
Enviar los correos (se envían desde la misma web)

La parte de configurar el correo es simple:

  • En PHP: se hace un form con los campos necesarios (en este caso: email de quien lo envía, asunto, texto). Al hacer submit del form, generamos un archivo de texto.
  • En vb.net: hacemos un button en el form (formulario, ventana) principal; al pulsarlo saldrá una nueva ventana con (de nuevo) los campos necesarios. Al validar el formulario, generamos un archivo de texto.
Modelo de archivo de texto: _GLOBAL_DATA.dat

[mail] = direccion_de_correo@direccion.com
[asunto] = asunto
[texto] = texto a enviar.| Sustituimos (para mejor gestión) los saltos de linea por pipelines ("|").|A la hora de recoger el texto, cambiamos las pipelines por saltos de línea.||Fin.

Ahora viene una parte que debemos solventar: ¿Cómo se qué publicaciones son las indicadas?

Pues con este interrogante me quedé... así que decidí investigar.

Lo primero es inspeccionar la página visualmente. Así que entro a www.paginaweb.es, y veo que hay un buscador por secciones

Busco, usando la primera sección, y me lleva a una página similar a la principal, con las publicaciones de esa sección. Para mi sorpresa veo que hay subsecciones (secciones dentro de secciones).

Entro a la primera subsección, y veo que no hay un tercer nivel. Bien... el problema es que hay varias páginas

Para más inri, veo que la web usa URL amigables, es decir, en vez de mostrar los parámetros de la URL los enmascara. Un ejemplo:

  • Sin URL amigables: http://paginaweb.es/buscar.php?seccion=X&subseccion=Y&pagina=1
  • Con URL amigables: http://paginaweb.es/buscar/X/Y/1
Se llaman "amigables" porque son más fáciles de recordar, y más bonitas visualmente (se ve más limpio).

Las URL amigables se consiguen moficando el archivo ".htaccess" del servidor (en futuras entradas tengo previsto una entrada para este tema)


Por curiosidad decido probar la página, y escribo directamente una URL que sé que no existirá, como:

http://www.paginaweb.es/buscar/qwertyui/holahola/999

No creo que exista la sección "qwertyui", subsección "holahola", página "999"... así que debería dar error. Y como era de esperar, la web me dice que la página no existe.

Hemos llegado a un "checkpoint" en nuestra pequeña investigación. De momento sabemos:

  • Tiene URL amigables
  • Se compone de secciones, subsecciones y páginas
  • Tiene protección contra errores

El siguiente paso es... ¿De donde saco las categorías? Podría copiarlas a mano mirando de la lista, pero... ¿Y si la actualizan? Entonces tendré que volver a copiar la lista... e ir revisando que la lista esté actualizada.

Por suerte (o por desgracia, según se mire), hay dos archivos con útil información que si están (y es recomendable que estén) han de ser públicos, es decir, que cualquiera los puede ver. Éstos son:
  • El mapa web
  • El archivo de robots 
El primer archivo es un archivo, generalmente con extensión "xml", que contiene una lista estructurada de los archivos que componen una web. Su estructura se basa en la siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc></loc>
    <lastmod></lastmod>
    <changefreq></changefreq>
    <priority></priority>
  </url>
</urlset>

Las dos primeras líneas son siempre las mismas: la primera indica la versión de XML y el charset (conjunto de caracteres) a usar (en este caso, UTF-8)

Después, hay una serie (aunque aquí solo haya uno) de nodos "url". Cada uno de estos nodos tiene información de una página web, a saber:

  • loc: la dirección web (http://www.paginaweb.com/buscar, por ejemplo)
  • lastmod: fecha de la última modificación de la página en cuestión
  • changefreq: aquí indicamos cada cuanto se actualiza la página: "daily" (cada día), "monthly" (cada mes)...
  • priority: le damos una importancia entre 0 y 1 (con un decimal) a la página, con respecto a las otras. Las páginas de prio. 1 tendrán mas prioridad que las que tengan 0.9



Y, ¿para qué se usa? Este archivo es usado por los buscadores (google, bing, yahoo) para insertarlos en sus bases de datos, y que cuando busquemos, nos salgan los resultados.

El otro archivo es el archivo de robots.

Éste siempre se tiene que llamar igual: robots.txt.

Sirve para que los robots (los "agentes" que rastrean las webs y los mapas webs) sepan qué páginas tienen que mirar y cuales no.

La estructura básica de este archivo es la siguiente:

User-agent: (nombre)
Disallow/Allow: página web

User-agent es el agente de búsqueda, por Internet están los nombres, aunque se suele aludir a todos a la vez con "*".

Bajo el User-agent indicado, permitiremos (Allow) o no (Disallow) que se mire la web, por ejemplo:

User-agent: *
Disallow: /pagina-secreta
Allow: /buscar

Se pueden poner varios User-agent si se quiere, para hacer más específico el archivo.

Ahora bien (al margen de todo), si tenemos deshabilitada la indexación de una página en concreto e intentamos entrar, ¿qué? Pues pasarán dos cosas:
  • Si la web ha estado bien programada: obtendremos un error, o una redirección
  • Si la web tiene un error: quien sabe lo que puede pasar... página en blanco, una página con errores...
Pero esto aún no lo trataremos (quizás lo vemos más adelante)

Volviendo a la investigación: fui a buscar el mapa web, para ver qué encontraba. Para encontrarlo hay dos vías:
  • Accedemos a robots.txt y buscamos ".xml", a ver si hay suerte
  • Usando un poco de sentido común vamos probando suerte
A mi me tocó usar la segunda... así que fui probando:
  • paginaweb.es/mapa.xml
  • paginaweb.es/mapaweb.xml
  • paginaweb.es/webmap.xml
  • paginaweb.es/sitemap.xml
Bingo! era sitemap.xml

Para mi sorpresa, veo que el mapa web tiene referencias a otros mapas web, que además están comprimidos con gzip (.gz)

Bueno, vamos avanzando... por suerte, PHP tiene soporte para comprimir y descomprimir archivos con gzip.

Pero antes de ponerse a ello, hay que ver si de verdad servirán los archivos comprimidos.

Copio la ruta de un <loc>, y se me descarga un archivo; lo descomprimo con winRAR, y veo que, efectivamente, cada archivo tiene una buena cantidad de <loc>, y que, a parte de tener las categorías, tiene las URL de TODAS las publicaciones de la web (cerca de 1.000.000.000)... guardar todas sería demasiado para PHP (en cuanto a memoria y tiempo), así que tendré que hacer algo para guardar solo las subcategorías (así las puedo poner en una lista)

Bien, de momento la investigación está completada: tengo la manera de acceder a las categorías... ahora falta averiguar la manera de sacarlas...

Para dar el máximo de código útil, haremos lo siguiente:
  • Obtener el mapa web general [PHP]
  • Obtener los mapas web referidos [PHP]
  • Descomprimirlos y guardarlos en nuestro servidor [PHP]
  • Descargar los archivos vía FTP [vb.NET]
  • Desmenuzar los archivos descargados [vb.NET]
  • Guardarlos en un archivo [vb.NET]
Entonces, todo empezará con un archivo .exe generado con vb.NET, al cual, al pulsar un button, hará todo eso.

Vayamos por partes:

Para obtener de una manera rápida el contenido de un documento (sea cual sea - fichero o web), en PHP tenemos la función file_get_contents()

Podemos definir, pues la siguiente función:

function getContent($url){
  return file_get_contents($url);
}

Esta función recibe un parámetro ($url) del que extraeremos el contenido. Puede parecer absurda esta función, pero después veremos que la podemos mejorar un poco...

El siguiente paso sería obtener los sitiemap referidos. Para ello tendremos que desmenuzar el archivo que acabamos de leer.

Lo más sencillo para desmenuzar es ir por pasos. Primero pasaremos el contenido del fichero a un array, con la función explode() de PHP.

function getMarkList($fgc){
    $array = explode("\n", $fgc);
    return $array;
}

Explode funciona de la siguiente manera: recibe dos parámetros. El primero se usa como delimitador, y el segundo es la cadena a partir. De esta manera, por ejemplo, si yo hago:

$array = explode("a", "Hola me llamo David");

El resultado sería:

$array[0] = "Hol", $array[1] = " me ll", $array[2] = "mo D", $array[3] = "vid".

Como veis, el delimitador ("a", en este caso) se pierde, por eso luego iteramos sobre el array y le añadimos una "a" al principio.

Además, nosotros buscamos las etiquetas "loc", por lo que podemos mejorar la función "getMarkList()"

function getMarkList($fgc, $token){
    $array = explode("\n", $fgc);
    $ret = array();

    for($i=0;$i<count($array);$i++){
        $aux = explode("<".$token.">", $array[$i]);

        if(count($aux) == 2){
            $aux = explode("</".$token.">", $aux[1];
            $ret[] = $aux[0];
        } 
    }

    return $ret;
}

Veamos: en $array guardamos lo mismo que antes. Luego iteramos sobre $array, y creamos un arreglo temporal ($aux). Éste será el resultado de hacer explode de "<$token>" sobre el elemento que estamos tratando.

Si este array ($aux) tiene 2 elementos, partimos el segundo elemento (índice 1) por el elemento "</$token>", y de este resultado agregamos el primer elemento (índice 0) al array que retornaremos.

Ahora, un ejemplo visual de porqué esta estructura:

Supongamos el siguiente $fgc

"<uno>
  <dos>
    <tres></tres>
  </dos>
  <tres></tres>
</uno>"

Al hacer explode, tendríamos:

$array = ("<uno>", "<dos>", "<tres>c1</tres>", "</dos>", "<tres>c2</tres>", "</uno>")

Y, supongamos que queremos el contenido de los elementos "tres", pues haríamos:

getMarkList($fgc, 'tres');

Al ir por los elementos de $array, los índices 0, 1, 3, y 5 no pasarían del primer "if", ya que al partirlo por "<tres>" no obtendríamos un array de dos posiciones.

Por otra parte, los índices 2 y 4 sí pasarían, quedando así:

$aux = array("<tres>" "c1</tres>")

Ahora, al el índice 1 por "</tres>", obtenemos:

$aux = array("c1", "</tres>")

Siendo $aux[0] lo que buscamos.

Ahora que tenemos la manera de obtener los "loc" del mapa web global, los guardaremos en un archivo. Para ello, usaremos las funciones fopen y fwrite de PHP.

function createBigIndex($list){
    $f = fopen("BIG_INDEX.dat", "w");

    for($i=0;$i<count($list);$i++)
        fwrite($f, "[".$i."] = ".$list[$i]."\n");

    fclose($f);
}

y ya tendremos nuestro archivo. Esta función itera sobre una lista, y la escribe con el siguiente patrón:

[0] = www....
[1] = www...

Bien, elaboremos ahora una llamada que cree el archivo grande:

createBigIndex(getMarkList(getContent("www.paginaweb.com/sitemap.xml"), 'loc'))

Aquí está todo en una sola llamada, para ahorrar espacio, pero vamos a desmenuzarlo:

Primero llamamos a getContent, y el resultado (una lista) pasa a getMarkList, con 'loc'. Este resultado pasará a ser el contenido del fichero.

Como esta entrada está quedando muy grande, seguiré en la próxima.

Veremos cómo apañárselas para obtener archivos y descomprimirlos; y usaremos algunas funciones que ya tenemos para obtener el listado de categorías.

A partir de ahí será cosa de vb.NET, el acceder vía FTP al archivo y generar una lista.

Las dos otras partes (conseguir las publicaciones y enviar los correos) los iremos viendo.

¡Hasta la próxima!

Aventuras de DoHITB: parte I

Buenas!

Como os dije, voy a explicaros la historieta de cómo una brecha tonta de seguridad puede arruinar una web entera.

No esperéis ataques a servidores, shellcodes, cosas rollo "matrix", ni nada por el estilo...

Tampoco es algo del tipo "scriptkiddie"... es simplemente aprovechar una pequeña apertura que permite (más bien dicho, permitiría) obtener la contraseña de un usuario en cierta web (no publicaré datos relevantes).

Voy a intentar seguir la siguiente estructura:


  • Contar la historia
  • Cómo encontré la brecha
  • Ampliar el efecto de la brecha
  • Arreglar la brecha
    • Posible solución
    • Inconvenientes de esta solución
El último punto se repetirá varias veces. Es decir, plantearé una posible solución, y sus inconvenientes; y así hasta dar con una buena.


Ya lo iremos viendo poco a poco... empecemos por el principio.

Todo empezó con un encargo de un tipo; un encargo que no parecía del todo trigo limpio; pero al fin y al cabo, pensé "es rápido de hacer, y me ofrecen algo de dinero"... pues duro con ello, ¿no?

Me puse con la cosa en cuestión; en unas 2 horas tenía la base de mi proyecto creada. Todo parecía correcto.

Una hora más, y tengo el proyecto completado. Le envío un correo a quien me lo pidió, y le envío un .rar con archivos php.

El contacto me responde, diciendo que cómo ejecuta el PHP.

Aquí ya me extraño un poco, así que lo agrego al chat de Outlook, y le comento:

 - Sube las 2 carpetas (en total son dos carpetas, una con 4 archivos .dat y otra con 3 archivos .php) tal cual a algún servidor suyo y que acceda por navegador.

 - Lo he hecho, y no me funciona

Me puse a revisar, y claro, al tener un "set_time_limit(0)" daba un error, ya que el hosting no permitía ese cambio; sin embargo en local (en mi localhost con wampp) si que podía...

 - Vale, en localhost funciona

 - ¿Cómo hago eso?

 - Bájate el wampp (link) y lo instalas; es siguiente, siguiente, siguiente...

Al cabo del rato me dice que lo tiene pero que no le funciona.

 - Mira el icono de la "w"; es verde?

 - Está naranja

Hablando y comprobando el "services.msc" logré ver que (por el motivo que fuera) le faltaba un servicio (el apache).

Seguí indagando, y resulta que tenía un pc x64, y bajó la verisón x86 (la que yo le pasé...)

Me cansé de tontunas e hice la aplicación con vb.net

 - Te la mando en .exe, será más fácil. El funcionamiento es el mismo

 - No me funciona

En esos momentos estaba ya maldiciendo su estampa... así que probé yo desde mi pc la aplicación, usando como referencia sus datos; y si, funcionaba (desde la primera entrega que funcionaba...)

Pero bueno, arreglo un par de cosas, pongo un botón de test y reenvío

 - Sigue sin funcionar

Ya no pude más... aunque se que funciona, lo envié a los cerros de Úbeda, y me puse a maquinar un plan para tocarle un poco la moral (de este plan me surgió una idea para una App, así que...)

Y así me hallo ahora mismo; con la aplicación entregada y sin cobrar... pero en fin, no me costó demasiado, y, por otra parte, obtuve varias ideas para aplicaciones; así que una cosa por la otra...


Por ahora lo dejamos en suspense, hasta la próxima entrada...

La próxima os contaré algunos detalles de la entrega (en qué consistía), y esto desembocará en cómo encontré la pequeña brecha, cómo la aproveché (veremos algo de código útil), y qué mecanismo usé para ensancharla (también tendremos aquí código útil, pero esta parte será en una tercera entrada).

Finalmente, en una cuarta parte, explicaré mecanismos que se podrían usar para cerrar esta brecha.

Saludos, y

¡Hasta la próxima!