Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
lunes, 30 de abril de 2007
Ya comentaba en otros posts (como este y este otro) qué eran los GUID (Globally Unique IDentifiers), y lo buenos que podían resultar como identificadores de elementos de forma única y universal.

Sin embargo, su utilidad se vería reducida en la práctica si su creación fuera compleja. Pero nada más lejos de la realidad, la obtención de un Guid es extremadamente sencilla:


// C#:
Guid g = Guid.NewGuid(); // Obtiene un nuevo GUID
Console.WriteLine(g); // Lo muestra por consola



' Visual Basic .Net:
Dim g as Guid = Guid.NewGuid() ' Obtiene un nuevo GUID
Console.WriteLine(g) ' Lo muestra por consola



Podemos observar que la clase (estructura) Guid (incluida en el namespace System) contiene un método estático que nos permite generar y obtener objetos de este tipo de forma directa. Además, recordemos que cada vez que generamos un GUID tenemos la seguridad casi absoluta de que será único, lo que hace que podamos utilizarlo directamente como identificador del elemento deseado.
domingo, 22 de abril de 2007
Hoy me voy a salir un poco de la temática habitual, relacionada con el mundo del desarrollo, para comentar una técnica de escaneo de puertos que me ha llamado mucho la atención por el ingenio que derrochó su inventor, Salvatore antirez Sanfilippo, en el año 1998.

Se trata de idle scan, una ocurrente forma para detectar los puertos abiertos en una máquina remota sin poner al descubierto al atacante, es decir, al equipo que realiza el escaneo. Para ello, se vale de una máquina intermedia, llamada zombie o dumb, que ejerce como intermediario en la comunicación y hace que en ningún caso la víctima reciba paquetes directamente desde el atacante, quedando éste en el más absoluto anonimato.

Bueno, he de decir que si no tienes claro el funcionamiento del protocolo TCP y el establecimiento de conexiones, es probable que debas pegar un repaso antes de seguir leyendo el post. En todo caso será una lectura aconsejable para todo humano interesado en saber qué está ocurriendo por debajo cuando estamos utilizando servicios en una red como Internet.

Ahora vamos al lío. La cuestión es que todo intrépido pirata sabe que antes de iniciar el ataque a una ciudad costera es conveniente ver los puertos en los que se puede atracar para hacer el desembarco, ¿no? Pues en Internet ocurre lo mismo, un puerto abierto en un equipo conectado a la red es siempre una posible vía de entrada al mismo; indica que hay una aplicación escuchando en la máquina, y habitualmente puede averiguarse cuál es y explotar sus debilidades.

Por tanto, un ataque tipo debería ir precedido de un escaneo de los puertos abiertos, es decir, recorrer los 65535 puertos posibles (o al menos el subconjunto de uso más habitual) a ver cuáles están en uso. La pega es que esto suele ser demasiado ruidoso, no son pocos los sistemas de detección de intrusos y filtros que detectan peticiones sucesivas desde una misma dirección y las clasifican de inmediato como sospechosas pudiendo llegar a banear (prohibir) la conexión desde la IP que está haciendo el barrido, o incluso a registrar la dirección para más adelante poder tomar medidas legales si procede.

Esta es la razón que hacen de Idle Scan una técnica interesante, puesto que, como he comentado antes, en ningún momento el atacado es consciente de la dirección del atacante.

Para ello se aprovecha, en primer lugar, el funcionamiento del three way handshake, el protocolo estándar utilizado para el establecimiento de conexiones TCP, donde de forma habitual:
  • El procedimiento se inicia cuando el cliente envía un paquete SYN al servidor. Si es posible realizar una conexión, éste responde con un SYN + ACK, y el cliente debe confirmar enviando de nuevo un ACK al servidor. En caso contrario, es decir, si no es posible realizar la conexión porque el puerto esté cerrado, el servidor responde con un RST y se da por finalizada la secuencia.
  • Si un host, sin haberlo solicitado previamente, recibe un paquete de confirmación de conexión SYN+ACK de otro, responde con un RST con objeto de informarle de que no va a establecerse conexión alguna.
  • Si un host, sin haberlo solicitado previamente, recibe un paquete de reseteo (RST), lo ignora.
