Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:python_avanzado_proyectos_seguridad:escaner_puertos_socket

¡Esta es una revisión vieja del documento!


Implementar en Python un escáner de puertos con sockets

Módulo perteneciente al curso Python avanzado para proyectos de seguridad

En esta sección revisaremos cómo podemos implementar el escaneo de puertos con sockets y cómo administrar las excepciones cuando trabajamos con sockets.

Los sockets son el bloque de construcción fundamental para las comunicaciones de red y de manera fácil podemos verificar si un puerto específico está abierto, cerrado o filtrado al llamar al método connect_ex().

Método connect_ex()

El método socket.connect_ex(dirección,puerto) se usa para implementar el escaneo de puertos con sockets. Este script muestra que los puertos están abiertos en la máquina localhost con la interfaz de dirección IP loopback de 127.0.0.1.

Puede encontrar el siguiente código en el archivo socket_ports_open.py:

import socket
 
ip ='127.0.0.1'
portlist = [22,23,80,912,135,445,20]
for port in portlist:
    sock= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    result = sock.connect_ex((ip,port))
    print(port,":", result)
    sock.close()

result es un número. Posibles valores:

  • 111: puerto cerrado (conexión rechazada)
  • 0: puerto abierto

Escáner de puertos con sockets

Por ejemplo, podríamos tener una función que acepte por parámetros una IP y una lista de puertos y devuelva para cada puerto si está abierto o cerrado.

En este caso necesitamos importar los módulos socket y sys. El módulo sys lo utilizamos para salir del programa con la instrucción sys.exit() y devolver el control al intérprete en caso de error de conexión. Si ejecutamos la función desde nuestro programa principal vemos como comprueba cada uno de los puertos y nos devuelve si está abierto o cerrado para una ip determinada. El primer parámetro puede ser tanto una ip como un nombre de dominio ya que el módulo es capaz de resolver un nombre a partir de una ip y viceversa.

Puede encontrar el siguiente código en el archivo comprobarListaPuertos.py:

import socket
import sys
 
def comprobarListaPuertos(ip,portlist):
    try:
        for port in portlist:
            sock= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
            sock.settimeout(5)
            result = sock.connect_ex((ip,port))
            if result == 0:
                print ("Puerto {}: \t Abierto".format(port))
            else:
                print ("Puerto {}: \t Cerrado".format(port))
            sock.close()
    except socket.error as error:
        print (str(error))
        print ("Error de conexion")
        sys.exit()
 
comprobarListaPuertos('www.google.es',[80,8080,443,22])

Ejemplo de ejecución:

Puerto 80:       Abierto
Puerto 8080:     Cerrado
Puerto 443:      Abierto
Puerto 22:       Cerrado

Explicación función escáner de puertos

La forma de ejecutar esta función es mediante la llamada:

comprobarListaPuertos(dominio|direccion_ip,[lista_puertos])

Si ejecutamos la función con una ip o nombre de dominio que no exista, nos devolverá un error de conexión junto con la excepción que ha devuelve el módulo socket al no poder resolver la dirección IP.

comprobarListaPuertos("local",[80,8080,443])

[Errno 11004] getaddrinfo failed.Error de conexion

La parte más importante de la función del ejemplo anterior la encontramos cuando comprueba si el puerto está abierto o cerrado:</p>

# ...
sock= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.settimeout(5)
result = sock.connect_ex((ip,port))
if result == 0:
    print ("Puerto {}: \t Abierto".format(port))
else:
    print ("Puerto {}: \t Cerrado".format(port))
sock.close()

En el código anterior vemos cómo utilizamos el método settimeout() para indicarle un tiempo de intento de conexión en segundos.

IMAAAAAAAAAAAAAAAGEN

Escáner de puertos avanzado

El siguiente código de Python le permitirá escanear un host local o remoto en busca de puertos abiertos. El programa busca puertos seleccionados a partir de una determinada dirección IP introducida por el usuario y refleja los puertos abiertos de regreso al usuario. Si el puerto está cerrado, también muestra información sobre el motivo, por ejemplo, por timeout de la conexión.

Puede encontrar el siguiente código en el archivo socket_scanner_puertos.py. El script comienza con información relacionada con la dirección IP y los puertos introducidos por el usuario:

# Escaner de puertos con sockets
 
# Importamos modulo socket
from socket import * 
 
# Preguntamos por la IP                
ip = input("Introduce IP : ")
 
# Preguntamos por el puertos          
puerto_inicio = input("Introduce puerto de inicio : ")
puerto_fin = input("Introduce puerto de fin : ")
 
print ("Escaneando IP {} : ".format(ip))
 
