2. Import Elenco Porte da File JSON

In questa lezione utilizzeremo un file JSON per poter mostrare ai nostri utenti il servizio associato ad ogni porta che è stata trovata aperta sul nostro server, e quindi evitare che l'utente debba scrivere ogni volta la porta che vuole analizzare in un ciclo infinito.

Ho ricavato il file JSON che utilizzeremo partendo da un file CSV messo a disposizione dallo IANA (Internet Assigned Numbers Authority) e nello specifico da questo link. Scorrendo il file, vediamo che si tratta di un elenco di coppie chiave valore dove per ogni chiave (porta) è associato un valore che rappresenta il servizio comunemente associato alla stessa porta:

{    
    "1": "tcpmux",
    "3": "compressnet",
    "7": "echo",
    "9": "discard",
    "13": "daytime",
    "17": "qotd",
    "19": "chargen",
    "20": "ftp-data",
    "21": "ftp",
    "22": "ssh"
    (...)
}


Come utilizzare file JSON con Python?

Si tratta di un'estensione di file particolarmente comune, e per questo motivo nella Standard Library di Python è presente un modulo json che contiene tutta una serie di funzionalità che ci rendono la vita semplice quando ci troviamo a dover lavorare con questi file.

Importiamo quindi subito il modulo e creiamo una funzione extract_json_data() che si occupi di restituirci il contenuto del file. Per rendere la funzione utilizzabile con più file e non solo col file specifico utilizzato dal nostro scanner, le passiamo come parametro filename. Definiamo quindi una variabile globale PORTS_DATA_FILE alla quale assegniamo il nome del nostro file, in questo caso “common_ports.json” (che potete scaricare dal repository dedicato nel mio profilo GitHub)

import json

PORTS_DATA_FILE = "./common_ports.json"

def extract_json_data(filename):
    with open(filename, "r") as file:
        data = json.load(file)
    return data

Stiamo facendo dei progressi: ma prima di utilizzare questi dati, dobbiamo ancora elaborarli in modo da poterli utilizzare col resto delle funzioni scritte finora.

Il nostro codice attuale è il seguente, e come potete notare da sotto ad if __name__ == “__main__”, stiamo anzitutto trasformando l’input passato dall’utente per la porta, in intero necessario al corretto funzionamento con la funzione scan_port():

import json
import socket

OPEN_PORTS = []
PORTS_DATA_FILE = "./common_ports.json"

def extract_json_data(filename):
    with open(filename, "r") as file:
        data = json.load(file)
    return data

def get_host_ip_addr(target):
    try:
        ip_addr = socket.gethostbyname(target)
    except socket.gaierror as e:
        print(f"C'è stato un errore... {e}")
    else:
        return ip_addr

def scan_port(ip, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(1.0)
    conn_status = sock.connect_ex((ip, port))
    if conn_status == 0:
        OPEN_PORTS.append(port)
    sock.close()


if __name__ == "__main__":
    print("Programma scritto per solo scopo educativo!!!")
    target = input("Inserire Target: ")
    ip_addr = get_host_ip_addr(target)
    while True:
        try:
            port = int(input("Inserire Porta: "))
            scan_port(ip_addr, port)
            print(OPEN_PORTS)
        except KeyboardInterrupt:
            print("\nExiting...")
            break

Possiamo quindi definire un'altra funzione, get_ports_info(), che sarà responsabile di restituirci le informazioni sulle porte di cui avremo bisogno per il nostro scanner nei formati corretti, partendo dai dati estratti dal file JSON.

def get_ports_info():
    data = extract_json_data(PORTS_DATA_FILE)
    ports_info = {int(k): v for (k, v) in data.items()}
    return ports_info

Per molti, la riga {int(k): v for (k, v) in data.items()} potrebbe sembrare criptica e confusionaria: si tratta di una Dict Comprehension di Python, simile alle List Comprehension di cui abbiamo parlato nel tutorial dedicato.


Dict Comprehension in Python

Stiamo creando un nuovo dizionario a partire dai dati ottenuti da data.items, che restituisce a sua volta un elenco di tuple di tipo (k, v), dove k rappresenta la chiave e v il valore del dizionario restituito da extract_json_data(). Per ciascuna tupla vogliamo creare una nuova coppia chiave valore per il nostro dizionario ports_info, che abbia come chiave la versione int della stessa chiave, e come valore lo stesso valore. Quindi restituiamo il dizionario con return, in modo che possa essere utilizzato.

Modifichiamo il codice sotto a if __name__ == "__main__":

if __name__ == "__main__":
    print("Programma scritto per solo scopo educativo!!!")
    target = input("Inserire Target: ")
    ip_addr = get_host_ip_addr(target)
    ports_info = get_ports_info()

    for port in ports_info.keys():
        try:
            print(f"Scanning: {ip_addr}:{port}")
            scan_port(ip_addr, port)
        except KeyboardInterrupt:
            print("\nExiting...")
            break

    print("Open Ports:")
    for port in OPEN_PORTS:
        print(str(port), ports_info[port])

Creiamo anzitutto il dizionario ports_info utilizzando la funzione get_ports_info(). Quindi, per ogni porta in esso contenuta (per ogni chiave del dizionario) effettuiamo la scansione sull'IP del target scelto dall'utente e per ogni porta aperta individuata mandiamo in print sia il numero che il servizio associato!

Di seguito il codice completo per questa lezione:

import json
import socket

OPEN_PORTS = []
PORTS_DATA_FILE = "./common_ports.json"


def extract_json_data(filename):
    with open(filename, "r") as file:
        data = json.load(file)
    return data

def get_ports_info():
    data = extract_json_data(PORTS_DATA_FILE)
    ports_info = {int(k): v for (k, v) in data.items()}
    return ports_info

def get_host_ip_addr(target):
    try:
        ip_addr = socket.gethostbyname(target)
    except socket.gaierror as e:
        print(f"C'è stato un errore... {e}")
    else:
        return ip_addr

def scan_port(ip, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(1.0)
    conn_status = sock.connect_ex((ip, port))
    if conn_status == 0:
        OPEN_PORTS.append(port)
    sock.close()


if __name__ == "__main__":
    print("Programma scritto per solo scopo educativo!!!")
    target = input("Inserire Target: ")
    ip_addr = get_host_ip_addr(target)
    ports_info = get_ports_info()

    for port in ports_info.keys():
        try:
            print(f"Scanning: {ip_addr}:{port}")
            scan_port(ip_addr, port)
        except KeyboardInterrupt:
            print("\nExiting...")
            break

    print("Open Ports:")
    for port in OPEN_PORTS:
        print(str(port), ports_info[port])