En segundo lugar, y es la parte importante, Idle Scan utiliza una característica de determinadas implementaciones de la pila TCP/IP, la numeración de paquetes salientes IP de forma consecutiva, principalmente con objeto de que, en el caso de que deban ser fragmentados, el destinatario pueda determinar a qué paquete pertenece cada fragmento. Al número identificativo de un paquete se le conoce como IPID.

Para detectar si un puerto está abierto o cerrado, es necesario primero observar el IPID del zombie, enviar paquetes a la víctima haciéndole ver que realmente se los está enviando éste y, posteriormente, observar de nuevo el IPID utilizado por el incauto intermediario. En función de los valores iniciales y finales obtenidos, se puede inferir el estado del puerto destino.

A continuación se exponen dos escenarios distintos de escaneo; en el primero de ellos se muestra lo que ocurre cuando el puerto objeto de la detección está abierto, mientras que en el segundo se supone que está cerrado.

Escenario 1: Víctima con el puerto abierto

El primer paso es enviar al zombie un paquete SYN+ACK, con objeto de que éste nos devuelva el paquete RST correspondiente, del cual tomaremos el IPID.

Acto seguido, se realiza una solicitud de conexión a la víctima, previa manipulación del paquete para que sea el zombie el que figure como origen del mismo. Al recibirlo, dado que estamos asumiendo que el puerto está abierto (escenario 1), la víctima envía de vuelta la confirmación de la conexión al que cree que es el solicitante, el zombie.

El zombie recibe la confirmación de la conexión, pero como no es él el que la ha generado, responde a la víctima con una señal de reseteo (RST), incrementando su IPID.

De nuevo, pasado unos segundos, desde el atacante se vuelve a obtener el IPID del zombie de la misma forma que al comienzo, comprobando que ha sido incrementado en 2 unidades. De esta forma, se determina que el puerto destino del escaneo estaba abierto.

El siguiente diagrama muestra la secuencia forma gráfica:




Escenario 2: Víctima con el puerto cerrado

Como en el escenario anterior, el primer paso siempre es obtener el IPID del zombie, enviándole un paquete SYN+ACK, con objeto de que éste nos devuelva el paquete RST correspondiente.

De la misma forma, se envía a la víctima el paquete de solicitud de conexión, indicando en las cabeceras que el origen del mismo es el host zombie. Dado que el puerto está cerrado (escenario 2), la víctima devuelve al aparente emisor un paquete RST indicándole que no será posible establecer la conexión solicitada. El zombie recibe el paquete RST y lo ignora.

El atacante, siguiendo la misma técnica que en otras ocasiones, obtiene el IPID del zombie, y dado que es el número siguiente al recibido al iniciar el procedimiento, puede determinar que no ha realizado ningún envío entre ambos, y que, por tanto, el puerto de destino estaba cerrado.


Desde el punto de vista del atacante las ventajas son, fundamentalmente:

  • El anonimato, puesto que desde la víctima todas las conexiones provienen virtualmente del zombie, y en ningún momento se envía información directamente desde el atacante.
  • El alcance, es decir, esta técnica permite escanear puertos de máquinas a las que directamente no se tendría acceso debido a la acción de filtros (como firewalls) intermedios. Dado que las conexiones provienen del zombie, sólo habría que tener acceso a éste para realizar el escaneo.
  • La visión de red que aporta, en otras palabras, permite determinar las relaciones de confianza existentes entre el zombie y la víctima. Si, por ejemplo, un intento directo de conexión a un puerto de la víctima es rechazado y, sin embargo, es posible acceder a él desde un intermediario, es porque existe algún tipo de relación de confianza entre ambos, lo cual puede ser utilizado en ataques posteriores.

En la actualidad, el principal inconveniente es la dificultad de localizar un zombie apropiado para realizar los ataques, tanto por las condiciones software que debe cumplir (sistemas operativos, kernels, etc.), el escaso tráfico de red que debe tener en el momento del escaneo (necesarios para que los IPID no se incrementen por otras conexiones) y, sobre todo, las medidas de seguridad de que disponga, puesto que desde él sería posible detectar al atacante.

