Mostrando las entradas con la etiqueta Aplicaciones seguras. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Aplicaciones seguras. Mostrar todas las entradas

viernes, 7 de setiembre de 2012

ASP.NET–Detecta las vulnerabilidades de tu aplicacion web

aspnet-attacksUna de las preguntas obligatorias que se hace un desarrollador de aplicaciones es:
“mi aplicación web será vulnerable en algún punto a ataques de delincuentes del ciberespacio?”
Y si no tienes mucha experiencia desarrollando sitios web, pues no tendrás idea por dónde empezar para detectar esas vulnerabilidades.
Y es posible que efectivamente estés haciendo una bomba de tiempo… por eso es necesario que encuentres alguna manera de testear si tu sitio web tiene agujeros de seguridad y puede ser hackeado.
Conforme un desarrollador va adquiriendo más experiencia, va aprendiendo cuáles pueden ser los puntos débiles que puede tener una aplicación web y entonces se preocupa por evitar que vulneren dichos agujeros… y le hará exigentes pruebas hasta encontrar cero vulnerabilidades (eso espero Guio).
Pero mientras tanto qué hacen?
Mientras tanto, pueden apoyarse de herramientas que realizan exámenes en línea, tal como lo hace https://asafaweb.com/
Así es, este web site le hace un examen integral a tu sitio ASP.NET en línea y te entrega un informe completo de las vulnerabilidades que halló en tu web site, y además te dice cómo puedes solucionar dichas fallas, añadiéndote enlaces para que estudies dichas vulnerabilidades y solucionarlas.
https://asafaweb.com/ es una herramienta muy útil, la cual se las recomiendo, al menos en una etapa inicial.
Espero que les sea de utilidad Guio

viernes, 9 de diciembre de 2011

ASP.NET - Recuperando imagenes desde carpeta fuera de tu sitio web

block_imageCómo hacemos para mostrar en nuestro sitio web imágenes que están guardadas en carpetas que están fuera de nuestro sitio web? Tienes dos posibilidades:
  1. Conviertes dicha carpeta con imágenes en directorio virtual, y luego accedes a dichas imágenes a través de una url.
  2. Añades un archivo handler, digamos Handler1.ashx, para que a través de él accedas a cada imagen ubicada en dicha carpeta.
Si eligieras la primera opción, tendrías el inconveniente de que si le usuario pone ver el código html de tu página… vería cuál es la ruta de cada imagen y podría acceder a ellas.
La segunda opción, al contrario te brinda muchas ventajas… como es la privacidad de las imágenes: no se menciona dónde están guardadas las imágenes… sólo se puede acceder a ellas a través de este handler…
además si quisieras poner algunas validaciones adicionales antes de despachar la imagen… podrías incluirlas en este handler. Hace algún tiempo escribí algo al respecto, puedes leerlo desde aquí:
http://www.neuronasoft.net/2010/04/aspnet-imagenes-solo-para-usuarios.html
Vamos entonces a demostrar entonces cómo servimos imágenes a través de un handler.ashx:
Primero, tendrás que ponerle a tus controles imagen una ruta como esta:
<asp:Image ID="Image1" ImageUrl="Handler1.ashx?img=banner.png" runat="server" />
Date cuenta que estoy pasando el nombre de la imagen como un querystring.
Luego, el código del handler sería el siguiente:
    Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest

        Dim Path As String = "C:\PrivateImages\"

        Dim img As String = context.Request.QueryString("img")

        context.Response.ContentType = "image/png"

        context.Response.TransmitFile(Path & img)

    End Sub
Como ves, es muy simple, sin embargo nos resuelve muchos contratiempos.
Espero que te sea de utilidad Guiño

lunes, 14 de febrero de 2011

ASP.NET - Ocultar metodos de Servicio Web y mostrar página personalizada

acceso-restringido
Ya habíamos comentado en un anterior post sobre cómo podíamos hacer para ocultar los métodos de un web service, es decir que si alguien accede a nuestro web service via url, no tenga la capacidad de visualizar cuáles son los métodos ni cuáles son los parámetros que requiere cada uno.
Esta vez vamos a mostrar una manera alternativa a aquella solución, y esta vez será más personalizada... en lugar de mostrar un error como lo hicimos anteriormente, esta vez mostraremos una pagina aspx con un mensaje personalizado.
Vamos a empezar con el ejemplo:
1.- añadan un web service y añadan algún método... luego
2.- añadan una página aspx llamada Presenter.aspx y pongan el sgte código en el lado aspx:
<h2 style="text-align: center">
Esta página se muestra como portada del web service</h2>
<h3 style="text-align: center">
de esta manera ocultamos los métodos del web service</h3>
<div style="text-align: center">
<img title="Acceso Restringido" src="http://conciencianoosfera.files.wordpress.com/2009/09/acceso-restringido.jpg"
style="width: 300px; height: 500px" alt="Acceso Restringido">
</div>

3.- vayan al web.config y escriban lo sgte entre las etiquetas </system.web>

<webServices>
<wsdlHelpGenerator href="Presenter.aspx"/>
</webServices>
Luego ejecuten el web service y verán lo que ocurre. Interesante no?

web_service_documentation_hidden

Sigamos. Ahora, para evitar despachar la documentación .disco o WSDL, podemos hacer lo sgte:

