miércoles, 28 de abril de 2010

Validaciones de contenido de archivo para subir a la WEB

En muchas aplicaciones se necesitan guardar archivos de los usuarios y en la mayoría de los casos se implementa una simple validación de extensión, pero un usuario malicioso podría subir archivos dañinos con extensiones falsas de los archivos que permite nuestra aplicación (ejm: imagenes, pdf, etc) con la esperanza que de alguna manera se ejecute el código malicioso del archivo subido.

Para proteger un poco mas nuestras aplicaciones podemos implementar una o dos barreras adicionales de protección: Validación de contenido y scan con antivirus. En este artículo hablare de la primera opción.

La idea e implementación es muy sencilla, basicamente lo que hay que hacer es que dependiendo de la extensión del archivo se chequea la cabecera/estructura del archivo para determinar si cumple o no con los estandares establecidos para el formato de archivo utilizado.

Por ejemplo un GIF debe comenzar con la cabecera GIF89a o GIF89a o PNG , para BMP los dos primero bytes son 0x42 0x4D, para PDF los primeros 5 bytes son %PDF-

Las validaciones pueden ser mucho mas complejas que solo la cabecera pero la cabecera es buen inicio ya que aunque el archivo maligno le coloquen la cabecera correspondiente a su tipo de archivo, el mismo hecho de modificar la cabecera para que cumpla el estandar dañara el código maligno o por lo menos la forma en que se ejecuta.

Ejemplo en código:

///
/// Valida si el contenido del archivo corresponde o no con el tipo de archivo que se indica solo para gif, jpg y pdf
///

///
El tipo de archivo indicado a validar///
La data del archivo/// Devuelve False si el contenido del archivo no coincide con el tipo de archivo
static private bool ValidarTipoArchivoContenido(string sTipoArchivo, byte[] Archivo)
{
bool bResultado = true;
try
{
int TamanoValidar = 0;
Stream objStream = new MemoryStream(Archivo);
switch (sTipoArchivo.ToLower())
{
case ".gif":
TamanoValidar = 6;
break;
case ".jpg":
case ".jpeg":
TamanoValidar = 4;
break;
case ".pdf":
TamanoValidar = 5;
break;
case ".bmp":
TamanoValidar = 2;
break;
case ".tiff":
case ".tif":
TamanoValidar = 4;
break;
case ".txt":
TamanoValidar = int.Parse(objStream.Length.ToString());
break;

}
if (TamanoValidar > 0)
{
bResultado = false;
byte[] Buffer = new byte[TamanoValidar];
objStream.Read(Buffer, 0, TamanoValidar);
objStream.Close();

string sCabecera = System.Text.Encoding.ASCII.GetString(Buffer);

switch (sTipoArchivo.ToLower())
{
case ".gif":
if (sCabecera == "GIF87a" || sCabecera == "GIF89a" || sCabecera.Substring(1, 3) == @"PNG")
bResultado = true;
break;
case ".jpg":
case ".jpeg":
if (int.Parse(Buffer.GetValue(0).ToString()) == 255 && int.Parse(Buffer.GetValue(1).ToString()) == 216 && int.Parse(Buffer.GetValue(2).ToString()) == 255 && (int.Parse(Buffer.GetValue(3).ToString()) == 224 || int.Parse(Buffer.GetValue(3).ToString()) == 225))
bResultado = true;
break;
case ".pdf":
if (sCabecera == "%PDF-")
bResultado = true;
break;
case ".bmp":
if (sCabecera == "BM")
bResultado = true;
break;
case ".tiff":
case ".tif":
if (sCabecera.Substring(0,2) == "II" && int.Parse(Buffer.GetValue(2).ToString()) == 42)
bResultado = true;
break;
}
}
}
catch (Exception ex)
{
bResultado = false;
log4net.LogManager.GetLogger("root").Error("Error validación contenido", ex);
}
return bResultado;
}

Esto es solo un ejemplo sencillo de implementación, si desea consultoría sobre el tema por favor contacteme

lunes, 12 de abril de 2010

Problemas de Reporting Services en IIS7, cambio idioma mensajes

Les comento unos problemas que se presentaron al colocar en producción algunos reportes de Reporting Services, el problema se manifiesta con problemas en el toolbar de los reportes (donde van los filtros) en donde no se muestra ninguna imagen de dicho toolbar.

El problema se da debido a las diferencias existentes en el nuevo core de IIS7 en donde existen cambios de arquitectura y hasta en los configs de las aplicaciones .NET. La sección que estaba debajo de se ha movido a debajo de

así que para que funcione en IIS deben colocar en el Config:

<system.webServer>
<handlers>
<add name="ReportViewerWebControl" path="Reserved.ReportViewerWebControl.axd" verb="*" type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
</handlers>
</system.webServer>

Link de donde saqué la información

Por otro lado si les interesa cambiar los mensajes predefinidos de Reporting Services (en Ingles), existe la alternativa de escribir una clase o dll y hacer referencia de el en el config.

Código Ejemplo:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Reporting.WebForms;
namespace NombreNameSpaceCompleto
{
public class CReportViewerCustomMessages :
Microsoft.Reporting.WebForms.IReportViewerMessages, IReportViewerMessages2
{
#region IReportViewerMessages Members
public string BackButtonToolTip
{get { return ("Página anterior"); }}
public string CurrentPageTextBoxToolTip

{get { return("Página actual"); }}
public string ExportButtonText

{get { return("Exportar"); }}
public string ExportButtonToolTip
{get { return ("Presione para Exportar"); }}
public string ExportFormatsToolTip
{get { return("Seleccione el formato para exportar"); }}
public string FalseValueText
{get { return("Falso"); }}

Y en el archivo de configuración se debe colocar en el AppSettings:

<add key="ReportViewerMessages" value="NameSpace.CReportViewerCustomMessages, NombreAssembly">

Por favor pongase en contacto si desea consultoría experta en Reporting Services integrado con ASP.NET.

jueves, 8 de abril de 2010

Generación PDF con ASP.NET

En un proyecto para un Banco, nos dieron el requerimiento de crear PDFs con información de los temas que manejaba la aplicación del Banco.

Para cubrir este requerimiento hice una investigación de las diversas tecnologías existentes que permiten generar PDFs dinamicamente y la que seleccioné fue iTextSharp

Esta librería está muy completa aunque todavía le hace falta trabajo, está desarrollada con el FrameWork 1.1 pero yo pude compilarla para el 2.0 sin muchos problemas.

Existen varias formas de crear los PDF, básicamente 2 principales:

1- Totalmente manual, en donde uno va construyendo desde código toda la estructura completa del contenido del PDF, esta es la opción que permite hacer mayor cantidad de cosas al generar los PDF pero de verdad que me parece mucho trabajo hacerlo todo a mano y ademas no es nada mantenible, si quieren cambiar un simple parrafo hay que modificar el código y cuadrar todo a medio ciegas. Ejemplo (cortado) de un simple PDF que usa una simple tabla:

Document document = new Document();
RtfWriter.getInstance(document, new FileStream("Chap0804.rtf", FileMode.Create));
document.Open();
Table table = new Table(3); table.BorderWidth = 1; table.BorderColor = new Color(0, 0, 255);
table.Padding = 5; table.Spacing = 5; Cell cell = new Cell("header");
cell.Header = true; cell.Colspan = 3; table.addCell(cell);
cell = new Cell("example cell with colspan 1 and rowspan 2");
cell.Rowspan = 2; cell.BorderColor = new Color(255, 0, 0);
table.addCell(cell);

Y asi seguiría, agregando mucho código a mano
 
Para el requerimiento del Banco, lo que decidimos fue utilizar las funciones de Parsear HTML que tiene la librería, en donde básicamente uno lo que hace es construir el aspx dinamico como quiere que se vea en el PDF y luego manda a parsear ese HTML con la librería.
 
Un tema de importancia con esta alternativa es que hay que tener en cuenta que el parser no soporta todas las etiquetas html, mas bien soporta muy pocas:
ol ul li a pre font span br p div body table td th tr i b u sub sup em strong s strike h1 h2 h3 h4 h5 h6 img
 
Así que hay que construír muy cuidadosamente el HTML generado por el ASPX para cuidarse de no utilizar etiquetas que generen error en el parse del HTML realizado por la librería. Así que nada de usar controles .net muy sofisticados, es mejor generar la página al viejo estilo, utilizando literales on inline code.
 
Un tema importante es que considero que deben bajar el código de la librería y hacer referencia al mismo ya que es útil para hacer debug cuando explotan los PDF por algún error HTML.
 
Por mi parte, yo mismo modifiqué código de la librería para corregir algunos detalles como situaciones en donde explota una Excepción pero el código de la librería se lo traga y entonces genera PDFs en blanco, cosa que es muy grave ya que no nos da seguridad que el PDF generado esté totalmente correcto.
Otro tema que recuerdo que modifiqué fué la manera como obtienen las imagenes para insertarla en el PDF, ya que en la arquitectura empleada para el Banco, el PDF se generaba en un WS que accedía a la capa WEB donde estaban los ASPX que eran la fuentes para generar los PDF, entonces se presentaron problemas de autenticación entre las dos capas porque los request enviados desde la librería no utilizaban configuración de credenciales, lo modifiqué para que los usara y se pudiera acceder a otros servidores sin problemas
 
Ejemplo de la construcción del PDF:

WebRequest request = WebRequest.Create(url);

request.UseDefaultCredentials = true;
readerValidator = new StreamReader(request.GetResponse().GetResponseStream());
string val = readerValidator.ReadToEnd();//stream reader para validar
log4net.LogManager.GetLogger("root").Info("html de la pagina "+val);
if (val.Contains(System.Configuration.ConfigurationManager.AppSettings["sValorError"])){
Archivo = null;
log4net.LogManager.GetLogger("root").Info("la página retornó error");
}else{
log4net.LogManager.GetLogger("root").Info("la página NO retornó error");
aux = new MemoryStream(Encoding.UTF8.GetBytes(val));//otro memory stream para generar el pdf
reader = new StreamReader(aux);
//pasar a pdf
System.Xml.XmlTextReader _xmlr = new System.Xml.XmlTextReader(reader);
HtmlParser.Parse(document, _xmlr);
//pasar pdf a array de bytes
Archivo = ArchivoResultante.ToArray();
reader.Close();document.Close();

Un punto importante es determinar si el HTML del URL que vamos a generar como PDF es correcto, para ello se puede hacer el truco que en el ASPX que genera el HTML a pasar a PDF se implemente un buen manejo de errores y en caso de error generar la página con un STRING en especifico que luego podamos consultar para saber si debemos generar el PDF o no.

Si usted necesita una solución o asesoría en temas de generación de PDFs en .NET, puede contactarme a través de este Blog para coordinar una reunión y poder revisar como lo podemos ayudar.

miércoles, 7 de abril de 2010

Monitoreo mensajes de Red en aplicaciones .NET

Mecanismo útil para cuando se quiere ver el trafico de red generado por una app .net, por ejemplo para monitorear los comando FTP enviados al servidor, el uso de los Sockets en comunicaciones TCP/IP, ETC


Aquí se puede ver el detalle: http://blogs.msdn.com/dgorti/archive/2005/09/18/471003.aspx

Básicamente es activar el Trace en el archivo de configuración de la aplicación

Lo utilice para depurar la comunicación de una librería FTP y ver como es la comunicación en detalle con el FTP de un AS/400,  lo que me permitió ver cada uno de los comando enviados por la librería

Un ejemplo de cómo queda el log:

System.Net Verbose: 0 : [1540] WebRequest::Create(ftp://127.0.0.1//Omar*.*)
System.Net Information: 0 : [1540] FtpWebRequest#39785641::.ctor(ftp://127.0.0.1//Omar*.*)
System.Net Verbose: 0 : [1540] Exiting WebRequest::Create() -> FtpWebRequest#39785641
System.Net Verbose: 0 : [1540] FtpWebRequest#39785641::GetResponse()
System.Net Information: 0 : [1540] FtpWebRequest#39785641::GetResponse(Method=LIST.)
System.Net Error: 0 : [1540] Exception in the FtpWebRequest#39785641::GetResponse - Unable to connect to the remote server
System.Net Error: 0 : [1540] at System.Net.FtpWebRequest.GetResponse()
System.Net Verbose: 0 : [1540] Exiting FtpWebRequest#39785641::GetResponse()

System.Net Verbose: 0 : [5260] WebRequest::Create(ftp://127.0.0.1//Omar*.*)
System.Net Information: 0 : [5260] FtpWebRequest#57352375::.ctor(ftp://127.0.0.1//Omar*.*)
System.Net Verbose: 0 : [5260] Exiting WebRequest::Create() -> FtpWebRequest#57352375
System.Net Verbose: 0 : [5260] FtpWebRequest#57352375::GetResponse()
System.Net Information: 0 : [5260] FtpWebRequest#57352375::GetResponse(Method=LIST.)
System.Net Information: 0 : [5260] Associating FtpWebRequest#57352375 with FtpControlStream#2637164
System.Net Information: 0 : [5260] FtpControlStream#2637164 - Received response [220 Microsoft FTP Service]
System.Net Information: 0 : [5260] FtpControlStream#2637164 - Sending command [USER orodriguez]
System.Net Information: 0 : [5260] FtpControlStream#2637164 - Received response [331 Password required for orodriguez.]
System.Net Information: 0 : [5260] FtpControlStream#2637164 - Sending command [PASS ********]
System.Net Information: 0 : [5260] FtpControlStream#2637164 - Received response [530 User orodriguez cannot log in.]
System.Net Information: 0 : [5260] FtpWebRequest#57352375::(Releasing FTP connection#2637164.)
System.Net Error: 0 : [5260] Exception in the FtpWebRequest#57352375::GetResponse - The remote server returned an error: (530) Not logged in.

martes, 6 de abril de 2010

Backup en Internet (muchos archivos, fuentes y gran tamaño de Backups)

Me tocó la responsabilidad de realizar backup periodicos sobre un servidor web en donde se hace hosting de algunas aplicaciones de unos clientes y algunas aplicaciones propias de la compañía donde trabajo.


El tema es que el Backup lo debo hacer en una maquina que no sea del hosting ya que el mismo a veces no es estable por lo que necesitaba que mis backups quedaran almacenados en la Internet en caso de tener problemas con el hosting.

Luego de una larga investigación en Internet, ninguna solución encontrada se ajustaba a mis necesidades, las buenas soluciones eran pagas y las gratis no permitían realizar todo lo que deseaba de manera automática, configurable y segura, además la mayoría no presentaba las ventajas de almacenamiento en Internet como DropBox.

La solución fue una mezcla de herramientas gratis que me permitieron hacer un sistema de Backup bien estable, configurable, seguro y totalmente automático.

La solución involucra:

1- DropBox como medio de almacenamiento/transferencia por Internet (2gb gratis hasta 8gb)

2- SyncToy como herramienta para hacer las copias de las aplicaciones y directorios de BD a las carpetas de DropBox

3- ExpressMaint. En este caso las BD están en SQL Server 2005 Express por lo cual no contaba con las herramientas de jobs y planes de mantenimiento para hacer Backups de las BD, la aplicación ExpressMaint permite realizar backups de SQL Server Express de manera muy fácil, rápida y configurable.

4- Un archivo bat que ejecuta los Backups de la BD con la herramienta del punto 3 y posteriormente manda a llamar a SyncToy para realizar el copiado a las carpetas de DropBox

5- Resource Kit de Microsoft para Windows 2003 para configurar una aplicación como servicio. Bajar Esto fue utilizado para hacer que DropBox corriera como servicio

6- Una tarea programada de Windows para que ejecutara el BAT del punto 4.

Básicamente la solución de Backup funciona de la siguiente manera:

1- La tarea programada de Windows se ejecuta de acuerdo a lo agendado (ejem: dos veces a la semana), esta tarea invoca a un archivo BAT

2- El archivo BAT internamente llama a la aplicación que hace los Backups de las BD (ExpressMaint) y luego llama a SyncToy para que ejecute las copias programadas

Ejemplo contenido BAT:

E:\Backups\ExpressMaint.exe -S (local)\SQLExpress -D ALL_USER -T DB -R e:\backups\reports -RU WEEKS -RV 1 -B e:\backups\Bds -BU DAYS -BV 1 -V -C
"C:\Program Files\SyncToy 2.1\SyncToyCmd.exe" -R

Los parámetros de configuración de ExpressMaint los pueden ver ejecutando el .exe y en la página de download de la app. En el ejemplo mostrado se hace Backup de todas las BD del servidor

La llamada a SyncToyCmd simplemente llama a SyncToy como si usáramos la interfaz pero sin requerir acciones del usuario para ejecutar la sincronización. Nota: Para que funcione se debe primero ejecutar una sincronización desde la interfaz grafica.

3- La ruta de origen de la tarea de Synctoy para copiar la bd es la ruta usada por ExpressMaint y la ruta destino debe ser una carpeta dentro de la carpeta de DropBox. Adicionalmente a las BD, se puede configurar en SyncToy otros pares de carpetas a sincronizar, esto con el propósito de copiar a DropBox las carpetas de aplicación.

4- El punto clave es la configuración de DropBox como servicio, para lo que les dejo el siguiente link que pueden seguir desde el punto 4. Running Dropbox as a Service

Luego de tener todo esto configurado, pueden ver sus respaldos en la página web de DropBox y pueden compartir las carpetas de Backup de dropbox para que sean bajadas en otras maquinas y estén disponibles para otros usuarios de DropBox.

Un punto adicional para lidiar con los problemas de espacio de DropBox (solo 2 gb gratis al principio) es por ejemplo crear un servicio de Windows que implemente un Filewatcher y al detectar nuevos archivos de una carpeta de DropBox los copia a otra carpeta fuera de DropBox y los elimina en DropBox. Esto por ejemplo se puede hacer para mantener solo en DropBox la copia mas reciente y guardar las anteriores en una maquina de manera local, todo de manera automática, configurable y escalable.

Si desea una solución de este tipo, deje un comentario par contactarlo y podemos revisar como implementar una solución de este tipo en su(s) servidores.