#recorrer cada uno de los puertos
for port in range(int(puerto_inicio),int(puerto_fin)):
    print ("Probando puerto {} ...".format(port))
    # Crea el objeto socket
    s = socket(AF_INET, SOCK_STREAM)
    s.settimeout(5)
 
    # Comprobar conexion e imprimimos si el puerto está abierto
    if(s.connect_ex((ip,port))==0):
        print("El puerto " , port, "está abierto")
 
    # Cierra el socket
    s.close()
 
print("Escaneo finalizado!")

Ejemplo de ejecución:

Introduce IP : 192.168.0.1
Introduce puerto de inicio : 22
Introduce puerto de fin : 80
Escaneando IP 192.168.0.1 :
Probando puerto 22 ...
Probando puerto 23 ...
El puerto  23 está abierto
Probando puerto 24 ...
Probando puerto 25 ...
(...)
El puerto  53 está abierto
Probando puerto 54 ...
Probando puerto 55 ...
Probando puerto 56 ...
(...)
Probando puerto 71 ...
Probando puerto 72 ...
Probando puerto 73 ...
Probando puerto 74 ...
Probando puerto 75 ...
Probando puerto 76 ...
Probando puerto 77 ...
Probando puerto 78 ...
Probando puerto 79 ...
Escaneo finalizado!

Escáner de puertos a partir de un dominio

El siguiente script de Python nos permitirá escanear una dirección IP con las funciones portScanning y socketScan. El programa busca puertos seleccionados en un dominio específico resuelto a partir de la dirección IP ingresada por el usuario por parámetro.</p>

En este script, el usuario debe ingresar como parámetros obligatorios el host y un puerto, separados por una coma:

IMAAAAAAAAAAAAAAAAAAAAGEN

Puede encontrar el siguiente código en el archivo socket_portScan.py:

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
# Para jugar con los argumentos que introduce el usuario:
import optparse
 
from socket import *
 
# Lanzar diferentes hilos de ejecución:
from threading import *
 
def socketScan(host, port):
    try:
        socket_connect = socket(AF_INET, SOCK_STREAM)
        socket_connect.settimeout(10)
        socket_connect.connect((host, port))
        print('[+] %d/tcp open \n' % port)
    except Exception as error:
        print(error)
        print('[-] %d/tcp closed \n' % port)
    finally:
        socket_connect.close()	
 
def portScanning(host, ports):
	try:
		ip = gethostbyname(host)
	except:
		print("[-] Cannot resolve '%s': Unknown host" %host)
		return
 
	try:
		name = gethostbyaddr(ip)
		print('\n[+] Scan Results for: ' + name[0])
	except:
		print('\n[+] Scan Results for: ' + ip)
 
	for port in ports:
                # Usamos el módulo 'Thread' para poder crear un hilo de ejecución 
                # distinto para cada puerto
		t = Thread(target=socketScan,args=(ip,int(port)))
		t.start()
 
def main():
	parser = optparse.OptionParser('socket_portScan '+ '-H <Host> -P <Port>')
	parser.add_option('-H', dest='host', type='string', help='specify host')
	parser.add_option('-P', dest='port', type='string', help='specify port[s] separated by comma')
 
	(options, args) = parser.parse_args()
 
	host = options.host
	ports = str(options.port).split(',')
 
	if (host == None) | (ports[0] == None):
		print(parser.usage)
		exit(0)
 
	portScanning(host, ports)
 
 
if __name__ == '__main__':
	main()

Función principal escáner de puertos a partir de un dominio

Este podría ser nuestro programa principal donde obtenemos los parámetros obligatorios de host y puertos para la ejecución del script. Una vez que hayamos obtenido estos parámetros, llamaremos a la función portScanning que resolverá la dirección IP y el nombre de host, y llamaremos a la función socketScan que usará el módulo de socket para determinar el estado del puerto:</p>

def main():
	parser = optparse.OptionParser('socket_portScan '+ '-H <Host> -P <Port>')
	parser.add_option('-H', dest='host', type='string', help='specify host')
	parser.add_option('-P', dest='port', type='string', help='specify port[s] separated by comma')
 
	(options, args) = parser.parse_args()
 
	host = options.host
	ports = str(options.port).split(',')
 
	if (host == None) | (ports[0] == None):
		print(parser.usage)
		exit(0)
 
	portScanning(host, ports)
 
 
if __name__ == '__main__':
	main()

Ejecución:

python socket_portScan.py -H www.google.es -P 80,21,22,23

En la ejecución del script anterior, podemos ver el estado de todos los puertos que están del dominio:

[+] Scan Results for: mad41s10-in-f3.1e100.net
[+] 80/tcp open

timed out
[-] 21/tcp closed

timed out
[-] 23/tcp closed

timed out
[-] 22/tcp closed

Actividad práctica

import socket
import sys
 
# Preguntamos por la IP
ip = input("Introduce IP:")
 
#Preguntamos por los puertos de inicio y fin
listaPuertos = list(int(num) for num in input("Introduce rango de puertos separados por comas:").strip().split(","))
 