1.- Añadan el archivo Global.asax y pongan el sgte código:

protected void Application_BeginRequest(Object sender, EventArgs e)
{
string currentRequest = Request.RawUrl.Trim().ToLower();
if (currentRequest.IndexOf("?wsdl") > 0 || currentRequest.IndexOf("?disco") > 0)
{ 
throw new HttpException(404, string.Empty);
}
}
Espero que les sea de utilidad ;)

martes, 7 de diciembre de 2010

ASP.NET - Ocultar metodos de Servicio Web

aspnet_web_service Cuando desarrollamos servicios web, habrán notado que toda la información de cómo hay que comunicarse con cada uno de los métodos del ws es pública.
Entonces, cualquier persona que llegue hasta tu web service, puede revisar cuáles son los parámetros que necesita cada uno de los métodos de tu servicio web para establecer una comunicación.
Lógicamente, dependiendo de los mecanismos de seguridad que hayas incluido en tu servicio web, podrás restringir el acceso a dichos métodos.
Pero, pero, pero… hay una forma recontra simple para esconder la documentación de todos los métodos de tu web service. Lo único que hay que hacer es añadir las siguientes líneas dentro del web.config (dentro de System.Web):
<webServices>
<protocols>
<remove name="Documentation"/>
</protocols>
</webServices>
Pero ojo: antes de añadir estas líneas en el web.config, primero deben añadir la referencia al web service desde su aplicación cliente, de este modo se descargará toda la información acerca de los métodos del web service, como son los archivos .disco, .wsdl y .map.

Luego de añadir esas líneas, ejecuten directamente el web service y verán que muestra un mensaje de error como la imagen que adjunto a continuación:

aspnet_web_service2

Sin embargo podrán conectarse desde su aplicación cliente al web service sin ningún problema.

Aquí hay un post adicional, con un método alternativo para ocultar los métodos del servicio web.

Espero que les sea de utilidad ;)

jueves, 2 de diciembre de 2010

ASP.NET - Decodificar el viewstate

viewstate_decoder
Como es sabido, ASP.NET WebForms para lograr mantener el contenido de los controles de servidor, utiliza el viewstate, que no es otra cosa que la información del usuario, de las propiedades de los controles, etc… guardada en un control oculto de la misma página, dicha información se guarda de manera codificada en formato Base64.
Si te interesa conocer más sobre cómo funciona este proceso, puedes hacer uso de un decodificador de viewstate, los cuales hay muchos.
Puedes buscar en internet poniendo: “online viewstate decoder” y aparecerán muchos para que elijas.
Pero, por qué alguien quisiera analizar el viewstate?
1.- Para conocer qué información se guarda en el viewstate.
2.- Para que seas consciente de cuánta información estás guardando en el viewstate.
3.- Para que sepas cómo se estructura la información en el viewstate.
4.- También permite determinar si la aplicación tiene agujeros de seguridad… por ejemplo, de esta manera he logrado encontrar la contraseña oculta en el perfil del usuario y otras cosillas más que no menciono para no mal-aconsejar ;)
5.- etc…(aquí va tu aporte).
Les dejo el enlace de dos decodificadores que uso de vez en cuando:
http://lachlankeown.blogspot.com/2008/05/online-viewstate-viewer-decoder.html
http://ignatu.co.uk/ViewStateDecoder.aspx
Antes que alguien diga: “usa ASP.NET MVC y se acabó el viewstate o encripta el viewstate y se acabó el problema”, mejor se los digo Yo.
Espero que les sea de utilidad ;)

sábado, 26 de junio de 2010

ASP.NET - Subir y descargar archivos del servidor mediante pagina aspx intermedia

download_file Lo prometido es deuda (lean los comentarios de este otro post), así que vamos con el desarrollo del post.
En esta ocasión vamos a realizar el proceso de:
1.- Guardar, en una carpeta del servidor, archivos, los cuales deben guardarse con otros nombres (nombres autogenerados, por ejemplo: 3F2504E0-4F89-11D3-9A0C-0305E82C3301).

2.- Guardar en la base de datos, el nombre original del archivo y el nuevo nombre.

3.- Recuperar los nombres y rutas de los archivos subidos al servidor y listarlos en un gridview.

4.- Basado en los puntos 2 y 3, permitir descargar los archivos con su original nombre.

Para completar este ciclo voy a crear en la base de datos una tabla llamada document_attachment y los respectivos procedimientos almacenados para la gestión de archivos.
Entonces, este es el script T-SQL que uso para el caso ( una tabla y 3 procedimientos almacenados):
CREATE TABLE [dbo].[document_attachment](
[attachmentid] [int] IDENTITY(1,1) NOT NULL,
[filename] [varchar](250),
[fileguid] [varchar](50),
CONSTRAINT [pk_document_attachment] PRIMARY KEY CLUSTERED 
([attachmentid] ASC)
)
GO
--------------------------------------------------
CREATE PROCEDURE[dbo].[document_attachment_GetAll]
AS

SELECT
[attachmentid],
[filename],
[fileguid]
FROM [dbo].[document_attachment]

GO
--------------------------------------------------
CREATE PROCEDURE[dbo].[document_attachment_Insert]
@attachmentid int output,
@filename varchar(250),
@fileguid varchar(50)

