• Publicidad

Seguridad CGI

Todo lo relacionado con el desarrollo Web con Perl: desde CGI hasta Mojolicious

Seguridad CGI

Notapor memrobles » 2015-12-07 19:53 @870

Hola, buenas tardes.

Soy nuevo en Perl y estoy interesado en publicar unas tablas de MySQL en la web con Perl. Empezaré por el login. La verdad que este código lo he encontrado en este mismo foro. Me gustaría saber que tan seguro es usar este código:

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2.     #
  3.     # Ejemplo de CGI::Session para login
  4.     #
  5.     use strict;
  6.     use warnings;
  7.     use diagnostics;
  8.      
  9.     use CGI::Session;
  10.     use CGI ':standard';
  11.     use CGI::Carp qw'fatalsToBrowser warningsToBrowser';
  12.      
  13.      
  14.     ## Creamos el objeto CGI
  15.     my $cgi = CGI->new();  
  16.      
  17.     ## Creamos el objeto CGI::Session
  18.     my $session = CGI::Session->new("driver:File", $cgi, {'Directory'=>'/tmp/'})
  19.         or die CGI::Session->errstr;
  20.      
  21.     ## Enviamos la cookie de sesión al usuario
  22.     print $session->header;
  23.      
  24.     # Aquí guardaremos el perfil del usuario
  25.     my $perfil;
  26.      
  27.     ## Inicialización
  28.     if ( not $session->param('~registrado') ) {     # si el usuario no está registrado
  29.      
  30.         # vemos si se está registrando en este momento
  31.         if (my $nombre = $cgi->param('login_nombre')) {
  32.             my $passwd = $cgi->param('login_passwd');  
  33.      
  34.             # Vemos si es un usuario conocido por nosotros
  35.             # si lo es, obtenemos su perfil
  36.             if ($perfil = usuario_registrado($nombre, $passwd)) {
  37.                
  38.                 # Guardamos el perfil asociado a la sesión
  39.                 $session->param('perfil', $perfil);
  40.                 $session->param('~registrado', 1);
  41.             }
  42.             else {  # Es un desconocido, le pedimos que rellene el registro otra vez
  43.                 presenta_login('Usuario desconocido');
  44.                 exit;
  45.             }
  46.         }
  47.         else {      # No está registrado ni se está registrando
  48.            presenta_login('Identifíquese');
  49.            exit;
  50.         }
  51.     }
  52.     else {          # es un usuario registrado (hay una sesión de él)
  53.         # recuperamos el perfil, desde la sesión
  54.         $perfil = $session->param('perfil');
  55.     }
  56.      
  57.      
  58.     # A partir de aquí, sabemos qué usuario es, y su perfil
  59.     # Podemos presentarle las opciones propias de un usuario registrado
  60.     my $nombre = $perfil->{nombre};         # otra forma de hacerlo sería recuperar esta información
  61.     my $email  = $perfil->{email};          # desde la base de datos, ya que sabemos qué usuario es
  62.      
  63.     if ($cgi->param('Desconectar')) {       # caso de que el usuario quiera desconectarse
  64.         $session->clear(['~registrado']);   # olvidamos que estaba registrado
  65.      
  66.         print       # despedida
  67.             start_html('Desconexión'),
  68.             h1('Desconexión'),
  69.             hr(),
  70.             p("Adios, $nombre"),
  71.             end_html()
  72.             ;
  73.     }
  74.     else {          # una página normal
  75.         print
  76.             start_html('Bienvenido'),
  77.             h1('Bienvenido'),
  78.             hr(),
  79.             p("Bienvenido $nombre ($email)"),
  80.             hr(),
  81.             p(a({-href=>'ficheros.html'},'Ver lista de ficheros')),
  82.             p(a({-href=>$cgi->url() . '?Desconectar=1'},'Desconectar')),
  83.             end_html()
  84.             ;
  85.     }
  86.      
  87.     # Perfil de un determinado usuario
  88.     sub usuario_registrado {
  89.         my ($nombre, $passwd) = @_;
  90.      
  91.         # aquí consultaríamos una base de datos, por ejemplo
  92.         if ($nombre eq 'JF'  and  $passwd eq '2010') {
  93.             # si es un usuario en nuestra base de datos, recuperamos su perfil
  94.             return { nombre => $nombre, email => '[email protected]' };
  95.         }
  96.          
  97.         return;
  98.     }
  99.      
  100.     # Presenta el formulario de entrada
  101.     sub presenta_login {
  102.         my $titulo = shift;
  103.      
  104.         print
  105.             start_html,
  106.             h1('Registro de entrada: ' . $titulo),
  107.             start_form,
  108.             p('Nombre: '     . textfield('login_nombre')),
  109.             p('Contraseña: ' . password_field('login_passwd','',8,8)),
  110.             p(),
  111.             submit('Entrar'),
  112.             end_form,
  113.             end_html()
  114.             ;
  115.     }
Coloreado en 0.004 segundos, usando GeSHi 1.0.8.4


La subrutina usuario_registrado, la voy a reemplazar por un consulta MySQL: Es aquí donde tengo duda si alguien puede descargar el código para hackear el usuario y contraseña, ¿qué podría hacer?

Saludos y gracias.
memrobles
Perlero nuevo
Perlero nuevo
 
Mensajes: 27
Registrado: 2015-12-07 15:05 @670

Publicidad

Re: Seguridad CGI

Notapor explorer » 2015-12-08 07:19 @347

