03 octubre 2008

Monitorizando aplicaciones con Telnet

El protocolo de comunicación Telnet (TELecommunication NETwork) fue diseñado inicialmente para acceder a una máquina remota por medio de comandos de teclado a través de una consola virtual utilizando para ello el puerto 23. Fue muy utilizado en los comienzos de Internet para administrar servidores Unix desde terminales remotos.

Aunque es un protocolo algo obsoleto (comparado con monitorizar ordenadores con programas como VNC o Terminal Server) si que podemos sacarle partido para controlar el estado de nuestras aplicaciones a través de Internet accediendo a la ordenador de nuestro cliente.

CREANDO EL SERVIDOR DE TELNET

Para crear un servidor de Telnet vamos a utilizar el componente IdTelnetServer situado en la pestaña de componentes Indy. Sólo tenemos que crear un nuevo formulario e introducir dicho componente:


Insertamos un componente de la clase TMemo que llamaremos Log que vamos a utilizar para monitorizar la conexión con el cliente. Luego añadimos el componente IdTelnetServer y los llamamos Servidor.

En este componente tenemos que dejar activado el servidor poniendo la propiedad Active a True. En el campo DefaultPort viene por defecto el 23 aunque podemos poner cualquier otro evitar evidencias por si hay alguién por la red escaneando puertos abiertos por debajo del 1024.

Lo que hay que asegurarse es que dicho puerto este habilitado por nuestro software que hace de cortafuegos y si tenemos un router habrá que redireccionar ese puerto desde nuestra IP pública a la tarjeta de red por medio de las opciones NAT.

Para ver si todo funciona bien ejecutamos el programa y saltará el cortafuegos de Windows (o el que tengamos por defecto) para habilitar el servidor:


Para poder probar si funciona nuestro servidor necesitamos un programa que haga de cliente de Telnet. Afortunadamente, Windows dispone en la consola de el cliente telnet.exe. Abrimos una ventana de comandos (Símbolo del sistema) y hacemos una conexión local escribiendo telnet 127.0.0.1. Si todo marcha según lo previsto debe verse este mensaje:


Podemos escribir cualquier cosa pulsando luego Intro y no hará absolutamente nada. Quedará como si estuviésemos escribiendo en un editor de texto. Al cerrar la ventana de nuestro servidor, la ventana de comandos nos dirá que hemos perdido la conexión:


También disponemos del programa Hyperterminal que se encuentra en Inicio -> Programas -> Accesorios -> Herramientas del sistema -> Comunicaciones -> Hyperterminal. Aunque este programa da algunos problemas que luego comentaré más adelante. Si no lo tuviésemos instalado se puede hacer fácilmente a través de la opción Agregar o quitar programas que se encuentra en el Panel de control.

De todas formas, con el cliente telnet de la línea de comandos nos sobra para hacer las pruebas pertinentes.

CONTROLANDO LA CONEXIÓN CON EL SERVIDOR

Para controlar el estado de la conexión vamos a utilizar los eventos OnConnect y OnDisconnet para informar en la ventana del servidor quien se ha conectado:

procedure TFServidorTelnet.ServidorConnect(AContext: TIdContext);
begin
Log.Lines.Add( 'Se ha conectado: ' + AContext.Binding.PeerIP );
end;

procedure TFServidorTelnet.ServidorDisconnect(AContext: TIdContext);
begin
Log.Lines.Add( 'Se ha desconectado: ' + AContext.Binding.PeerIP );
end;

Ahora pasemos a la validación del usuario.

VALIDANDO LOS USUARIOS QUE SE CONECTAN

Lo primero que vamos a hacer es cambiar el mensaje de bienvenida poniendo en la propiedad LoginMessage del componente IdTelnetServer la frase Servidor de Telnet.

Antes de proceder a leer comandos del cliente primero tenemos que autentificarlo para evitar intrusiones de terceros. Esto se hace en su evento OnAuthentitacion:

procedure TFServidorTelnet.ServidorAuthentication(AContext: TIdContext;
const AUsername, APassword: string; var AAuthenticated: Boolean);
begin
Log.Lines.Add('Usuario: '+AUserName);
Log.Lines.Add('Password: '+APassword);