AS
INSERT INTO[dbo].[document_attachment](
[filename],
[fileguid]
)
VALUES(
@filename,
@fileguid
)
SET @attachmentid = SCOPE_IDENTITY()
GO

--------------------------------------------------
CREATE PROCEDURE[dbo].[document_attachment_GetByGuid]
@guid varchar(50)
AS

SELECT
[filename],
[fileguid]
FROM [dbo].[document_attachment]
WHERE [fileguid] = @guid
--------------------------------------------------
Antes de pasar al código ASP.NET, les recomiendo que guarden la cadena de conexión a la base de datos en el web config (para el ejemplo está sin encriptar, …pero uds deben encriptarlo)

<connectionStrings>
<add name="DB" connectionString="server=.\SQLEXPRESS;database=my_database;uid=sa;pwd=xxxxxxxxxxxxxxx" />
</connectionStrings>

Otra cosa, deben crear una carpeta llamada Uploads (en la raiz de su aplicación), que es donde se guardarán los archivos que se suben… a esta carpeta hay que darle permisos de lectura/escritura al usuario SERVICIO DE RED.
uploads_folder

Ahora sí, necesitamos dos páginas aspx:
1.- Uploader.aspx, que es la página que permite subir los archivos al servidor e, inmediatamente después actualiza el contenido del gridview con los archivos subidos.

2.- Downloader.aspx, que es la página intermedia para descargar archivos… gracias a esta página podemos evitar que el usuario sepa desde qué carpeta se descargan los archivos y además en el código de esta página pueden hacer las verificaciones que quieran antes de permitirle al usuario descargar el archivo. Tú me preguntarás: puedo usar un archivo ashx (que es más ligero) en lugar de una pagina aspx para descargar? la respuesta es sí.

Este es el código completo:
Uploader.aspx
uploader

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div style="width: 350px; background-color: #f2ceea; padding: 10px; margin: 20px">
Seleccione archivo:<asp:FileUpload ID="FileUpload1" runat="server" />
<hr />
<asp:Button ID="btnUpload" runat="server" Text="Guardar archivo" />
</div>
<asp:GridView ID="griddocument_attachment" BackColor="lightgreen" Width="400px" runat="server"
AutoGenerateColumns="False" DataKeyNames="fileguid" HeaderStyle-HorizontalAlign="Left"
HeaderStyle-VerticalAlign="Top" RowStyle-VerticalAlign="Top">
<Columns>
<asp:TemplateField HeaderStyle-Width="3%">
<ItemTemplate>
<asp:Image ID="Image1" ImageUrl="~/images/file.png" runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Archivo">
<ItemTemplate>
<%# Eval("filename")%>
                </ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Descargar" HeaderStyle-Width="5%">
<ItemTemplate>
<asp:HyperLink ID="hplAttachment" Text='<%# Eval("filename")%>' NavigateUrl='<%# String.Format("~/Downloader.aspx?guid={0}", Eval("fileguid")) %>'
ImageUrl="~/images/download.png" runat="server"></asp:HyperLink>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>    
</form>
</body>
</html>

En el gridview, el hyperlink con la imagen download.png es quien tiene el enlace de descarga, parecido a esto:
http://localhost/Test_UI/Downloader.aspx?guid=ee4a00fa-cfa9-490e-a65c-12d652fd2c00

código VB.NET:
Imports System.Data
Imports System.Data.SqlClient

Partial Public Class Uploader
Inherits System.Web.UI.Page

Private GlobalDBConnection As String = ConfigurationManager.ConnectionStrings("DB").ConnectionString
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack Then Return

document_attachment_GetAll()

End Sub

Protected Sub btnUpload_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnUpload.Click
If Not FileUpload1.HasFile Then Return

Dim fileguid As String = Guid.NewGuid.ToString
Dim FileName As String = FileUpload1.FileName

Try
FileUpload1.SaveAs(Server.MapPath("./Uploads/") & fileguid)
Catch
'error....
Return
End Try

Dim i As Integer = document_attachment_Insert(FileName, fileguid)
If i = 0 Then
'error....
Else
document_attachment_GetAll()
End If

End Sub

Sub document_attachment_GetAll()
Dim connection As New SqlConnection(GlobalDBConnection)
Dim command As New SqlDataAdapter("document_attachment_GetAll", connection)
command.SelectCommand.CommandType = CommandType.StoredProcedure

Dim dt As New DataTable()
command.Fill(dt)

griddocument_attachment.DataSource = dt
griddocument_attachment.DataBind()

End Sub

Function document_attachment_Insert(ByVal filename As String, ByVal fileguid As String) As Integer
Dim connection As New SqlConnection(GlobalDBConnection)
Dim command As New SqlCommand("document_attachment_Insert", connection)
command.CommandType = CommandType.StoredProcedure

Dim p_attachmentid As New SqlParameter("@attachmentid", SqlDbType.Int)
p_attachmentid.Direction = ParameterDirection.Output
command.Parameters.Add(p_attachmentid)
command.Parameters.AddWithValue("@filename", filename)
command.Parameters.AddWithValue("@fileguid", fileguid)

command.Connection.Open()
Try
command.ExecuteNonQuery().ToString()
Catch
Return 0
Finally
command.Connection.Close()
End Try

If Not IsDBNull(p_attachmentid.Value) Then
Return CInt(p_attachmentid.Value)
Else
Return 0
End If
End Function

