Mostrando entradas con la etiqueta web. Mostrar todas las entradas
Mostrando entradas con la etiqueta web. Mostrar todas las entradas

lunes, 17 de diciembre de 2012

Es guay utilizar algoritmos de digest

Las funciones hash o algoritmos de digest tienen muchas aplicaciones y sobre todo, son guay. En esta entrada estaré hablando del uso de las funciones hash y los datos de validación.

Las Funciones Hash Y Las Contraseñas
Imagina que eres un intruso con pocos privilegios dentro de un servidor.  Encuentras un programa que posee privilegios para apagar el sistema, pero necesita clave, por eso examinas su código:
#!/usr/bin/env ruby

print 'Password '
exec 'shutdown -h now' if gets.chomp == 'adios'

puts 'Wrong password, try again.'
 Y ahí está la clave, ahora la cuestión es ¿Cómo reparar el script de modo que otras personas con privilegios de lectura no puedan ver la contraseña? Pues es simple en realidad, con una función hash codificas la contraseña y a la hora de comprobar, codificas la entrada y luego compruebas el hash resultante con el hash previamente calculado, así:
#!/usr/bin/env ruby
require 'digest/sha2'

# pass = newbieshell
PASSWORD = 'eeedeff1fde3065be0afcfc56fc775fb6de1f449bcb3f1845a8503e113bd8236'

print 'Password: '
sha2 = Digest::SHA256.hexdigest(gets.chomp)

exec 'shutdown -h now' if PASSWORD == sha2

puts 'Wrong password, try again.'
De esta forma el usuario no podrá ver la contraseña, para eso deberá recurrir a la fuerza bruta y si la clave es seguraen, le será muy difícil hallarla, incluso si cuenta con todo procesamiento del mundo .

Guardando Información En La Base De Datos
Cualquier ordenador conectado al Internet es vulnerable, por más seguridad y cosas que se hagan para protegerlo, seguirá siendo vulnerable; es la pura realidad. Por esta razón, también se deberá poner hincapié en proteger la información sensible; poner todo lo más difícil posible para cuando penetren el sistema (!).

Pues bien, lo primero que se debe hacer es, poner clave de acceso a todos los servicios y base de datos, incluso si solo se pueden acceder de manera local. Codificar con una función hash todos los campos de las base de datos que solo se utilizan para validar, p. ej. nombre de usuario, contraseña, correo.

De esta forma, si sufrimos un ataque de inyección SQL u otro de la misma naturaleza y tenemos todo los campos de validación codificados con una función hash, cuando el atacante obtenga la información, tendrá todo un reto en frente de él.

miércoles, 5 de diciembre de 2012

Cómo implementar un URL Shortener funcional con Sinatra y Redis

En esta entrada estaremos implementando un servicio web muy común, URL Shortening. Para lograr nuestros objetivos estaremos utilizando Ruby, Sinatra y Redis.

¿Por qué Sinatra?
 Es cierto que hay entornos más completos y funcionales que Sinatra, pero la pega está en que los entornos populares como Rails requieren una pila de dependencias enorme y están orientados para aplicaciones web un poco más complejas y extensas. Sinatra es la opción ideal debido a su simplicidad y su cantidad mínima de dependencias. más información

¿Por qué Redis?
Si bien se podría usar un Hash interno, que de manera más eficiente lleve el registro de todo. El uso de Redis agrega unas cuantas ventajas muy atractivas, como la persistencia de datos, tiempo de vida para los datos y la capacidad de acceder externamente y realizar métricas sobre los datos. más información

