Tips para evitar SQL Injection

En un gran porcentaje, la mayoría de nuestras aplicaciones necesitan acceder a las bases de datos para realizar consultas, actualizaciones e incluso eliminaciones de nuestros registros… El problema es cuando alguien desde fuera intenta hacer esas tareas por nosotros 😉

Voy a proponeros algunos tips a la hora de tratar los valores ajenos a nosotros desde el lado del servidor, para evitar este tipo de catástrofes. Por otro lado, cualquier tip adicional será bienvenido 😀

Convierte siempre el valor a su tipo correspondiente

Si por ejemplo estamos esperando un valor de tipo numérico, deberíamos intentar parsear este valor a dicho tipo para asegurarnos de que no incluye texto adicional:


var id = Request.QueryString["id"];

int result;

if (Int32.TryParse(id, out result))
    //Do things

Parametrizar las consultas SQL

Un error muy común es hacer uso de los valores que nos llegan sin especificar ningún tipo para el mismo. Algo parecido a esto:

var id = Request.QueryString["id"];
var query = "SELECT * FROM HOUSES WHERE ID=" + id;

Para evitarlo, podemos parametrizar las sentencias SQL y poder especificar de esta manera el tipo que estamos esperando para cada parámetro.


var objConnection = new OleDbConnection(strDbConnectionString);

objConnection.Open();

const string query = "SELECT * FROM HOUSES WHERE ID=?";

var objCommand = new OleDbCommand(query, objConnection);
var id = new OleDbParameter("@idParam", OleDbType.Integer) { Value = id};
objCommand.Parameters.Add(id);

var objReader = objCommand.ExecuteReader();;

Usar una cuenta con permisos restringidos a la base de datos

Otro dato importante a tener muy en cuenta es asegurarnos de que la cuenta de usuario utilizada por nuestra aplicación tiene los permisos necesarios para poder acceder y/o modificar unos datos concretos pero también que sea lo suficiente restrictiva para no alterar otro tipo de datos.

No mostrar al usuario la información de error generada por la base de datos

En muchos casos los mensajes de error pueden ser lo suficientemente descriptivos como para que el usuario se percate de información acerca de la estructura de la base de datos.

Rechazar las peticiones con caractéres sospechosos

Carácter especial Significado SQL
; Delimitador de consultas.
Carácter delimitador de cadena de datos.
Comentario.
/* */ Delimitadores de comentario. El texto entre /* y */ no es evaluado.
xp_ Se utiliza en el inicio del nombre de procedimientos almacenados extendidos de catálogo, como xp_cmdshell.

Espero que sea de utilidad 🙂

¡Saludos!

PDC 2010 online y en directo 28 y 29 de Octubre

Como cada año, desde el campus de Microsoft en Redmond, tiene lugar la Conferencia de Desarrolladores Profesionales (PDC). En este 2010 se celebrará los días 28 y 29 de Octubre.

Si bien otros años hemos podido seguir el evento de manera online, este año Microsoft Ibérica nos invita a sus oficinas para ver la retransmisión de la sesión inaugural 😀 ¡Regístrate a través de este enlace!

Si por el contrario Pozuelo de Alarcón te queda lo bastante lejos como para no poder acompañarnos os invito a seguir el evento de manera online en la siguiente dirección.

En cualquier caso, el acontecimiento comenzará a las 18:00 (hora peninsular española) el 28 de Octubre. Para más información os adjunto la agenda del evento.

Por otro lado, para estar al corriente de las ultimas noticias, podemos seguir la cuenta @PDCEvent de Twitter y utilizar el hashtag #PDC10 para enviar nuestros comentarios.

¡Saludos!

Fault domains & Update domains