Bienvenido a los foros de Perl en Español, memrobles.

Efectivamente: no hay que fiarse de lo que llega de fuera.

Perl tiene un modo de funcionamiento llamado 'tainted' (manchado, entintado o contaminado), por el cual, todo lo que llega del exterior se considera que está "manchado". Y manchará todo lo que toque dentro de Perl a menos que sepamos cómo "limpiarlo".

(el modo 'taint' se explica en la sección 'Taint mode' del documento perlsec.pod Usar perldoc perlsec desde la línea de comandos).

Podemos activarlo colocando un '-T' en la primera línea del programa: !/usr/bin/perl -T

A partir de ese momento, las variables que reciban información del exterior estarán "contaminadas".

Entonces lo que hacemos es "lavarlas" para quitarle la suciedad. En nuestro caso, podemos decir que la condición básica que deben cumplir tanto el $nombre como el $passwd del usuario es que se componen exclusivamente de caracteres alfanuméricos. Momento ideal para aplicar una sencilla expresión regular:

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1.         if ($nombre =~ /^([\w.]+)$/) {           # el $nombre debe contener solo caracteres alfanuméricos, más el punto
  2.             $nombre = $1;                        # ahora $nombre queda limpio
  3.         }
  4.         else {
  5.             warn "Datos erróneos en '$nombre'";  # registramos algo extraño
  6.             return;                              # regresamos con error
  7.         }
  8.  
  9.         # lo mismo para $passwd
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
(ejemplo sacado de la sección 'Laundering and Detecting Tainted Data' en perlsec).

Hay otro nivel de seguridad más: la propia base de datos.

Mucha gente aplica lo que le llega del exterior en la consulta a la base de datos:

my $sth = $dbh->prepare("SELECT * FROM usuarios WHERE nombre='$nombre' AND password='$passwd'"); # ¡NO HACER ESTO! ¡CACA!

y es donde vienen los problemas: $nombre o $passwd pueden contener parte de código SQL, que es lo que se llama inyección de código.

Lo que se puede hacer es usar el 'binding' de los argumentos:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
my $sth = $dbh->prepare('SELECT * FROM usuarios WHERE nombre = ? AND password = ?');
$sth->execute($nombre, $passwd);
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
(Ver la sección 'DESCRIPTION' en DBD::mysql). Ellos se encargarán de "escapar" los caracteres extraños.

Hilo relacionado: Problemas con Taint mode en CGI usando Perl 5.14 en Windows
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Re: Seguridad CGI

Notapor memrobles » 2015-12-20 22:12 @966

Gracias, explorer.

Ya se valida el número de caracteres, lo puse solo a 4 dígitos alfanuméricos, pero no puedo hacer que valide desde la base :cry: Esto es lo tengo.

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. sub usuario_registrado {
  2.     my ($nombre, $passwd) = @_;
  3.     my $username;
  4.     my $password;
  5.  
  6.     my $dsn = "DBI:mysql:database=basetest;host=127.0.0.1";
  7.     my $dbh = DBI->connect($dsn, "usertest", "123456");
  8.     my $sql = q{select count(*) from users where username = ? and password = ?};
  9.  
  10.     if ($nombre =~ /\w{4,}/) {          # el $nombre debe contener solo 4 caracteres alfanuméricos
  11.         #$nombre = $1;                  # ahora $nombre queda limpio # este linea la comente por que no me loguea
  12.         if ($dbh->selectcol_arrayref($sql, undef, $username, $password)->[0] == 1) {
  13.             print "login"; # logeado
  14.             return { nombre => $nombre, email => '[email protected]' };
  15.         }
  16.         else {
  17.             print "login incorret"; return;   # login error
  18.         }
  19.     else {
  20.         warn "Datos erróneos en '$nombre'";  # registramos algo extraño
  21.         return;                              # regresamos con error
  22.     }
  23.     return;
  24. }
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


Ya no sé por dónde más buscarle. ¿Alguien me podría ayudar, por favor?
memrobles
Perlero nuevo
Perlero nuevo
 
Mensajes: 27
Registrado: 2015-12-07 15:05 @670

Re: Seguridad CGI

Notapor explorer » 2015-12-21 03:37 @192

Pues porque en ningún sitio estás estableciendo el valor para la variable $username ni $password.

Quedaría algo así (no probado):
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. sub usuario_registrado {
  2.     my($nombre, $passwd) = @_;
  3.  
  4.     if ($nombre =~ /^\w{4}$/) {                 # el $nombre debe contener 4 caracteres alfanuméricos
  5.  
  6.         my $dsn = "DBI:mysql:database=basetest;host=127.0.0.1";
  7.         my $dbh = DBI->connect($dsn, "usertest", "123456");
  8.         my $sql = $dbh->prepare('select count(*) from users where username = ? and password = ?');
  9.         my $res = $dbh->selectcol_arrayref($sql, undef, $nombre, $passwd);
  10.  
  11.         if ($res->[0] == 1) {
  12.             print "logged";                     # logeado
  13.             return { nombre => $nombre, email => '[email protected]' };
  14.         }
  15.         else {
  16.             print "login incorrect"; return;    # login error
  17.         }
  18.     else {
  19.         warn "Datos erróneos en '$nombre'";    # registramos algo extraño
  20.     }
  21.  
  22.     return;                                     # regresamos con error
  23. }
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4

Además ponemos un par de anclas en la expresión regular, para ajustar el nombre de usuario a exclusivamente lo que queremos.22
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España


Volver a Web

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 4 invitados