End Class

Downloader.aspx
 en el lado aspx tiene absolutamente nada, excepto el tag Page:
<%@ Page Language="vb" …………………………………………………… %>
 código VB.NET:
Imports System.Data
ImportsSystem.Data.SqlClient

Partial Public Class Downloader
Inherits System.Web.UI.Page

Private GlobalDBConnection As String= ConfigurationManager.ConnectionStrings("DB").ConnectionString
Protected SubPage_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim guid As String= Request.QueryString("guid")

If String.IsNullOrEmpty(guid) Then Return

' aquí puedes poner las validaciones que quieras

'............   ....................

Dim dt As DataTable = document_attachment_GetByGuid(guid)
If dt.Rows.Count = 0 Then Return

Dim
OriginalFileName As String= dt.Rows(0)("filename")
 DownloadFile(Server.MapPath("./Uploads/") & guid, OriginalFileName)
End Sub

Public Sub
DownloadFile(ByVal FilePath As String, ByVal OriginalFileName As String)
Dim fs As IO.FileStream = Nothing

'obtenemos el archivo del servidor
fs = IO.File.Open(FilePath, IO.FileMode.Open, IO.FileAccess.Read)
Dim byteBuffer(CInt(fs.Length - 1)) As Byte
fs.Read(byteBuffer, 0, CInt(fs.Length))
 fs.Close()

Using ms As New IO.MemoryStream(byteBuffer)
'descargar con su nombre original
Response.AddHeader("Content-Disposition", "attachment; filename=" & OriginalFileName)
 ms.WriteTo(Response.OutputStream)
End Using

End Sub

Function
document_attachment_GetByGuid(ByVal guid As String) As DataTable
Dim connection As New SqlConnection(GlobalDBConnection)
Dim command As New SqlDataAdapter("document_attachment_GetByGuid", connection)
 command.SelectCommand.CommandType = CommandType.StoredProcedure
 command.SelectCommand.Parameters.AddWithValue("@guid", guid)
Dim dt As New DataTable()
 command.Fill(dt)

Return dt
End Function

 End Class


ACTUALIZACIÓN:
Debido a las preguntas que constantemente estoy recibiendo acerca de este post, veo que no ha quedado muy claro... entonces, estoy añadiendo el proyecto completo para que lo descarguen y lo analicen con más calma. 
Debido a que estoy incluyendo la base de datos en la carpeta app_data, lo único que tendrían que modificar en el we.config antes de correr el proyecto es el nombre del servidor de su bd... osea reemplacen .\SS2008 por el nombre del servidor que Uds le hayan puesto.
Es todo, espero que les sea de utilidad.
 ;)

lunes, 21 de junio de 2010

ASP.NET - Expresiones regulares para contraseñas seguras

