One point that comes up often among people who work in security, and that at first glance seems strange: Python was not born to do hacking. It was born to be easy to read. The fact that it became the language of attack and assessment is a consequence, not a design.
And the reason it is worth knowing is not that it is "powerful". It is that it does not get in your way. Whatever you need to do, in Python you do it in five lines. When you have to do it in C, you start doing something else.
Everything that follows applies to systems and networks on which you have written authorisation. A pentest without a contract is not research, it is a crime: in Italy it constitutes aggravated computer intrusion (art. 615-ter of the Criminal Code). No technique in this article becomes lawful because it is easy to write.
The five technical reasons
- Readability as an operational advantage. In an assessment you already have problems of mental order: you have to remember the state of the machine, what you have already tried, what you have broken. If the script is in C, half of your cognitive budget is spent remembering what the string function is called. In Python you read what you want to do. When the target is complex and you are under pressure, the difference is felt.
- It is glue, not an island. Python does not compete with the other tools: it orchestrates them.
subprocess,socket,ssl,sqlite3,jsonare in the standard library. Nmap, curl, openssl, ssh, a container, a SQLite database: they are invoked and their output is read. This is the main reason, and it is worth more than the other four put together. - The ecosystem is already ready. Two examples: Scapy to build and analyse packets, and Requests or
httpxfor HTTP. You are not writing from scratch, you are using mature, documented libraries, with thousands of public issues. - Tests are written in three lines. An exploit is a sequence of deterministic steps. You write one function per step and you check that every assertion holds. That is what distinguishes a test from a script that "usually works".
- It runs everywhere. For an assessment script portability is worth more than speed: the same file runs on Linux, WSL2, macOS and inside a container. Half of the historical pentest tools are shell scripts that do not run on Windows.
Point 1 in practice, for anyone who has never seen a line of it:
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}")
The two limits, stated first
Python is slow and it knows it. One instruction in Python costs dozens of native operations, and for intensive processing — brute force over large spaces, hash cracking, very high volume packet processing — it is an order of magnitude slower than C.
The practical consequence is that you do not use Python for the fast part: you use it to orchestrate, and the heavy batches are left to C, Rust or dedicated tools. It is also the reason why those who do this for a living know Python together with something else, not instead of it.
The second limit is the attack surface of the environment. import os is a
way to execute arbitrary code, pip can install packages that are identical by
a typing mistake, and dependencies are not signed. On a production machine
pip install is a security decision, not a harmless command.
The history: why it is called that
1989, CWI in Amsterdam. Guido van Rossum, a developer at the Centrum voor Wiskunde en Informatica, begins working on a personal language during the Christmas holidays. The declared aim is to replace ABC, which at the CWI was used to teach programming and which in his view was too slow and too little used in practice.
From ABC he takes almost everything that characterises him: significant indentation, native types, the idea that code should be readable. On top of that he makes object orientation optional, which in ABC was cumbersome.
The name. Guido did not want to name it after another person, unlike many fashions of the era. The name comes from Monty Python, the British comedy group: it was short, and the first two letters were in alphabetical order as in the "Perl" it was replacing. It is a choice of humility, not a whim.
1991, the first public release. On 20 February 1991 version 0.9.0 comes out, distributed on Usenet. The Git repository did not exist yet: the code moved between computers as attachments to emails and discussion groups, and whoever was not connected at the moment of the handover was left out. One of the reasons why the early versions struggled to spread is this.
1994, Python 1.0. Released on 26 January 1994, Guido is by now working in the United States. He sets two design rules that still hold: extensions written in C are discouraged, in C++ they are banned; and the language must remain readable even for those who do not design it.
The benevolent dictator. Guido takes on the role of benevolent dictator: he decides, and he explains publicly why. This model is the form of Python’s governance still today, and it is the thing that most contributes to the stability of a language: when someone decides in a hurry, and the reason is public, the project does not fragment.
The slowcoach tortoise. The name CPython, the reference implementation, comes from an acronym: C for the language it is written in, Python for the project. The story of the tortoise falling off a table in an episode of Monty Python's Flying Circus, from which the abundant use of the name Python in the project would derive, is later and far less well founded. It is worth saying: it is a nice story, and it is not a fact.
2000, the fork. On 16 October 2000 Python 2.0 is released, which introduces list comprehensions, a garbage collector that collects cyclic references, augmented assignment and a transition to a more transparent development process supported by the community. It is also the year in which Guido announces that the language is evolving and that "Python 2 will be only one of the possible ways of writing Python".
2008, Python 3.0. On 8 December 2008, eight years later. The three technical differences that matter are: real Unicode strings (in Python 2 they were bytes), integer division that yields a float, and a cleaner exception system. The migration was long — almost ten years, with a parallel standard library — but today Python 3 is the only correct way to write new Python.
2018 and 2021, two transitions that matter. On 12 July 2018 Guido resigns from the role of dictator, formally for a "sabbatical", but the message was clear. The Python Software Foundation takes on the governance role, as applies to open source in Open source: why it benefits paid software. In 2021 the PSF receives the "gift of tax": with it it becomes the owner of the licences and the trademarks, closing the case of the commercial use of the names "Python" and "Anaconda". On 1 January 2020, meanwhile, support for Python 2 officially ends.
How it is used, in practice
A pentest with Python is not a script: it is a small programme with structure. The four phases, and what Python tooling covers in each:
| Phase | Question | Python |
|---|---|---|
| Reconnaissance | what is there? | socket, httpx, wrappers for nmap |
| Enumeration | who is it, what does it say? | requests, parsing, sqlite3 for data |
| Verification | is it really vulnerable? | requests + pwntools, one assert per step |
| Report | what do I need to tell the client? | Jinja2, PDF reports |
The transition that needs to be stated is the third: verification, not
"scanning". The difference between a penetration tester and a script kiddie is
the same as the one between nmap -sV and a manual check that the identified
version is really vulnerable to something. The first tells you there is a
port, the second tells you whether you can get in and what you can do with it.
And there are three rules that always apply, in order of importance.
- Authorisation first, always. Not in a footnote to a deliverable: before starting, in writing, with the exact IPs and the time window. A professional tester asks for it, has it signed, and keeps it. If the client does not sign it, the work is not done.
- Do not automate destruction. Python is perfect for automation and this is also its risk. A deletion run as
for ip in range(...)against the wrong IP does real damage and there is no rollback. Destructive operations should be performed by hand, with your eyes on it, and logged. - No secrets in the code. The credentials you find during an assessment belong to the client, and managing a secret is part of the job. They do not end up in a script file, they do not end up in a message, they do not end up in a repository. When the client receives the report, the secret should be rotated: found during a test it is no longer a secret, it is a finding.
The rule we use
The reasoning of CYBER ONIONS on this subject is simple:
Python is excellent for building the right tools, and poor as a substitute for thinking. It gets you to the point where you have to decide what to do — and at that point it no longer helps you.
Its value is not that it makes you dangerous. It is that it makes you read code, and anyone who reads other people’s code stops trusting words.