Ahora que poco a poco vuelvo a retomar mi vida, mi trabajo y la parte de Windows Azure :P, me gustaría hablaros de estos dos términos utilizados dentro de la nube. Si bien la plataforma de Windows Azure nos permite abstraernos de la infraestructura es necesario conocer algunos aspectos de su funcionamiento. Lo primero que debemos tener claro es qué es un role. Un role es en realidad una aplicación dentro de nuestro proyecto Cloud Service con una configuración personalizada. Como cualquier solución, podemos crear varios proyectos de distinto tipo. Con la plantilla de Visual Studio podemos ver claramente cuáles son los proyectos disponibles a día de hoy:

Por cada uno de esos roles podemos elegir el número de instancias que queremos desplegar en la nube, lo que supondrá una máquina virtual por instancia. Para modificar este valor, nos ubicamos en el proyecto de Cloud Service y, dentro de la carpeta Roles, hacemos doble clic en el rol que queramos configurar.

Ahora bien, cuando tenemos dos o más instancias de uno o más roles Windows Azure, más concretamente una parte llamada Fabric Controller, distribuye las instancias en diferentes racks en una determinada posición para conseguir la mayor disponibilidad del servicio en caso de fallo físico, lógico, debido a actualizaciones de los sistemas e incluso a nuevos deploys. Dicho esto ¿Qué es un Fault Domain y un Update Domain?

Fault Domain

Este término puede ser relacionado con el rack físico donde se alojan las instancias de los distintos roles. El objetivo del Fabric Controller es distribuir las instancias en estos faults domains a través de un algoritmo con el fin de no repetir la misma ubicación para instancias de un mismo rol en un mismo sitio físico. Por otro lado, si uno de los fault domains presenta algún problema de hardware que impida a una de nuestras instancias funcionar con normalidad, tendríamos el resto de ellas distribuidas por los otros fault domains con el objetivo de seguir prestando servicio. Cuando el Fabric Controller se percate de este incidente actuará para reubicar la instancia de nuestro rol en un fault domain operativo.

Update Domain

Si bien un fault domain puede relacionarse con una unidad física (un rack) donde quedan almacenados diferentes roles, procurando no duplicar varias instancias de uno de los integrantes, el término update domain trata de una unidad lógica donde Windows Azure consigue agrupar un conjunto de instancias de distintos roles con el fin de poder realizar una actualización progresiva sin anular el servicio de un role. Dicho de otra forma: El conjunto de instancias que forman un update domain consta de varios roles distintos con el fin de poder actualizar los mismos dejando operativos el resto de instancias de dichas aplicaciones. Podemos configurar el número de update domains a través del archivo ServiceDefinition.csdef. El número por defecto es 5 si no especificamos ningún valor.
<ServiceDefinition name=»CloudService» upgradeDomainCount=»2″
xmlns=»http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition»>

En este caso el Fabric Controller procurará tener las instancias de manera equilibrada entre el número de dominios de actualización para seguir manteniendo el servicio activo.


(Click para agrandar)

Si nos fijamos en la imagen anterior, podemos ver que tenemos 4 fault domains y 4 roles distintos con 4 instancias ubicadas en los distintos racks sin repetición en cuanto a la posición dentro del fault domain y el update domain. ¡Saludos!

Cómo enviar peticiones utilizando OAuth: Firmando peticiones

En el mes de Mayo intenté mostrar cómo podíamos recuperar los tokens de autenticación, tanto para Twitter como para Yahoo, con el objetivo de hacer uso del protocolo abierto OAuth. A pesar de todo, es posible que aún tengamos dudas sobre cómo realizar nuestras peticiones y nos hagan falta algunos tips a tener en cuenta 🙂 En este post me centraré en Twitter.

En el apartado de documentación podemos ver una barra lateral derecha donde se nos muestran todas las acciones disponibles, la URL a la cual debemos realizar la petición y el HTTP Verb necesario:

 

Para este ejemplo, voy a mostrar cómo sería para el caso más común que es actualizar
el estado
.

En primer lugar, voy a crear una aplicación de consola donde vamos a llamar a
un wrapper creado para interactuar con la API y que a su vez utilizará la clase
implementada para el protocolo OAuth en el
post anterior
.