if (AUsername = 'admin') and (APassword = '1234') then
begin
AAuthenticated := True;
AContext.Connection.Socket.WriteLn( 'Bienvenido al servidor de Telnet.' );
AContext.Connection.Socket.Write( '' );
AContext.Connection.Socket.Write( '>' );
Log.Lines.Add( 'Acceso concedido.' );
end
else
begin
AAuthenticated := True;
AContext.Connection.Socket.WriteLn( 'Acceso denegado.' );
AContext.Connection.Disconnect;
Log.Lines.Add( 'Acceso denegado.' );
end;
end;

Para poder compilar sin problemas con este evento debemos añadir arriba la unidad IdContext.

Es este evento lo primero que hacemos es imprimir en la ventana del servidor el usuario y password del que se conecta. Luego si adivina el usuario admin y la clave 1234 entonces le damos el mensaje de bienvenida y esperamos a que el usuario escriba un comando. Si no ha podido acceder le denegamos la conexión.

LEYENDO LOS COMANDOS ENVIADOS POR EL CLIENTE TELNET

Una vez que el usuario esta dentro lo que hay que hacer es ir leyendo los comando que envía y según nuestro criterio le vamos dando respuestas. Esto se hace en el evento OnExecute:

procedure TFServidorTelnet.ServidorExecute(AContext: TIdContext);
var
sComando: String;
begin
sComando := LowerCase( AContext.Connection.Socket.ReadLn );
Log.Lines.Add( 'Comando: ' + sComando );

if sComando = 'quit' then
begin
AContext.Connection.Socket.WriteLn( 'Gracias por su visita.' );
AContext.Connection.Disconnect;
end
else
if sComando = 'fecha' then
begin
AContext.Connection.Socket.WriteLn( DateToStr( Date ) );
AContext.Connection.Socket.Write( '>' );
end
else
if sComando = 'hora' then
begin
AContext.Connection.Socket.WriteLn( TimeToStr( Time ) );
AContext.Connection.Socket.Write( '>' );
end
else
begin
AContext.Connection.Socket.WriteLn( 'Comando desconocido: '+sComando );
AContext.Connection.Socket.Write( '>' );
end;
end;

Este es un proceso cíclico en el que vamos leyendo comandos y dando su respuesta. En este caso sólo he programado tres comandos:

quit -> abandona la conexión

fecha -> devuelve la fecha del ordenador remoto

hora -> devuelve la hora del ordenador remoto


PROBANDO EL SERVIDOR


Vamos a realizar una prueba de conexión:


Esta sería la ventana de bienvenida:


Ahora escribimos como usuario admin con password 1234:


Una cosa rara que pasa con nuestro servidor telnet de Indy es que envía el eco de lo que escribimos, cuyo problema no se si esta en el cliente telnet.exe o en el componente IdTelnetServer. Cuando lo averigüe actualizaré este artículo con la solución (se aceptan sugerencias). Con el programa Hyperterminal no sucede esto al hacer login, pero tiene el problema que una vez estamos dentro no vemos lo que estamos escribiendo.

En fin, escribimos usuario y clave y al entrar veremos el mensaje de bienvenida:


Ahora probamos cada uno de nuestros comandos:


En nuestro servidor también vamos registrando los eventos:


Como se puede apreciar en la ventana del servidor vemos las acciones que ha ido haciendo nuestro cliente. Este protocolo tiene la ventaja de que gasta un ancho de banda ridículo para los tiempos que estamos (ADSL, Cable, etc.) dándonos la potencia de monitorizar varios ordenadores simultáneamente.

Podemos inventarnos todos los comandos que nos vengan en gana, por ejemplo:

- Leer las unidades de disco del ordenador remoto.

- Acceder a las tablas de una base de datos y ampliar o reducir campos.

- Ejecutar eventos en el PC remoto: abrir CD-ROM, apagar equipo, ejecutar copia de seguridad, capturar la pantalla y enviarla por correo, etc.

Es decir, se pueden crear todas las funciones de un troyano (no seáis malos). El único inconveniente que tiene este protocolo es que viaja por la red sin encriptar, con lo que si alguien un poco avispado intercepta nuestra conexión con un sniffer podría averiguar el usuario y password de nuestro servidor telnet.

En el próximo artículo veremos las alternativas que tenemos para solucionar este problema.

Pruebas realizadas en RAD Studio 2007.

7 comentarios:

Anónimo dijo...

Creo que vale la pena apuntar que los servidores INDY incluyen soporte multihilos de paquete. Esto significa que cada cliente que se conecta es atendido por un hilo de ejecución diferente, controlado y mantenido por INDY.

Esto es muy bueno, pues evita que se serialicen las operaciones de los clientes (muy, muy deseable), pero es algo a tener en cuenta a la hora de programar los eventos del servidor.