jueves, 12 de abril de 2007
Resulta que leyendo la especificación de C# 2.0 he encontrado, en el apartado correspondiente a los tipos anulables un nuevo operador que me ha parecido de lo más interesante, para el tratamiento rápido de valores nulos, el Null Coalescing Operator (NCO) u Operador de Fusión de Nulos (traducción libre).

El operador en cuestión, expresado como ?? (dos cierres de interrogación), permite devolver un valor si no es nulo, o devolver otro valor alternativo ante la nulidad del primero. En otras palabras, un código como:
if (s!=null)
return s;
else
return "por defecto";

O también escrito de la forma, utilizando el operador ternario:

return (s!=null?s:"por defecto"); 

Quedaría, utilizando el nuevo operador de fusión, como:

return s ?? "por defecto"; 

Qué limpio, ¿no?

A primera vista puede parecer que salvo mejorar la legibilidad, no aporta demasiadas ventajas frente al operador ternario ?, pero fijaos en el siguiente código, en un método que retorna el nombre del algo cuyo Id le llega como parámetro:

string nombre = obtenerNombre(id);
if (nombre==null)
return "Desconocido";
else
return nombre;

Utilizando el operador ternario ? podríamos dejarlo en una única línea:

return obtenerNombre(id)==null?"Desconocido":obtenerNombre(id); 

Esto tiene una pega aparte de la dificultad de lectura: si el nombre del llamamos dos veces a obtenerNombre(id), lo cual podría tener efectos secundarios no deseados, o simplemente causar un problema de rendimiento. Para mejorarlo podríamos hacer esto, que mejora la legibilidad y llama sólo una vez al método, aunque es más largo de codificar, siendo muy similar al if inicial:

string p = obtenerNombre(id);
return p==null?"Desconocido":p;

Con el nuevo operador el código quedaría así de simple:

return obtenerNombre(id) ?? "Desconocido"; 

Esto se ejecuta de la siguiente forma: se llama a obtenerNombre(id) y si no es nulo se retorna el valor obtenido. Sólo si el resultado de la expresión ha sido nulo se retornaría el literal "Desconocido".

No sé a vosotros, pero a mí me ha parecido interesantísimo, y de lo más útil.
domingo, 8 de abril de 2007
Vía una entrada en Barrapunto, llego a un artículo en el País titulado "Proveedores de Internet se unen contra el correo basura" donde se recoge la noticia de que los 26 principales proveedores españoles se han puesto de acuerdo en utilizar SPF para luchar contra el spam. Lógico, teniendo en cuenta que sólo un 15% de los mensajes que pululan por sus servidores y redes son reales, lo que indica que la mayor parte de sus recursos están siendo utilizados por el lado oscuro.

SPF (Sender Policy Framework) es una especificación experimental publicada por la IETF en la RFC 4408 (abril 2006), y describe un conjunto de técnicas para evitar la utilización de dircciones de email falsas en los mensajes que circulan por la red.

Aunque creo que esto ya lo he comentado en otras ocasiones, uno de los principales problemas que hacen florecer el spam, scam, phishing, y alguna que otra atrocidad más, es la inocencia con la que fue creado el protocolo SMTP, utilizado para los envíos de mensajes de correo electrónico entre servidores. En este estándar no se establece ninguna técnica para asegurar la veracidad del remitente, por lo que cualquiera puede escribir en nombre de otro, hacerse pasar por quien desee, o simplemente inventar sobre la marcha un emisor, existente o no.

Por eso los mensajes de spam que recibimos pueden provenir incluso de personas que conocemos, pertenecientes a nuestro propio dominio, compañeros que tenemos sentados justo a nuestro lado, etc.

El SPF pretende acabar con esto, pues define un sistema de comprobación en tiempo real de la veracidad del remitente, así como del servidor desde el que se envía un mensaje, consistente, por una parte, en incluir en los registros MX de cada dominio emisor de correos una línea donde se indique la dirección IP desde la que pueden ser enviados. De esta forma, cuando otro servidor SMTP recibe un mensaje con un remitente del dominio anterior, puede consultar esos registros y comprobar si la IP desde la que está recibiendo el email está autorizada para enviarlo.