using System;
using TwitterWrapper;

namespace TwitterClient
{
    class Program
    {
        static void Main()
        {
            const string status = "Testing my twitter client with #OAuth and ¡special characteres! ¿*?";

           var result = new Twitter().UpdateStatus(status);

            Console.WriteLine("The status was successfully updated!");
            Console.WriteLine("ID: {0}", result.Id);
            Console.WriteLine("Date: {0}", result.Created);
            Console.WriteLine("Status: {0}", result.Text);

            Console.ReadLine();
        }
    }
}

Como podéis ver, he creado una clase llamada Twitter desde la cual llamo al método UpdateStatus. El objetivo del mismo es reunir los parámetros necesarios para poder construir la petición y que posteriormente sea firmada.

using System.IO;
using System.Net;
using System.Web;
using System.Xml.Serialization;
using OAuthTools;
using TwitterWrapper.Resources;

namespace TwitterWrapper
{
    public class Twitter
    {
        private const string ConsumerKey = "yourConsumerKey";
        private const string ConsumerSecret = "yourConsumerSecret";
        private const string AccessToken = "yourAccessToken";
        private const string AccessTokenSecret = "yourAccessTokenSecret";

        public Status UpdateStatus(string status, WebProxy webProxy = null)
        {
            var query = string.Format("http://api.twitter.com/1/statuses/update.xml?status={0}", HttpUtility.UrlEncode(status));
            var request = new OAuth(ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret);
            var result = request.PostResource(query);

            return Serialize(result);
        }

        public Status Serialize(string xml)
        {
            var xmlSerializer = new XmlSerializer(typeof(Status));
            var reader = new StringReader(xml);
            var status = (Status)xmlSerializer.Deserialize(reader);

            return status;
        }
    }
}

Una vez que la petición ha sido realizada, intentamos almacenar el resultado
XML dentro de la clase Status gracias a XmlSerializer y los XmlAttributes en los atributos que
necesitemos almacenar en nuestra aplicación.

Tip: Es muy importante hacer uso de
HttpUtility.UrlEncode para recuperar los valores Unicode del
status que acabamos de pasarle al método. De lo contrario algunos caracteres especiales podrían interpretarse
de un modo erróneo y serán omitidos de la petición
. Por ejemplo, uno de
los casos que me estaba ocurriendo, es al intentar introducir un hashtag. Al no
pasar esta información en formato Unicode, si intentamos transformar el string
en Uri interpretará el símbolo # y todo lo que venía detrás como un ancla ;)

Si bajamos una capa más en el código, llegamos a la clase OAuth pero con un
nuevo método para las peticiones POST llamado PostResource.

/* POST Requests */
public string PostResource(string url)
{
    var uri = new Uri(url);
    var parameters = GetOAuthParameters(HttpMethod.Post, url, HttpUtility.ParseQueryString(uri.Query), _accessToken, _accessTokenSecret);
    url = string.Format("{0}://{1}{2}", uri.Scheme, uri.Authority, uri.AbsolutePath);
    var oAuthRequest = new OAuthRequest(parameters, url, HttpMethod.Post, proxy: _proxy);
    var queryParameters = HttpUtility.ParseQueryString(uri.Query);
    var response = oAuthRequest.GetResponse(queryParameters);

    return response;
}

En esta función vamos a formatear el parámetro de entrada (url de Twitter +
 el nuevo estado) dentro de la clase Uri y posteriormente vamos a recuperar los
parámetros para el protocolo OAuth que irán incluidos en la cabecera.

Una vez que tenemos generados estos parámetros (nonce, timestamp, la firma,
etc.) podemos hacer la llamada a través del método POST utilizando la clase
oAuthRequest. Crearemos una instancia de dicha clase con los
parámetros recopilados hasta el momento y por último llamaremos a GetResponse
para obtener el resultado.