print ("Escaneando IP {} en los puertos {} : ".format(xxx,xxx))
 
try:
    for port in xxx:
        s = socket.socket(socket.AF_INET,xxx)
        s.settimeout(5)
        if (s.connect_ex((xxx,xxx))==0):
            try:
                serv = socket.getservbyport(xxx)
            except socket.error:
                error_servidor="not-found"
 
            print(("El puerto %s:está abierto  Servicio:%s  "%(xxx,xxx)))
 
    print("Scanning Completed")
 
except KeyboardInterrupt as exception:
    print(exception)
    sys.exit()

Completa el siguiente script para obtener un escáner de puertos introduciendo desde teclado la IP y los puertos de inicio y fin. Sustituir las xxx por variables definidas.

Solución:

import socket
import sys
 
# Preguntamos por la IP
ip = input("Introduce IP:")
 
#Preguntamos por los puertos de inicio y fin
listaPuertos = list(int(num) for num in input("Introduce rango de puertos separados por comas:").strip().split(","))
 
puerto_inicio = listaPuertos[0]
puerto_fin = listaPuertos[1]
 
print ("Escaneando IP {} en los puertos {} : ".format(ip,listaPuertos))
 
try:
    for port in range(int(puerto_inicio),int(puerto_fin)+1):
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        s.settimeout(5)
        if (s.connect_ex((ip,port))==0):
            try:
                serv = socket.getservbyport(port)
            except socket.error:
                error_servidor="not-found"
 
            print(("El puerto %s:está abierto  Servicio:%s  "%(port,serv)))
 
    print("Scanning Completed")
 
except KeyboardInterrupt as exception:
	print(exception)
	sys.exit()

Implementar en Python un servidor HTTP

Podríamos crear un socket del tipo TCP y vincularlo a un puerto. Podríamos utilizar “localhost”, para aceptar conexiones desde la misma máquina. El puerto podría ser 80, pero como necesita privilegios de root, usaremos uno mayor o igual que 8080.

HTTP (Hyper Text Transfer Protocol) es un protocolo base de la web y que ofrece un conjunto de instrucciones para que los servidores y navegadores funcionen

Un protocolo es un formato o conjunto definido de reglas, procedimientos que permiten inter-operar a dos dispositivos. Un protocolo sirve para la comunicación en red o entre aplicaciones

Métodos de socket del servidor

Estos son los principales métodos que podemos usar desde el punto de vista del servidor:

  • socket.bind(dirección): este método nos permite conectar la dirección con el socket, con el requisito de que el socket debe estar abierto antes de establecer la conexión con la dirección.
  • socket.listen(numero_conexiones): este método acepta como parámetro el número máximo de conexiones de los clientes e inicia la escucha TCP para las conexiones entrantes.????
  • socket.accept(): este método nos permite aceptar conexiones del cliente. Este método devuelve dos valores: client_socket y la dirección del cliente. client_socket es un nuevo objeto de socket utilizado para enviar y recibir datos. Antes de usar este método, debe llamar a los métodos socket.bind(dirección) y socket.listen(numero_conexiones).

Implementación del servidor

De los métodos comentados anteriormente podríamos utilizar el método bind() que acepta como parámetros la dirección ip y el puerto.

El módulo socket proporciona el método listen() que permite poner en cola hasta un máximo de n solicitudes. Por ejemplo podríamos establecer en 5 el número máximo de peticiones con la instrucción mysocket.listen(5).

Posteriormente, podríamos establecer la lógica de nuestro servidor cada vez que recibe la petición de un cliente. Utilizamos el método accept() para aceptar conexiones, leer datos entrantes con el método recv y respondemos una página HTML con el método send().

import socket
 
mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mySocket.bind(('localhost', 8080)
mySocket.listen(5)
 
while True:
    print('Esperando conexiones...')
    (recvSocket, address) = mySocket.accept()
    print('Petición HTTP recibida:')
    print(recvSocket.recv(1024))
    recvSocket.send(bytes("HTTP/1.1 200 OK\r\n\r\n <html><body><h1>Hello World!</h1></body></html> \r\n",'utf-8'))
    recvSocket.close()

Al ejecutar el código anterior, el script se quedará en ejecución a la espera de conexiones.

Podemos probar que funciona correctamente haciendo una petición con curl, por ejemplo:

curl localhost:8080

Nos devolvería el documento HTML que indicamos en el código del servidor.

Veremos cómo implementar un cliente HTTP sencillo en Python para probar también el servidor.

Probando el servidor creando un cliente HTTP

Si queremos probar el funcionamiento del servidor HTTP, podríamos crearnos otro script que nos permite obtener la respuesta envía el servidor que hemos creado.

#!/usr/bin/python
import socket
webhost = 'localhost'
webport = 8080
print("Contacting %s on port %d ..." % (webhost, webport))
webclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
webclient.connect((webhost, webport))
webclient.send(bytes("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n".encode('utf-8')))
reply = webclient.recv(4096)
print("Response from %s:" % webhost)
print(reply.decode())

Con el servidor HTTP funcionando, ejecutaríamos el cliente y veríamos una respuesta similar a la siguiente:

Contacting localhost on port 8080 ...
Response from localhost:
HTTP/1.1 200 OK</p>

Actividad práctica

Completa el siguiente script para implementar un servidor HTTP que esté escuchando en el puerto 8080 y acepte como máximo 5 conexiones de forma simultánea.

import socket
 
# Crear un socket del tipo TCP y vincularlo a un puerto
# Utilizamos 'localhost', por lo tanto, solo aceptamos conexiones desde la misma máquina
# El puerto podría ser 80, pero como necesita privilegios de root,
# usemos uno por mayor o igual que 8080
 
mySocket = socket.socket(xxx,xxx)
mySocket.bind(('localhost', 8080))
 
# Poner en cola un máximo de 5 solicitudes de conexión TCP
 
xxx.listen(5)
 
# Aceptar conexiones, leer datos entrantes y responder una página HTML (en un bucle)
 
while True:
    print('Waiting for connections')
    (recvSocket, address) = mySocket.xxx()
    print('HTTP request received:')
    print(recvSocket.xxx(1024))
    recvSocket.xxx(bytes("HTTP/1.1 200 OK\r\n\r\n <html><body><h1>Hello World!</h1></body></html> \r\n",'utf-8'))
    xxx.close()

Solución:

import socket
 
# Crear un socket del tipo TCP y vincularlo a un puerto
# Utilizamos 'localhost', por lo tanto, solo aceptamos conexiones desde la misma máquina
# El puerto podría ser 80, pero como necesita privilegios de root,
# usemos uno por mayor o igual que 8080
 
mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mySocket.bind(('localhost', 8080))
 
# Poner en cola un máximo de 5 solicitudes de conexión TCP
mySocket.listen(5)
 
# Aceptar conexiones, leer datos entrantes y responder una página HTML (en un bucle)
 
while True:
    print('Waiting for connections')
    (recvSocket, address) = mySocket.accept()
    print('HTTP request received:')
    print(recvSocket.recv(1024))
    recvSocket.send(bytes("HTTP/1.1 200 OK\r\n\r\n <html><body><h1>Hello World!</h1></body></html> \r\n",'utf-8'))
    recvSocket.close()

Resumen

En esta unidad hemos aprendido:

  • Crear un socket utilizando el constructor s = socket.socket (socket_family, socket_type, protocol = 0) donde indicamos la familia, el tipo y el protocolo.
  • Obtener información con los métodos gethostbyaddr(dirección) que nos permite obtener un nombre de dominio a partir de la dirección IP y gethostbyname(hostname) que nos permite obtener una dirección IP a partir de un nombre de dominio.
  • Gracias al uso del método socket.connect_ex(dirección,puerto) hemos conseguido implementar un escaneo de puertos, pasando como parámetros la dirección ip y el puerto.
  • Implementar nuestro propio servidor HTTP que tiene la capacidad de atender peticiones por parte de n clientes de forma simultánea. Con el uso del método socket.bind(dirección) nos permite conectar la dirección con el socket, con el requisito de que el socket debe estar abierto antes de establecer la conexión.
  • El método socket.listen(numero_conexiones) acepta como parámetro el número máximo de conexiones de los clientes e inicia la escucha TCP para las conexiones entrantes.???
  • Finalmente, implementamos un cliente HTTP para realizar peticiones al servidor creado anteriormente.

FAQ

¿Cuántas familias de direcciones para sockets encontramos en el módulo socket de Python?

  • AF_INET: son las direcciones IPv4. Se representa con el host y el puerto.
  • AF_INET6: son las direcciones IPv6. Se representa con el host y el puerto (así como la etiqueta de flujo “Flow Label” con la documentación en https://tools.ietf.org/html/rfc6437, y el alcance de la id “scope id” que indica el interfaz qué usar)
  • AF_UNIX o AF_LOCAL: son las direcciones locales de Unix. Se representa como un string que apunta a un fichero del sistema.

¿Cuál es la utilidad de los sockets?

Los sockets mantienen la conexión en tiempo real entre un cliente y un servidor con el objetivo de enviar y recibir datos de un lado a otro. Por ejemplo, podremos crear nuestro propio chat; es decir, una aplicación de escritorio en nuestro ordenador recibiendo y enviando mensajes, para que el lado del servidor reciba y envíe los mensajes en tiempo real.

informatica/programacion/cursos/python_avanzado_proyectos_seguridad/escaner_puertos_socket.1731337576.txt.gz · Última modificación: por tempwin