Por ejemplo, tener en cuenta que la VCL no tiene soporte multihilos, por tanto toda operación que se realice con la interfaz visual debe ejecutarse en el hilo principal de la aplicación, o los resultados son impredecibles (corrupciones de memoria, cuelgues de la aplicación, etc).

En el ejemplo que propones, las líneas que agregan información al log (un TMemo de la VCL) debieran seguir este consejo. (Para evitar la complicación innecesaria del código podrías, por ejemplo, crear un método AddToLog que se encargue de ello)

Por lo demás, el artículo me parece excelente!

Un saludo.

Administrador dijo...

Muchas gracias por tu aclaración.

Saludos.

Jhonatan dijo...

hola amigo, este programa esta muy bueno. sigue asi, para poder aprender mas y mas. lo unico que te pido es que tambien hagas las pruebas en delphi 7, ya que tuve que inventar hasta que me salio :-).
aqui esta los codigos para delphi 7

procedure TTFServidorTelnet.ServidorConnect(AThread: TIdPeerThread);
begin
Log.Lines.Add( 'Se ha conectado: ' +Athread.Connection.Socket.Binding.PeerIP );
end;

procedure TTFServidorTelnet.ServidorDisconnect(AThread: TIdPeerThread);
begin
Log.Lines.Add( 'Se ha desconectado: ' + Athread.Connection.Socket.Binding.PeerIP );
end;

procedure TTFServidorTelnet.ServidorAuthentication(AThread: TIdPeerThread;
const AUsername, APassword: String; var AAuthenticated: Boolean);
begin
Log.Lines.Add('Usuario: '+AUserName);
Log.Lines.Add('Password: '+APassword);

if (AUsername = 'admin') and (APassword = '1234') then
begin
AAuthenticated := True;
athread.Connection.WriteLn('Bienvenido al servidor de telnet.');
// AContext.Connection.Socket.WriteLn( 'Bienvenido al servidor de Telnet.' );
athread.Connection.Write( '' );
// AContext.Connection.Socket.Write( '' );
athread.Connection.Write( '>' );
// AContext.Connection.Socket.Write( '>' );
Log.Lines.Add( 'Acceso concedido.' );

end
else
begin
AAuthenticated := True;
athread.Connection.WriteLn('Acesso denegado');
// AContext.Connection.Socket.WriteLn( 'Acceso denegado.' );
athread.Connection.Disconnect;
// AContext.Connection.Disconnect;
Log.Lines.Add( 'Acceso denegado.' );
end;
end;

procedure TTFServidorTelnet.ServidorExecute(AThread: TIdPeerThread);
var
sComando: String;
begin
sComando := LowerCase( athread.Connection.ReadLn );
Log.Lines.Add( 'Comando: ' + sComando );

if sComando = 'quit' then
begin
athread.Connection.WriteLn( 'Gracias por su visita.' );
athread.Connection.Disconnect;
end
else
if sComando = 'fecha' then
begin
Athread.Connection.WriteLn( DateToStr( Date ) );
Athread.Connection.Write( '>' );
end
else
if sComando = 'hora' then
begin
Athread.Connection.WriteLn( TimeToStr( Time ) );
Athread.Connection.Write( '>' );
end
else
begin
Athread.Connection.WriteLn( 'Comando desconocido: '+sComando );
Athread.Connection.Write( '>' );
end;
end;

end.

Administrador dijo...

Muchas gracias por tu colaboración.

Damian Cipolat dijo...

Que tal les dejo, como borrar la pantalla usando telnet y Delphi:

#27 - es el caracter de escape.
Si escribimos esto y usamos el telnet de windows se borrara la pantalla, similar al CLRSCR de turbo pascal.
writeln(#27+'[2J');

les dejo un link, con mas datos sobre esto.
http://www.termsys.demon.co.uk/vtansi.htm

y una tabla ascii con caracteres que pueden servir:
http://www.robelle.com/smugbook/ascii.html

saludos.

damian_cipolat dijo...

Como borrar la pantalla en el cliente telnet de windows con delphi:

writeln(#27+'[2J');

y causa el mismo efecto que el clrscr de turbo pascal.

Les dejo un link con mas info sobre esto: http://www.termsys.demon.co.uk/vtansi.htm

cuando dice <escape>se refiere al caracter #27.

saludos.

Administrador dijo...

Gracias Damian por esta información, es muy interesante.

Publicidad