El Algoritmo
De toda la aplicación, esta es la parte mas fácil de implementar, pero sin embargo, es la que requiere un poco más de cuidado de nuestra parte. Una mala elección en el algoritmo interno podría resultar en procesamiento innecesario, perdidas de tiempo y por que no, problemas de mantenimiento. A continuación listo algunos acercamientos junto con sus pros y contras.

  1. Strings Aleatorios
    - PROS
             - Fácil de implementar.
    - CONS
             - Se debe comprobar mucho.
             - Se podría necesitar generar varias cadenas aleatorias para obtener una disponible.
  2. Strings Secuenciales
    - PROS
             - Fácil de implementar.
             - No requiere comprobaciones.
    - CONS
             - Cualquier usuario podría predecir direcciones válidas.
  3. Permutación De Carácteres
    - PROS
             - Fácil de implementar.
             - No requiere comprobaciones.
             - Las direcciones no se pueden predecir.
    - CONS
             -
    Para implementar el tercer acercamiento, solo basta con usar el método Array#permutation para generar las secuencias únicas, y para mitigar el problema de la predictibilidad, se baraja el array de carácteres con el método Array#shuffle y listo, solo queda iterar por todas las permutaciones.
    class Url
         def initialize(n=1)
              @n = n
              @seq = mkseq(@n)
         end
    
         def next
              begin
                   @seq.next.join
              rescue StopIteration   
                   @n += 1
                   fail 'No more resources available!' if @n > CHARS.length
    
                   @seq = mkseq(@n)
                   self.next()
              end
         end
    
         private
    
         def mkseq(n, random=true)
              if random
                   CHARS.shuffle.permutation(n)
              else
                   CHARS.permutation(n)
              end
         end
    
         CHARS = [*(0..9), *('A'..'Z'), *('a'..'z')]
    end 
    Es muy importante señalar que es una mala idea convertir el resultado del método Array#permutation en un array, pues podría resultar en una perdida de tiempo del orden de minutos o días. Bueno, implementado ya el corazón de la aplicación, solo nos queda manejar las peticiones con Sinatra y unir todo.

    La mecánica es la siguiente: Cuando un usuario hace una petición a nuestro servidor a través de una URL corta, se toma la segunda parte de la dirección — el recurso o path y se hace una petición a Redis, si dicho recurso existe, el usuario es redirigido al enlace devuelto por Redis utilizando el método redirect, si no existe, entonces en vez de redirigir al usuario, se presenta un aviso con información al respecto.
    #!/usr/bin/env ruby
    #encoding: UTF-8
    require './url.rb'
    require 'redis'
    
    rd_child, wr_parent = IO.pipe()
    rd_parent, wr_child = IO.pipe()
    
    if fork()
         rd_child.close
         wr_child.close
    
         resources = Url.new
         loop do
              IO.select([rd_parent])
              rd_parent.read(1)
              wr_parent.write(resources.next)
         end
    else
         rd_parent.close
         wr_parent.close
    
         require 'sinatra'
    
         redis = Redis.new
    
         set :static, true
         set :public_folder, File.join(File.dirname(__FILE__), 'static')
    
         get '/' do
              erb :index #template
         end
    
         get '/:resource' do
              @url = redis.get(params[:resource])
    
              if @url
                   redirect(@url)
              else
                   halt 400, '<html><head><title>Error</title></head><body><h1>Not Found</h1></body></html>'
              end
         end
    
         post '/create' do
              begin
                   URI.parse params[:url]
              rescue
                   halt 400, '<html><head><title>Error</title></head><body><h1>Bad Url</h1></body></html>'
              end
    
              wr_child.write('g')
              IO.select([rd_child])
              @resource = rd_child.sysread(512)
              redis.set(@resource, params[:url])
    
              erb :index   # tamplate
         end
    end
    Hay un pequeño inconveniente del que no se habla mucho en la documentación oficial de Ruby. Los Fibers no se pueden utilizar a través de múltiples hilos, por ejemplo si intentas ejecutar el siguiente código, producirá un error:
    fiber = nil
    Thread.new do
         fiber = Fiber.new do { Fiber.yield 1 }
    end
    fiber.resume # FiberError: fiber called across threads
    
    Debido a que el método Array#permutation utiliza Fibers y algunos servidores web como Thin usan Threads, en efecto,  para evitar errores en tiempo de ejecución está la decisión de crear dos procesos en el código de arriba, uno para ejecutar Sintra y otro exclusivamente para generar combinaciones. Bien, ahora solo queda la parte web; el front end.

    gina Web
    Bueno, como realmente el diseño es para los diseñadores, me haré de una página que genere una interfaz más o menos aceptable para luego descargar el código y modificarlo a gusto.

    Para lograr una interfaz parecida a la de esta entrada, visita http://www.phpform.org/ y selecciona a gusto los elementos de la página principal, descarga el código y personifícalo, recuerda, la página principal debe quedar lo más simple posible.

    Una cosa más, para mostrar la nueva url al usuario, se utiliza el valor almacenado por la aplicación en la variable de instancia @resource. Cada vez que se acorta un enlace, dicha variable es accesible desde el view, en mi caso el fichero index.erb — el cual elaboré a partir del código generado por la página phpform —. El siguiente es un fragmento del código necesario para mostrar la URL recién creada en el pie (footer) de la página desde el view index.erb.

    <div id="footer">
         <% if @resource %>
              <% url = "http://localhost:4567/#@resource" %>
              <p><a href="<%=url%>"><%=url%></a></p>
         <% else %>
              <p><a href="">Submit new url</a> </p>
         <% end %>
    </div>

    Descargar Aplicación De La Entrada
    Actualización 9/12/12
     Uso del método Array#permutation en lugar del método Array#combination.

    miércoles, 18 de julio de 2012

    Distancia de edición

    adminitrador: Saludos User123
    user123: Saludos
    adminitrador: disculpe por las molestias, pero lamento informaros que nuestra base de datos ha sido comprometida y por lo que parece, por una persona de nuestro equipo.
    user123: Lo lamento, pero y  ¿por qué me estás diciendo eso? no tengo nada que ver con lo que ha sucedido.
    adminitrador: estamos llevando una investigación a fondo, y como se podrá . . .

    ¡Espera un momento! ¿Quién diablos es ese? ¡administrador sin s!

    Bueno, sin duda ese era alguien un poco más astuto de la cuenta, y estoy seguro de que hubo personas que no pudieron percibir ese pequeño detalle mientras leían la parte de arriba.

    El robo de identidad es algo muy común en la red y muchas veces es muy fácil de llevar a cabo, veamos como podemos proteger a los usuarios y nuestra web, en lo posible, de este tipo de cosas utilizando la distancia de edición de Levenshtein, o simplemente distancia de Levenshtein:

    Antes de comenzar, necesitarás la siguiente gema:
    gem install levenshtein
    Continuando, según el ejemplo del timador de arriba, para nuestros fines, consideraremos insegura cualquier palabra que necesita dos o menos cambios para convertirse en otra.

    Primero probemos un poco con irb:
    irb(main):001:0> require 'levenshtein'
    => true
    irb(main):002:0> Levenshtein.distance("administrador", "adminitrador")
    => 1
    irb(main):003:0> Levenshtein.distance("webmaster", "wepmaster")
    => 1
    irb(main):004:0> Levenshtein.distance("administrador", "administradores")
    => 2
    irb(main):005:0>
    
    Viendo los resultados, podríamos concluir que si tenemos un usuario de nombre webmaster, sería inseguro aceptar otro usuario de nombre wepmaster, de la misma manera para: administrador y adminitrador; administrador y administradores.

    Preparemos ahora otro ejemplo, pero esta vez uno que lea una lista de usuarios y de ellas nos diga si es seguro aceptar el usuario o no.
    require 'levenshtein'
    
    RULE= 2
    
    def safe_name?(pool, username, rule=RULE)
         not pool.any? do|name|
              Levenshtein.distance(name, username) <= rule
         end
    end
    
    if ARGV.empty?
         $stderr.puts "Uso: ruby #{$0} <usuario>"
         exit
    end
    
    # pool = IO.read('lista_usuarios.txt').scan(/\w+/)
    
    pool = %w{ administrador abogado policia admin webmaster
               paypal google yahoo amazon dios jesus gandhi}
    
    if safe_name?(pool, ARGV[0].downcase)
         puts "El nombre de usuario `#{ARGV[0]}' es seguro."
    else
         puts "`#{ARGV[0]}' no es un nombre de usuario seguro."
    end
    

    ~ $ ruby edit_distance.rb googel
    `googel' no es un nombre de usuario seguro.
    
    $ ruby edit_distance.rb jesu
    `jesu' no es un nombre de usuario seguro.
    
    $ ruby edit_distance.rb gandi 
    `gandi' no es un nombre de usuario seguro.
                                            
    ~ $ ruby edit_distance.rb abogados
    `abogados' no es un nombre de usuario seguro.
    

    Conclusión

    Últimamente los entornos de trabajo modernos (frameworks) integran mecanismo como este, de modo que la responsabilidad de comprobar este tipo de cosas recae en el sistema y no en el programador, pero nunca está de más estar alerta y consciente de los lugares de entrada y la forma de tapar esos huecos.

    Ya con esto haremos que nuestros usuarios estén un metro más lejos de las garras de personas malintencionadas y brindaremos una capa más de seguridad a nuestra aplicación. Para aquellos que utilicen PHP en vez de Ruby, buenas noticias, PHP incluye en su librería estándard la función levenshtein() .

    Enlaces Útiles