form_validate_re El tema de crear una política de constraseñas seguras para los aplicativos es un tema de amplia discusión en las empresas.
Hay ocasiones en los que simplemente se le crea una cuenta a un usuario y se le asigna una contraseña segura, luego este usuario puede cambiar esa contraseña… pero igual debe elegir una contraseña segura.
Y este es el asunto, no todos los usuarios quieren trabajar con contraseñas seguras. Tú dirás:
Se les obliga a usar contraseñas seguras y punto, pero eso no siempre funciona en la vida real.
Muchas veces los dueños de la empresa o la alta dirección quieren usar el password de toda su vida. Tú puedes hacer denodados esfuerzos: les explicas con ejemplos, con videos, con pruebas, etc… hasta que al fin los convences… luego de tu gran esfuerzo te dicen:
pata, yo quiero seguir usando mi viejo password ya?
Qué te queda? renegar y acatar la orden (previo: Yo se lo advertí!!).
Pero como nosotros estamos del lado de los sensatos, vamos a invertir nuestro tiempo creando una funcionalidad que nos genera una expresión regular para exigir contraseñas seguras, basado en las condiciones que elijas.
Código aspx:
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<style type="text/css">
*
{
font-family: Verdana, Arial;
font-size: 11px;
height: 21px;
}
table
{
border-collapse: collapse;
background-color: Orange;
}
th
{
background-color: Gray;
color: White;
font-size: 13px;
}
th, td
{
padding: 5px;
white-space: nowrap;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<table>
<tr>
<th>
Expresiones Regulares para contraseñas seguras
</th>
</tr>
<tr>
<td>
<asp:CheckBox ID="chkUpper" runat="server" Text="Incluir mayúsculas" />

</td>
</tr>
<tr>
<td>
<asp:CheckBox ID="chkLower" runat="server" Text="Incluir minúsculas" />

</td>
</tr>
<tr>
<td>
<asp:CheckBox ID="chkDigits" runat="server" Text="Incluir dígitos" />
</td>
</tr>
<tr>
<td>
<asp:CheckBox ID="chkSpecialChars" runat="server" Text="Incluir Caracteres especiales (!@#$%^&amp;*_+-/)" />
</td>
</tr>
<tr>
<td>
<strong>Longitud</strong>
<br />
Mínima: &nbsp;<asp:TextBox ID="txtMin" runat="server" Width="54px"></asp:TextBox> 
<br />
Máxima: <asp:TextBox ID="TxtMax" runat="server" Width="54px"></asp:TextBox>  
<br />
Nota: Si desean una longitud exacta, pongan la misma longitud en ambos textboxes              
</td>
</tr>
<tr>
<td style="text-align: right">
<asp:Button ID="btnGenerate" runat="server" Text="Generar Expresión Regular" />
</td>
</tr>
<tr>
<td style="text-align: center">
<asp:TextBox ID="txtRegularExpression" Width="95%" Rows="5" TextMode="MultiLine"
runat="server"></asp:TextBox>
</td>
</tr>
</table>

<div style="background-color: yellow; margin-top: 30px; height: 70px; width: 500px">
Haga sus pruebas aquí:
<br />
Texto a validar: <asp:TextBox ID="TextBox1" ValidationGroup="test" runat="server"></asp:TextBox>
<asp:RegularExpressionValidator ID="RegularExpressionValidator1" ValidationGroup="test" runat="server" 
ControlToValidate="TextBox1" ValidationExpression=""
ErrorMessage="*"></asp:RegularExpressionValidator>
<asp:Button ID="Button1" runat="server" ValidationGroup="test" Text="Validar expresión..." />
</div>

</form>
</body>
</html>

Código VB.NET:

Protected Sub btnGenerate_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnGenerate.Click
Dim RegExp As New StringBuilder

'longitud
If txtMin.Text.Length > 0 AndAlso IsNumeric(txtMin.Text) Then
RegExp.Append("(?=^.{" & txtMin.Text)

If txtMin.Text <> TxtMax.Text Then
RegExp.Append(",")
If TxtMax.Text.Length > 0 AndAlso IsNumeric(TxtMax.Text) Then
RegExp.Append(TxtMax.Text)
End If
End If

RegExp.Append("}$)")
End If

'dígitos
If chkDigits.Checked Then RegExp.Append("(?=.*\d)")

'caracteres especiales
If chkSpecialChars.Checked Then RegExp.Append("(?=.*\W+)")

'mayúsculas
If chkUpper.Checked Then RegExp.Append("(?=.*[A-Z])")

'minúsculas
If chkLower.Checked Then RegExp.Append("(?=.*[a-z])")

RegExp.Append(".*$")

txtRegularExpression.Text = RegExp.ToString
RegularExpressionValidator1.ValidationExpression = RegExp.ToString

End Sub

Es todo, el código está completo, sólo añadan una página aspx y peguen el código respectivo. Luego ejecuten la página, y marquen algunas de las opciones… presionen el botón Generar Expression Regular y la expresión se mostrará en la caja de texto mostrada, Uds pueden usar esta funcionalidad para generar las expresiones regulares que luego le asignarán a su control  asp:RegularExpressionValidator

Aprovecho para recomendarles una herramienta imprescindible para construir / validar expresiones regulares: Expresso.

Como para finalizar el post, te dejo un enlace que genera contraseñas seguras, como para que te ahorres el uso de neuronas generando contraseñas: http://strongpasswordgenerator.com/

;)

lunes, 14 de junio de 2010

ASP.NET - Pagina intermedia para descargar archivos

Bien, vamos a suponer que ya hemos subido varios archivos al servidor y que están guardados por separado en varias carpetas.
Ahora, resulta fácil poner un link al archivo de manera que puedan descargarlo, correcto? por ejemplo, digamos que tenemos una carpeta llamada Uploads dentro de la carpeta donde se aloja nuestra aplicación web… y dentro de la carpeta Uploads hay subcarpetas con archivos que hemos ido  subiendo… por ejemplo:
Uploads\Documents\Excel1.xls
Uploads\Documents\Excel2.xls
Uploads\KB\Excel3.xls
Uploads\KB\Excel4.xls

La manera más fácil de poner a disposición estos archivos, es poner links como éstos:
http://www.midominio.com/uploads/documents/excel1.xls
http://www.midominio.com/uploads/KB/excel3.xls

Pero, estoy creando agujeros de seguridad, sin querer estoy diciéndole a todos mis usuarios (en voz alta): POR SI ACASO, TODOS LOS ARCHIVOS IMPORTANTES Y CONFIDENCIALES QUE NADIE DEBE REVISAR ESTÁN EN LA CARPETA UPLOADS/DOCUMENTS AH?, REPITOOOOOOOOOO EN  LA CARPETA UPLOADS/DOCUMENTS… ASÍ QUE YA SABEN, NO TOQUEN NADA.
Obviamente, nadie te va a hacer caso y al poco tiempo ya tendrías que estar buscando otro trabajo ;)
Pero como aquí queremos que conserves tu trabajo, te vamos a enseñar cómo deberías construir los enlaces para distribuir archivos a descargar, sin delatar la verdadera ruta del archivo.

Otra práctica que recomiendo es guardar el archivo con otro nombre y sin extensión, sólo con un codigo generado… esto me permite guardar subir varios archivos con el mismo nombre sin tener que preocuparme por ello… dado que el verdadero nombre se guarda en una tabla y se le restituye ese nombre al momento de descargar el usuario.

Nota: vamos a asumir que uds, queridos lectores, ya guardan los archivos con otro nombre y que para ello usan una tabla como ésta en la bd:
filetable
si no les parece claro, me lo indican en un comentario y a ver si hacemos un post al respecto.
Ahora sí, empecemos con los pasos para descargar archivos:
Paso 1.- Crear una clase llamada Upload.vb, y dentro de ella una enumeración con los distintos módulos que manejan descarga de archivos en mi sistema:
Public ClassUpload

