Menu

Python per l'ethical hacking: perché e come si usa

8 min di lettura Manuel

Un punto che viene fuori spesso chi lavora in sicurezza, e che a prima vista sembra strano: Python non è nato per fare hacking. È nato per essere facile da leggere. Il fatto che sia diventato il linguaggio dell'attacco e del controllo è una conseguenza, non un progetto.

E la ragione per cui conviene conoscerlo non è che sia "potente". È che non ti blocca. Qualsiasi cosa tu debba fare, in Python la fai in cinque righe. Quando invece la devi fare in C, cominci a fare un'altra cosa.

Tutto quello che segue vale per sistemi e reti su cui hai un'autorizzazione scritta. Un pentest senza contratto non è ricerca, è reato: in Italia si configura come intrusione informatica aggravata (art. 615-ter c.p.). Nessuna tecnica di questo articolo diventa lecita perché è facile da scrivere.

Le cinque ragioni tecniche

  1. Leggibilità come vantaggio operativo. In un assessment hai già problemi di ordine mentale: devi ricordare lo stato della macchina, che cosa hai già provato, che cosa hai rotto. Se lo script è in C, metà del tuo budget cognitivo va speso a ricordare come si chiama la funzione per le stringhe. In Python leggi quello che vuoi fare. Quando il bersaglio è complesso e tu sei sotto pressione, la differenza si sente.
  2. È glue, non un'isola. Python non compete con gli altri strumenti: li orchestra. subprocess, socket, ssl, sqlite3, json sono in libreria standard. Nmap, curl, openssl, ssh, un container, un database SQLite: si chiamano e se ne leggono l'output. Questa è la ragione principale, e vale più delle altre quattro messe insieme.
  3. L'ecosistema è già pronto. Due esempi: Scapy per costruire e analizzare pacchetti, e Requests o httpx per l'HTTP. Non stai scrivendo da zero, stai usando librerie mature, documentate, con migliaia di issue pubbliche.
  4. I test si scrivono in tre righe. Un exploit è una sequenza di passi deterministici. Scrivi una funzione per passo e verifichi che ogni asserzione regga. È ciò che distingue un test da uno script che "di solito funziona".
  5. Gira ovunque. Per uno script di assessment la portabilità vale più della velocità: lo stesso file gira su Linux, WSL2, macOS e dentro un container. Metà degli strumenti di pentest storici sono script shell che non girano su Windows.

Il punto 1 in pratica, per chi non ha mai visto una riga:

import socket

def porta_aperta(host, porta, timeout=1.0):
    """True se qualcuno risponde.

    È ricognizione, non exploit: dice solo che qualcuno è lì.
    """
    try:
        with socket.create_connection((host, porta), timeout=timeout):
            return True
    except OSError:
        return False

for p in (22, 80, 443, 3306, 5432):
    if porta_aperta("192.168.1.10", p):
        print(f"aperta: {p}")

I due limiti, detti prima

Python è lento e lo sa. Un'istruzione in Python costa decine di operazioni native, e per l'elaborazione intensiva — brute force su spazi grandi, cracking di hash, packet processing ad altissimo volume — è un ordine di grandezza più lento del C.

La conseguenza pratica è che non usi Python per la parte veloce: lo usi per orchestrare e i lotti pesanti li lasci a C, Rust o agli strumenti dedicati. È anche il motivo per cui chi fa questo per lavoro conosce Python insieme a qualcos'altro, non al posto di.

Il secondo limite è la superficie di attacco dell'ambiente. import os è una via per eseguire codice arbitrario, pip può installare pacchetti identici per errore di battitura, e le dipendenze non sono firmate. Su una macchina di produzione pip install è una decisione di sicurezza, non un comando innocuo.

La storia: perché si chiama così

1989, CWI ad Amsterdam. Guido van Rossum, sviluppatore nel Centrum voor Wiskunde en Informatica, inizia a lavorare a un linguaggio personale durante le vacanze di Natale. L'obiettivo dichiarato è sostituire l'ABC, che al CWI si usava per insegnare la programmazione e che secondo lui era troppo lento e troppo poco usato in pratica.

Da ABC prende quasi tutto quello che lo caratterizza: indentazione significativa, tipi nativi, l'idea che il codice debba essere leggibile. In più rende opzionale l'orientamento a oggetti, che in ABC era pesante.

Il nome. Guido non volle battezzarlo con il nome di un'altra persona, a differenza di molte mode dell'epoca. Il nome viene dalla Monty Python, la comica britannica: era corto, e le prime due lettere erano in ordine alfabetico come nella "Perl" che stava sostituendo. È una scelta di umiltà, non un vezzo.

1991, la prima versione pubblica. Il 20 febbraio 1991 esce la versione 0.9.0, diffusa su Usenet. Il repository Git non esisteva ancora: il codice si spostava fra computer in allegato alle email e ai gruppi di discussione, e chi non era collegato al momento del passaggio restava fuori. Uno dei motivi per cui le prime versioni faticarono a diffondersi è questo.