Lo vemos con un ejemplo real, por suerte (?) tengo bastantes para elegir. Hoy he recibido un correo desde la dirección finkenhoferqov0@hotmail.com aconsejándome soluciones para mis terribles problemas de erección (¿cómo se habrán enterado? :-D). Por suerte, según me indican, la mitad de los varones los sufren, el que no se consuela es porque no quiere.

Si rastreo el mensaje, veo que ha llegado a mi servidor desde la dirección IP 71.186.100.38, cuyo nombre parece ser pool-71-186-100-38.chi01.dsl-w.verizon.net. Si seguimos escarbando, podemos obtener la siguiente información de esta IP:

OrgName: Verizon Internet Services Inc.
OrgID: VRIS
Address: 1880 Campus Commons Dr
City: Reston
StateProv: VA
PostalCode: 20191
Country: US
NetRange: 71.160.0.0 - 71.191.255.255
CIDR: 71.160.0.0/11
NetName: VIS-BLOCK

Y aunque las herramientas de geoposicionamiento de direcciones IP no son el colmo de la precisión, podríamos incluso atrevernos a conjeturar dónde se encuentra el spammer, en Washington.

A la vista de estos datos, se trata de un equipo que utiliza una dirección IP dinámica, conectado a través de una DSL que, de forma voluntaria o no, está enviando spam, cual poseso, al resto del mundo. Está claro que ningún spammer decente dejaría ver todos sus datos así, por lo que, o bien se trata de un PC secuestrado (zombie), o bien toda la información que deja ver es falsa.

En cualquier caso, está claro que no se trata de Hotmail, de donde era, como recordaréis, el remitente del mensaje.

Si tanto mi servidor como Hotmail utilizaran SPF, se habría rechazado el mensaje de forma directa. Al recibir la dirección del remitente, habría acudido a los registros MX de Hotmail para averiguar si la IP desde la que se ha producido la conexión es válida para envíos @hotmail.com, y, obviamente, de esta comprobación siempre se determinaría que el emisor es inválido.

Todo esto está muy bien, personalmente creo bastante en este tipo de soluciones colaborativas y de consenso para luchar contra el spam, más incluso que en las de análisis de contenido que, como ya he comentado en posts anteriores, tienen bastantes dificultades para garantizar el filtrado correcto dada la gran variedad de trucos utilizados.

El problema es precisamente su implementación en la práctica, y eso que en el caso del SPF no es especialmente compleja, todo lo contrario. Está claro que si todos los ISP del mundo utilizaran este método, el spam, fishing y en general cualquier historia basada en la utilización de remitentes falsos tendrían los días contados... al menos en la forma en que hoy los conocemos, claro.
miércoles, 4 de abril de 2007
Indiscutiblemente, los nulos son una de las peores pesadillas del desarrollador, y causa de una gran cantidad de errores en tiempo de ejecución de aplicaciones. La falta de herramientas para tratarlos apropiadamente hacen a veces compleja la convivencia con la oveja negra del rebaño de los valores posibles a aplicar a una variable.

Y es que con poco que hayáis desarrollado, seguro que alguna vez habéis encontrado la difícil tarea de asignar un valor nulo a una variable de tipo valor (int, float, char...) donde no encaja más que usando artimañas difíciles de realizar, trazar y documentar. Por ejemplo, dada una clase Persona con un propiedad de tipo entero llamada Edad, ¿qué ocurre si cargamos un objeto de dicha clase desde una base de datos si en ésta el campo no era obligatorio?

A priori, fácil: al leer de la base de datos comprobamos si es nulo, y en ese caso le asignamos a la propiedad de la clase el valor -1. Buen parche, sin duda.

Sin embargo, optar de forma general por esta idea presenta varios inconvenientes. En primer lugar, para ser fieles a la realidad, si quisiéramos almacenar de nuevo este objeto en la base de datos, habría que realizar el cambio inverso, es decir, comprobar si la edad es -1 y en ese caso guardar en el campo un nulo.