Public EnumType
Document = 1
KB = 2
End Enum

End Class

Paso 2.- Cada vez que construya una url para descargar archivos, debe seguir este formato:
http://www.midominio.com/downloader.aspx?type=1&guid=927826ff-2a59-48ac-a38d-272e64f78658

donde el parámetro type representa el tipo de archivo, en este caso 1, osea que vamos a descargar un archivo de tipo Documento.

Luego, el parámetro guid es el identificador del archivo. Asimismo, este identificador me permitirá recuperar el nombre del archivo.

Paso 3.- Añadir una pagina aspx adicional llamada Downloader.aspx, que contenga el siguiente código:




Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim type As String = Request.QueryString("type")
If String.IsNullOrEmpty(type) Then Return
If Not
IsNumeric(type) Then Return

Dim
guid As String = Request.QueryString("guid")
If String.IsNullOrEmpty(guid) Then Return

Dim
Folder As String = String.Empty

Dim dt As New DataTable


Select Case type
Case Upload.Type.Document
'Recupero de la bd, basado en el parámetro guid
dt = document_attachment.GetByGuid(guid)
'indico cómo se llama el folder donde se encuentra el archivo
Folder = "Document"


Case Upload.Type.KB
'Recupero de la bd, basado en el parámetro guid
dt = KB_attachment.GetByGuid(guid)
'indico cómo se llama el folder donde se encuentra el archivo
Folder = "KB"
End Select


If dt.Rows.Count = 0 Then Return
'este es el nombre guid
Dim OldFileName As String = dt.Rows(0)("fileguid")
'éste es su original nombre
Dim OriginalFileName As String = dt.Rows(0)("filename")
'descargo el archivo
DownloadFile(Server.MapPath("~") & "\Uploads\" & Folder & "\" & OldFileName, OriginalFileName)
End Sub


Public Sub DownloadFile(ByVal FilePath As String, ByVal OriginalFileName As String)


Dim fs As IO.FileStream = Nothing

'obtenemos el archivo del servidor
fs = IO.File.Open(FilePath, IO.FileMode.Open, IO.FileAccess.Read)

Dim byteBuffer(CInt(fs.Length - 1)) As Byte
fs.Read(byteBuffer, 0, CInt(fs.Length))
fs.Close()

Using ms As New IO.MemoryStream(byteBuffer)
'descargar con su nombre original
Response.AddHeader("Content-Disposition", "attachment; filename=" & OriginalFileName)
ms.WriteTo(Response.OutputStream)
End Using

End Sub

Es todo, espero que les sea de utilidad ;)

jueves, 29 de abril de 2010

ASP.NET - Imagenes solo para usuarios autentificados

Vamos a ponernos en el caso de que tenemos una galería privada de imágenes y no queremos que alguien las esté referenciando en su sitio web sin que nosotros lo aprobemos… mejor dicho, para que visualizen ciertas imágenes tienen que estar autenticados en el sistema. Si resultara que no estás autentificado, en lugar de ver la iamgen que esperabas, verás esta imagen:
nodisponible nodisponible.png
Sobre este tema siempre he visto tremendas sábanas de código y en este caso, creo que la solución es muy simple.
Lo que propongo es usar un archivo httpHandler para devolver las imágenes… en lugar de mostrar la ruta completa de la imagen en el control IMAGE. Entonces cada control IMAGE debería estar solicitando la imagen de esta manera (en la página aspx):
<form id="form1" runat="server">

<asp:Image ID="Image1" ImageUrl="~/Handler.ashx?img=MI_IMAGEN.png" runat="server" />

</form>

Lo que estamos haciendo es pasar el nombre del archivo handler.ashx + un parámetro querystring en donde le digo cuál es el nombre de la imagen que quiero displayar.

Y el código del archivo handler.ashx sería:

Imports System.Web
Imports System.Web.Services

Public Class Handler
Implements System.Web.IHttpHandler, IRequiresSessionState

Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest

Dim Path As String = context.Server.MapPath("~/images/")
Dim img As String = context.Request.QueryString("img")

'si el usuario no está autentificado
If  IsNothing(context.Session("UserID")) Then img = "nodisponible.png"

        context.Response.ContentType = "image/png"

        context.Response.TransmitFile(Path & img)

End Sub

ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property

End Class

Dense cuenta que este handler puede acceder al objeto sesión porque también implementa IRequiresSessionState (más información).

Enter las ventajas que este método brinda tenemos:

1.- El usuario nunca sabrá cuál es la ruta en donde se almacena la imagen.

2.- Las imágenes no podrán ser referenciadas directamente en otros web sites (hot linking)

3.- Sólo la pueden ver usuarios autentificados.

4.- Se pueden hacer más verificaciones adicionales si se requieren.

Para este ejemplo básico he asumido que todas las imágenes están en formato png…

context.Response.ContentType = "image/png"

tú puedes mejorar el método para que sirva todo tipo de imágenes… aunque poner el tipo de salida no es necesario, el browser reconocerá el tipo de contenido… pero para mejor comprensión se estila poner.

En este post hemos demostrado un método para servir imágenes sólo a usuarios con una sesión autentificada. Y las imágenes simplemente son recuperadas de una ruta física en el servidor… podríamos hacer otro post en donde las imágenes se recuperen de la base de datos e igual se muestren a través del handler…, no les parece?
 ;)