1994, Python 1.0. Rilasciato il 26 gennaio 1994, Guido lavora ormai negli Stati Uniti. Fissa due regole di progetto che valgono ancora: le estensioni scritte in C sono sconsigliate, in C++ sono bandite; e il linguaggio deve restare leggibile anche per chi non lo progetta.

Il benevolent dictator. Guido assume il ruolo di dictatore benevolo: decide, e spiega pubblicamente perché. Questo modello è la forma della governance di Python ancora oggi, ed è la cosa che più contribuisce alla stabilità di un linguaggio: quando qualcuno decide in fretta, e il motivo è pubblico, il progetto non si frammenta.

La tartaruga lentina. Il nome CPython, l'implementazione di riferimento, viene da una sigla: C per il linguaggio in cui è scritta, Python per il progetto. La storia della tartaruga che cade da un tavolo in una puntata di Monty Python's Flying Circus, e da cui verrebbe l'abbondante uso del nome Python nel progetto, è più tarda e molto meno fondata. Vale la pena dirlo: è una bella storia, e non è un fatto.

2000, la biforcazione. Il 16 ottobre 2000 esce Python 2.0, che introduce le list comprehension, il garbage collector che raccoglie i riferimenti ciclici, l'assegnazione aumentata e una transizione a un processo di sviluppo più trasparente e sostenuto dalla comunità. È anche l'anno in cui Guido annuncia che il linguaggio si evolve e che "Python 2 sarà solo uno dei modi possibili di scrivere Python".

2008, Python 3.0. L'8 dicembre 2008, otto anni dopo. Le tre differenze tecniche che contano sono: stringhe vere Unicode (in Python 2 erano byte), divisione intera che dà float, e un sistema di eccezioni più pulito. La migrazione è stata lunga — quasi dieci anni, con una libreria standard in parallelo — ma oggi Python 3 è l'unico modo corretto di scrivere Python nuovo.

2018 e 2021, due passaggi che contano. Il 12 luglio 2018 Guido si dimette dalla carica di dictator, formalmente per una "vacanza", ma il messaggio era chiaro. La Python Software Foundation assume il ruolo di governance, come per l'open source vale in Open source: perché conviene al software a pagamento. Nel 2021 la PSF riceve la "gift of tax": con essa diventa proprietaria delle licenze e delle marche, chiudendo il caso dell'uso commerciale dei nomi "Python" e "Anaconda". Il 1° gennaio 2020 intanto finisce ufficialmente il supporto a Python 2.

Come si usa, in pratica

Un pentest con Python non è uno script: è un piccolo programma con struttura. Le quattro fasi, e cosa tool Python copre in ognuna:

FaseDomandaPython
Ricognizioneche cosa c'è?socket, httpx, wrapper per nmap
Enumerazionechi è, che cosa dice?requests, parsing, sqlite3 per i dati
Verificaè davvero vulnerabile?requests + pwntools, assert per passo
Reportcosa devo dire al cliente?Jinja2, report in PDF

Il passaggio che va detto è il terzo: verifica, non "scansione". La differenza fra un penetration tester e uno script kiddie è la stessa che passa fra nmap -sV e una verifica manuale che la versione identificata è davvero vulnerabile a qualcosa. Il primo ti dice che c'è una porta, il secondo ti dice se puoi entrarci e cosa ne facci.

E ci sono tre regole che valgono sempre, in ordine di importanza.

  1. Autorizzazione prima, sempre. Non in una nota a piè di pagina di un deliverable: prima di iniziare, per iscritto, con gli IP esatti e la finestra temporale. Un tester professionista la chiede, la fa firmare, e la conserva. Se il cliente non la firma, il lavoro non si fa.
  2. Non automatizzare la distruzione. Python è perfetto per l'automazione e questo è anche il suo rischio. Una rimozione for ip in range(...) eseguita contro l'IP sbagliato fa danni reali e nessuna rollback. Le operazioni distruttive vanno eseguite a mano, con l'occhio, e registrate.
  3. Niente segreti nel codice. Le credenziali che trovi durante un assessment sono del cliente, e la gestione di un segreto è parte del lavoro. Non finiscono in un file di script, non finiscono in un messaggio, non finiscono in un repository. Quando il cliente riceve il report, il segreto va ruotato: trovato durante un test non è più un segreto, è un finding.

La regola che usiamo noi

Il ragionamento di CYBER ONIONS su questo tema è semplice:

Python è ottimo per costruire gli strumenti giusti, e pessimo come sostituto del pensiero. Ti fa arrivare al punto in cui devi decidere cosa fare — e a quel punto non ti aiuta più.

Il suo valore non è che ti rende pericolosi. È che ti fa leggere il codice, e chi legge il codice degli altri smette di fidarsi delle parole.