using System;
using System.Text;
using System.Collections.Specialized;
using System.Net;
using System.IO;

namespace OAuthTools
{
    public class OAuthRequest
    {
        private readonly HttpWebRequest _request;
        private static byte[] _content;

        public string GetResponse(NameValueCollection queryParameters = null)
        {
            try
            {
                /*POST method*/
                if (queryParameters != null)
                {
                    var sb = new StringBuilder();
                    for (var i = 0; i < queryParameters.AllKeys.Length; i++)
                    {
                        var key = queryParameters.AllKeys[i];

                        sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(queryParameters[key]));

                        if (i < queryParameters.Count - 1)
                            sb.Append("&");

                    }

                    _content = Encoding.ASCII.GetBytes(sb.ToString());
                    _request.ContentLength = _content.Length;

                    using (var stream = _request.GetRequestStream())
                    {
                        if (_request.ContentLength > 0)
                            stream.Write(_content, 0, _content.Length);
                    }

                }

                using (var response = _request.GetResponse())
                {
                    var stream = response.GetResponseStream();
                    using (var reader = new StreamReader(stream))
                    {
                        return reader.ReadToEnd();
                    }
                }
            }
            catch (WebException ex)
            {
                //If something is wrong, We'll retrieve the server response.
                /*    HTTP 400 Bad Request
                        o Unsupported parameter
                        o Unsupported signature method
                        o Missing required parameter
                        o Duplicated OAuth Protocol Parameter
                      HTTP 401 Unauthorized
                        o Invalid Consumer Key
                        o Invalid / expired Token
                        o Invalid signature
                        o Invalid / used nonce
                */
                if (ex.Response is HttpWebResponse)
                {
                    using (var reader = new StreamReader(ex.Response.GetResponseStream()))
                    {
                        var result = reader.ReadToEnd();
                        return result;
                    }
                }

                throw;
            }
        }

        //  2. For each parameter, the name is immediately followed by an ‘=’ character (ASCII code 61), a ‘”’ character (ASCII code 34), the parameter value (MAY be empty), and another ‘”’ character (ASCII code 34).
        //  3. Parameters are separated by a comma character (ASCII code 44) and OPTIONAL linear whitespace.
        //  4. The OPTIONAL realm parameter is added
        /// <summary>
        /// The OAuth Protocol Parameters are sent in the Authorization  header the following way:
        /// 1.  Parameter names and values are encoded.
        /// </summary>
        /// <param name="parameters">oAuthParameters</param>
        /// <param name="url"></param>
        /// <param name="method">Http Method</param>
        /// <param name="proxy"></param>
        public OAuthRequest(NameValueCollection parameters, string url, string method, IWebProxy proxy = null)
        {
            ServicePointManager.Expect100Continue = false;
            ServicePointManager.UseNagleAlgorithm = false;

            var header = new StringBuilder();

            header.Append("OAuth ");

            for (var i = 0; i < parameters.Count; i++)
            {
                var key = parameters.GetKey(i);
                header.Append(string.Format("{0}="{1}"", key, parameters[key]));

                if (i < parameters.Count - 1)
                    header.Append(",");
            }

            _request = CreateHttpWebRequest(url, method, proxy);

            _request.Headers["Authorization"] = header.ToString();

        }

        private static HttpWebRequest CreateHttpWebRequest(string url, string method, IWebProxy proxy = null)
        {
            var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
            httpWebRequest.Method = method.ToUpper();

            if (proxy != null) httpWebRequest.Proxy = proxy;

            if (method == HttpMethod.Post)
                httpWebRequest.ContentType = "application/x-www-form-urlencoded";

            return httpWebRequest;
        }
    }
}

Con esta clase podríamos realizar tanto peticiones GET como POST, donde en
las segundas se envía el cuerpo del mensaje.

Para poder ver el ejemplo completo, he actualizado el proyecto de CodePlex.

Espero que sea de utilidad :D

¡Saludos!