lunes, 27 de julio de 2009

Burlando la autenticacion de sqlservercentral.com

ssc0 Para quienes no conocen sql server central, es un portal web con muchos recursos para desarrolladores y administradores de bases de datos.

Para poder visualizar los interesantes artículos de este portal, sólo hay que crear una cuenta de usuario, de manera gratuita… simple.

Hoy, desde google llegué a este sitio web, y como hace mucho tiempo que no estoy por sqlservercentral.com, olvidé la contraseña que antes usaba… y a veces demora el correo que te devuelve tu contraseña cuando lo solicitas…

Pero pensé que esto no me podía detener y decidí ver si podía burlar el formulario de autenticación para ver el artículo sin loguearme. Y lo logré. El truco lo hice sobre Internet Explorer 8, usando la herramienta Developer Toolbar de Microsoft.

En realidad no fue gran cosa, veamos:

1.- Si ingresan al sqlservercentral e intentan leer un artículo, pero no están autenticados, sólo podrán ver las primeras 3 líneas del artículo y además con un efecto de opacidad bastante molesto.

Yo hice las pruebas sobre esta página : http://www.sqlservercentral.com/articles/65804/

ssc2

Como ven en la imagen, he activado la barra Developer toolbar.. luego de ello, hice click sobre el ícono que se muestra seleccionado en la imagen… luego de ello, cuando pasen el cursor sobre la página web… notarán que se forman recuadros, esos recuadros son los distintos elementos html que han sido usados para darle la estructura a la página que estamos viendo, por ejemplo, el recuadro de la imagen muestra un elemento div:

ssc3

Ahora, traten de seleccionar el mismo área que yo he seleccionado en la imagen… si no lo pueden hacer… vayan a la developer toolbar y localizen la línea que tiene un div con id=”limitContainer”… hagan click allí y luego modifiquen los datos de la derecha, osea, quitenle el check igual como se muestra en la imagen:

ssc4

después de esto, ya deberían poder visualizar el artículo completo… sin necesidad de autenticarse.

Puedes hacer el mismo truco si usas Mozilla Firefox, con la herramienta FireBug.

Ahora, la pregunta es: Qué se hizo mal? por qué ocurre esto?

Podrían haber varias respuestas:

Una de las respuestas sería que no deberían haber traído el artículo entero… sino sólo un extracto, y recién recuperar el artículo completo de la base de datos después que el usuario se ha logueado correctamente.

Bueno, a ver si los de sqlservercentral arreglan ese asunto.

;)

martes, 29 de julio de 2008

Un pool para cada aplicación web ASP.NET

Empiezo el post reconociendo que el título es algo exagerado... pero sí, sí se puede hacer eso:
Ir al IIS y crear un pool de aplicaciones para cada aplicación web.

Para los que no sepan qué es un pool de aplicaciones, lo explico con mis palabras:
Es un gol de media cancha!! llegó con el IIS6.
consiste en agrupar o separar aplicaciones web ASP.NET en grupos... eso quiere decir que si tengo 5 pools de aplicaciones, y en cada pool tengo 10 aplicaciones, de las cuales por lo menos 1 aplicación de cada pool se está ejecutando en un momento dado... entonces habrán 5 procesos w3wp.exe (proceso ASP.NET) corriendo en la máquina servidor.
Exacto!!! se abre un proceso w3wp.exe aislado por cada pool de aplicación que tenga por lo menos una aplicación web corriendo.
Exacto!!! creamos un pool para que las aplicaciones web contenidas allí funcionen aisladas de las otras aplicaciones web...

Un proceso es una unidad aislada de código y datos ejecutándose... osea, consumo de memoria y recursos.

Qué le pasa niño Juanito, lo veo preocupado... quiere preguntar algo?
sí Profesor, profesor... por qué tienen que estar separando las aplicaciones en grupos o pools?
jaja, por muchos motivos:
No puedes meter aplicaciones hechas con el framework 1.1 y 2.0 en un mismo costal, hay que ponerlas en pools separados.

También hay que saber que crear pools de aplicaciones "a lo bestia" osea sin criterio, no es la solución a algo... porque cada pool de aplicaciones consume recursos... y ese consumo puede degradar la performance de las demás aplicaciones.
Entonces, primero hay que tener una buena maquina como servidor... sino en lugar de aprovechar esta bondad, estamos creando un problema.

Otro buen motivo para crear un pool de aplicaciones es para usar otra identidad con la que corre la aplicación ASP.NET. Claro, puede ser que necesitemos que el usuario bajo el cual corre ASP.NET tenga más derechos sobre el servidor que el usuario ASP.NET, entonces creamos un nuevo pool de aplicaciones e impersonamos el usuario con el que correrán por defecto las aplicaciones web de ese pool.
Quieres saber cómo se crea un pool de aplicaciones?
es muy simple... sólo abre el IIS, ve al nodo que dice Grupo de aplicaciones (Application pools) y hazle click derecho, añadir nuevo Pool de aplicaciones, le pones un nombre, le eliges bajo qué framework correrá y listo!
Luego puedes asignarle la identidad (usuario) bajo la cual correrán las aplicaciones que usen ese pool.