En segundo lugar, fijaos que estamos llevando a la clase artificios que no tienen sentido en el dominio del problema a resolver, en la entidad a la que representa. Mirándolo un poco desde lejos, ¿qué sentido tiene una edad negativa en una entidad Persona? Ninguno.

En tercer lugar, existe un problema de coherencia en las consultas. Si tengo en memoria una colección de personas (realizada, por ejemplo usando generics ;-)) y quiero conocer las que tienen edad definida, debería comprobar por cada elemento si su propiedad Edad vale -1; sin embargo, al realizar la misma consulta en la base de datos debería preguntar por el valor NULL sobre el campo correspondiente.

Cierto es que podríamos llevar también a la base de datos el concepto "-1 significa nulo" en el campo Edad, pero... ¿no estaríamos salpicando a la estructura de datos con una particularidad (y limitación) del lenguaje de programación utilizado? Otra idea bizarra podría ser introducir la edad en un string y problema solucionado: las cadenas, al ser un tipo referencia pueden contener nulos sin problemas, lo que pasa es que las ordenaciones saldrían regular ;-)

Por último, ¿y si en vez de la edad, donde claramente no pueden existir negativos se tratase de una clase CuentaCorriente y su propiedad SaldoActual? Aquí sí que se ve claramente que esto no es una solución válida.

Soluciones, aparte de las comentada, hay para todos los gustos. Se podría, por ejemplo, añadir una propiedad booleana paralela que indicara si el campo Edad es nulo (un trabajazo extra, sobre todo sin son varias las propiedades que pueden tener estos valores), o encapsular la edad dentro de una clase que incorporara la lógica de tratamiento de este nulo.

En cualquier caso, las soluciones posibles son trabajosas, a veces complejas, y sobre todo, demasiado artificiales para tratarse de algo tan cotidiano como es un simple campo nulo.

Conscientes de ello, los diseñadores de C# han tenido en cuenta en su versión 2.0 una interesante característica: los nullables types, o tipos anulables (traducción libre), un mecanismo que permite introducir el nulo en nuestras vidas de forma no traumática.

La siguiente línea generaba un error en compilación, que decía, y no le faltaba razón, que "no se puede convertir null en 'int' porque es un tipo de valor":
int s = null; 

Ahora, en C# 2.0, es posible hacer lo siguiente:

int? s;
s = null;
s = 1;

Obsérvese la interrogación junto al tipo, que es el indicativo de que la variable s es de tipo entero, pero que admite también un valor nulo.

Por dentro, esto funciona de la siguiente forma: int? es un alias del tipo genérico System.Nullable<int>. De hecho, podríamos usar indistintamente cualquiera de las dos formas de expresarlo. Internamente se crea una estructura con dos propiedades de sólo lectura: HasValue, que retorna si la variable en cuestión tiene valor, y Value, que contiene el valor en sí.

Se entiende que una variable con HasValue igual a false contiene el valor nulo, y si intentamos acceder al mismo a través de Value, se lanzará una excepción.

Sin embargo, la principal ventaja que tienen es que se utilizan igual que si fuera un tipo valor tradicional. Los tipos nullables se comportan prácticamente como ellos y ofrecen los mismos operadores, aunque hay que tener en cuenta sus particularidades, como se aprecia en el siguiente código:

int? a = 1;
int? b = 2;
int? intNulo = null;
bool? si = true;
bool? no = false;
bool? niSiNiNo = null;
Console.WriteLine(a + b); // 3
Console.WriteLine(a + intNulo); // Nada, es nulo
Console.WriteLine(a * intNulo); // Nada, es nulo
Console.WriteLine(si & no); // false
Console.WriteLine(si & no); // true
Console.WriteLine(si & niSiNiNo); // Nada, es nulo
Console.WriteLine(no & niSiNiNo); // false
Console.WriteLine(si | niSiNiNo); // true
Console.WriteLine(no | niSiNiNo); // Nada, es nulo

Curioso en los booleanos, en los que el valor nulo se puede interpretar como un quizás. De esta forma es fácil prever el resultado de una operación lógica: "Verdad ó Quizás" resuelve como verdadero, ó "Falso y Quizás" es falso, por ejemplo.