Ahora te estarás preguntando... cómo asocio una aplicación ASP.NET a un determinado pool de aplicaciones?
simple:
1.- Crea una aplicación ASP.NET con Visual Studio... ponle cualquier nombre
2.- Abre el IIS y ve a las propiedades de la aplicación que acabas de crear... y elige el pool de aplicaciones bajo el cual deseas que se ejecute:
Windows Server 2003 + IIS6
















Windows Vista + IIS7















Si deseas saber bajo qué identity está ejecutándose tu aplicación, puedes poner este código en tu páginas.aspx
Label1.Text = "Esta aplicación se ejecuta bajo la identidad del usuario : " & System.Security.Principal.WindowsIdentity.GetCurrent().Name

Qué es lo recomendable?
Tampoco hay que irse la extremo de crear un pool por cada aplicación web ASP.NET.
Lo recomendable es crear sólo unos cuantos pools de aplicaciones, según varios criterios, por ejemplo:
-un pool de aplicaciones para las aplicaciones que usan el framework 1.1 que están en producción.
-un pool de aplicaciones para las aplicaciones que usan el framework 2.0 que están en producción.
-un pool de aplicaciones para las aplicaciones que usan el framework 2.0 que aún están en desarrollo (claro, en el caso que no tengas un servidor de pruebas).
-Un pool de aplicaciones para una aplicación que sea crítica... osea, prefiero ponerla en un pool de aplicaciones sola porque me reduce el riesgo de caída, a la vez que me mejora la performance de la aplicación.

O sino también, puedo separar mis aplicaciones según un criterio de seguridad... es decir, en un pool pongo aplicaciones que requieran alto nivel de seguridad y en otro pool pongo las que no requieran mucha seguridad... uds vean cuál es la mejor distribución que pueden tener, según las características de las aplicaciones que manejan.

viernes, 11 de julio de 2008

Nivel de confianza Medium en ASP.NET - II

Decíamos en el anterior post que la manera recomendada de hostear múltiples aplicaciones ASP.NET sobre un servidor compartido era configurando el nivel de confianza a Medium. Esto, hay que decirlo, limita el uso de los recursos a los que se puede acceder... por ejemplo:
--No se puede acceder al proveedor de datos administrado ADO.NET OLEDB para acceder a bases de datos.
--No se puede acceder al log de eventos de Windows.
--No se puede usar reflection.
--No se puede acceder al registro de Windows.
--Sólo se puede comunicar con direcciones web externas especificadas en el elemento [trust] del web.config.
--Sólo se puede acceder a archivos que están dentro del directorio virtual de la aplicación.

Esas son las limitaciones, pero no es el fin del mundo... como ves también tiene muchos beneficios... y es que no podrán eliminarse tu aplicación web, ni hurgar en ella.
En realidad, decir que no se puede acceder al registro, ni al log de windows, etc...es mentir, sí se puede... a través de los que se llama políticas personalizadas. Sigue leyendo para que entiendas.

Digamos que tu aplicación necesita acceder a una base de datos ACCESS, para ello habría que usar OLEDB cierto?
pero hemos dicho que en un nivel de confianza Medium no se puede acceder a OLEDB... les mentí.... sí se puede... pero no por default, hay que crear una política personalizada del nivel Medium.
Veamos cómo se crea una política personalizada que lo haga:
1.- Ve a la ruta %windir%\Microsoft.NET\Framework\{Version}\CONFIG y copia el archivo web_MediumTrust.config... luego pégalo en la misma carpeta, ponle otro nombre... digamos oledb_mediumtrust.config
2.- Abre el archivo, fíjate que hay una sección [SecurityClasses], y dentro de ella están los elementos [SecurityClass... ahí debes añadir
[SecurityClass Name="OleDbPermission" Description="System.Data.OleDb.OleDbPermission, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/]
3.- Luego, busca el elemento [NamedPermissionSets], dentro hay elementos [PermissionSet... allí debes añadir
[IPermission class="OleDbPermission" version="1" Unrestricted="true"/]

4.- Ahora modifica el archivo web.config que está en esa misma carpeta, añadiremos la nueva política personalizada que acabamos de crear. Para ello nos vamos a la zona del elemento [securityPolicy] y añadimos el trustLevel de name = CustomOleDB, referenciamos el archivo copiado y renombrado inicialmente, así quedaría:

[securityPolicy]
[trustLevel name="Full" policyFile="internal"/]
[trustLevel name="High" policyFile="web_hightrust.config"/]
[trustLevel name="Medium" policyFile="web_mediumtrust.config"/]
[trustLevel name="Low" policyFile="web_lowtrust.config"/]
[trustLevel name="Minimal" policyFile="web_minimaltrust.config"/]
[trustLevel name="CustomOleDB" policyFile="oledb_mediumtrust.config" /]
[/securityPolicy]


5.- Nada más, acabamos de crear una política personalizada... ahora para usarlo desde tu aplicación web, simplemente pones en tu web.config lo sgte:
[trustLevel = "CustomOleDB" /]

Papayita verdad??
Eso es todo, para despedirme quería recordarles algo: si Ud quiere comprobar si su aplicación está preparada para desenvolverse al 100% en un ambiente Medium, haga las pruebas necesarias en su servidor de pruebas... para esto sólo añade a tu web.config:
[trust level="Medium" originUrl="" /]