Step 164. Enumeration Tools, Complete Review — A System for Flipping Every Stone

Step 164. Enumeration Tools, Complete Review — A System for Flipping Every Stone

Level 2 — Introduction to Security and Attack Skill Basics | Difficulty ★★★☆☆ | Estimated time: 3 hours

Prerequisites: Step 81 (nmap), Step 126 (linPEAS), and Step 145 (directory discovery) complete. You can open a socket connection in Python.

  • What you need: Python 3, a Linux lab (WSL or Kali), a personal wiki (for storing checklists)
  • ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.
  • Chapter type: today is a [project] chapter. The goal is not new tools but "organizing the tools so far by situation and completing your own enumeration script."

Enumeration is "flipping every single stone." Ports, service versions, web paths, users, shared folders — every clue you must find in a penetration test comes out of enumeration. The problem is that a person working without a checklist inevitably misses something. That’s why practitioners bundle tools into automation and fix their own inspection order in a document.

Today you make two things: a single map organizing the enumeration tools you’ve learned by situation, and your own enumeration script that rolls by itself from port scan to web path enumeration. Building a script yourself makes you understand in your body what nmap and gobuster do inside — and while actually building it, I hit one wall I hadn’t expected, and that accident became today’s best lesson.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Pick the right tool for each enumeration situation (external network / web / host internals) without hesitation
  • Write an "enumeration checklist" in your own words and explain the reasons for its order
  • Build a port-scan + path-enumeration script yourself with Python sockets and urllib
  • Show with measurements why banner grabbing is the first step of service identification
  • Operate automation tool results in a workflow of "collect wide, read deep"

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Python 3 (standard library only) + Linux shell (WSL or Kali)
Today’s commands ss -tln, nmap -sV, (in the lab) autorecon
Concepts needed the 3-situation classification of enumeration, banner grabbing, connect scans, automation vs manual role division
Today’s artifact one enumeration checklist + my_enum.py (your self-built enumeration script)

2-1. What Is Enumeration — The Difference from Scanning

If a port scan asks "is the door open?", enumeration asks "what’s beyond the door?" It’s the stage of harvesting concrete information usable for attack — the service version behind an open port, a web server’s hidden paths, the system’s user list and shared folders.

nmap’s -sV (version detection) and -sC (default scripts) from Step 81 were in fact the start of enumeration, and Step 126’s linPEAS was the automation of "host-internal enumeration." Today you merge those pieces into one map.

2-2. The Situation-by-Situation Tool Map

Organizing the tools so far by "what are you enumerating" gives this:

Situation The question asked Tool/command Learned in
External network Which ports are open? nmap -sV -sC target Step 81
Web service Are there hidden paths? gobuster dir -u URL -w wordlist family Step 145
Host internals (Linux) Which services are listening? ss -tlnp Step 34
Host internals (privileges) setuid files? find / -perm -4000 Steps 97, 126
Host internals (whole) Where’s the gap to climb? linpeas.sh Step 126

How to read it: when you meet a new target, you move down this table from top to bottom. Start outside; once you gain a foothold, move inside. Always being conscious of "which row am I on right now?" is the start of systematic enumeration.

2-3. Why Automate, and Why Humans Read

A person enumerating without a checklist inevitably misses things. That’s why automation frameworks like AutoRecon run nmap, gobuster, and enum4linux in one go and organize the results into folders. Tools are strong at collecting wide.

But the eye that reads the output is a human’s to grow. Picking "odd version, non-standard port, anonymous access allowed" out of the hundreds of lines automation gathered is something tools can’t do. This is why you build a script yourself today — only someone who knows automation’s insides reads its output properly.

2-4. Banner Grabbing — A Service’s Self-Introduction

Many services send a greeting first on mere connection. This first sentence is called a banner, and the act of receiving and reading it is banner grabbing. SSH is the representative case — the moment you connect, it declares its identity with SSH-2.0-OpenSSH_.... A single banner line often reveals the OS and software version, making it enumeration’s first ingredient.

Note that services like HTTP — "answer only when the client speaks first" — have no banner. In today’s measurement, this difference shows up vividly.


3. Follow Along

3-1. Launching a Simulated Target Server

Let’s make a web server with "a few slightly hidden things" to test the enumeration script against. This Flask server listens only on 127.0.0.1 port 8090 (this textbook measured it on 2026-09-09).

Input (target164.py)

from flask import Flask, abort

app = Flask(__name__)

@app.route("/")
def index():
    return "Welcome to lab-server"

@app.route("/robots.txt")
def robots():
    return "User-agent: *nDisallow: /backupn", 200, {"Content-Type": "text/plain"}

@app.route("/admin")
def admin():
    abort(403)          # exists but forbidden — information to an enumerator

@app.route("/backup")
def backup():
    return "db dump (fake)", 200, {"Content-Type": "text/plain"}

@app.route("/api")
def api():
    return '{"status": "ok"}', 200, {"Content-Type": "application/json"}

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=8090)

How to read it: the point is that /admin returns 403 (Forbidden), not 404 (Not Found). "Doesn’t exist" and "exists but blocked" are completely different information to an enumerator. The Disallow: /backup in /robots.txt is also a classic information exposure — "please don’t look" is itself a map.

3-2. My Own Enumeration Script v1 — And an Unexpected Fall

Let’s move the checklist’s first two boxes, "port scan → web path enumeration," into code.

Input (my_enum.py v1 — core part)

import socket
import urllib.request
import urllib.error

TARGET = "127.0.0.1"
PORTS = [21, 22, 25, 53, 80, 443, 445, 3000, 3306, 5000, 8000, 8080, 8090]
PATHS = ["admin", "login", "backup", "robots.txt", ".git",
         "config", "api", "test", "uploads", "secret"]

def scan_ports(host, ports, timeout=0.5):
    open_ports = []
    for p in ports:
        s = socket.socket()
        s.settimeout(timeout)
        if s.connect_ex((host, p)) == 0:   # 0 = connection succeeded
            open_ports.append(p)
        s.close()
    return open_ports

def probe_http(host, port, paths):
    found = []
    for path in paths:
        url = f"http://{host}:{port}/{path}"
        try:
            with urllib.request.urlopen(url, timeout=2) as r:
                found.append((url, r.status))
        except urllib.error.HTTPError as e:
            if e.code in (401, 403):
                found.append((url, e.code))
        except urllib.error.URLError:
            return None
    return found

Run (with python target164.py up in terminal 1, then in terminal 2)

python my_enum.py

Output (measured 2026-09-09):

=== Stage 1: Port scan (127.0.0.1, 13 ports) ===
[+] Open: 22/tcp
[+] Open: 445/tcp
[+] Open: 8090/tcp

=== Stage 2: Web path enumeration (10 paths) ===
Traceback (most recent call last):
  ...
http.client.BadStatusLine: SSH-2.0-OpenSSH_for_Windows_9.5

How to read it: the script died on port 22. The cause is interesting — it tried HTTP on every open port, but 22 was SSH, and SSH sent its banner SSH-2.0-OpenSSH_for_Windows_9.5 first upon connection. urllib failed trying to interpret that as an HTTP response.

But flip it over: the error message told you the service’s identity. This "accident" is exactly the invention moment of banner grabbing.

3-3. v1.1 — Adding a Banner-Grab Stage

Turn the failure into a feature. Before path enumeration, add a stage that briefly connects to each open port and receives any greeting that comes first.

Input (function to add in v1.1)

def grab_banner(host, port, timeout=1.0):
    """Receive the banner from ports where the service greets first."""
    try:
        s = socket.socket()
        s.settimeout(timeout)
        s.connect((host, port))
        data = s.recv(64)
        s.close()
        return data.decode(errors="replace").strip()
    except (socket.timeout, OSError):
        return ""    # services that don't greet (HTTP, etc.)

And widen probe_http‘s except urllib.error.URLError to except Exception so non-HTTP ports are quietly skipped.

Output (measured 2026-09-09):

=== Stage 1: Port scan (127.0.0.1, 13 ports) ===
[+] Open: 22/tcp
[+] Open: 445/tcp
[+] Open: 8090/tcp

=== Stage 2: Banner grab ===
[22/tcp] SSH-2.0-OpenSSH_for_Windows_9.5
[445/tcp] (no banner — speaks only when spoken to)
[8090/tcp] (no banner — speaks only when spoken to)

=== Stage 3: Web path enumeration (10 paths) ===
[22/tcp] Not HTTP — skipped
[445/tcp] Not HTTP — skipped
[+] 403 http://127.0.0.1:8090/admin
[+] 200 http://127.0.0.1:8090/backup
[+] 200 http://127.0.0.1:8090/robots.txt
[+] 200 http://127.0.0.1:8090/api

=== Enumeration complete ===

How to read it: record three things. ① On port 22, a single banner identified "OpenSSH 9.5, Windows." ② 445 (SMB) and 8090 (HTTP) don’t greet first — protocols have different personalities. ③ Path enumeration distinguished 403 from 200 and found /admin (exists but forbidden) and /backup (exposed). A small script of 13 ports and 10 paths performed three checklist boxes by itself.

3-4. Linux Internal Enumeration — Real Outputs of ss and nmap -sV

Once you’ve gained a foothold after intrusion, "what’s running inside this box?" is the next question. These outputs were measured on WSL (Ubuntu 24.04).

Input

ss -tln

Output (measured 2026-09-09):

State  Recv-Q Send-Q  Local Address:Port  Peer Address:PortProcess
LISTEN 0      4096    127.0.0.53%lo:53         0.0.0.0:*
LISTEN 0      1000   10.255.255.254:53         0.0.0.0:*
LISTEN 0      4096       127.0.0.54:53         0.0.0.0:*
LISTEN 0      4096        127.0.0.1:45381      0.0.0.0:*

How to read it: services bound to 127.0.0.1 are invisible from outside but visible to internal enumeration — finding "services closed outside but open inside" is the core of internal enumeration.

Next, we launched a temporary web server and measured nmap’s version detection.

Input

python3 -m http.server 8000 &      # terminal 1
nmap -sV -p 8000 127.0.0.1         # terminal 2

Output (measured 2026-09-09):

PORT     STATE SERVICE VERSION
8000/tcp open  http    SimpleHTTPServer 0.6 (Python 3.12.3)

How to read it: even on the HTTP port where our script said only "(no banner)," nmap sends probes and identifies SimpleHTTPServer 0.6 (Python 3.12.3). When there’s no banner, you speak first and identify by the shape of the answer — this is -sV‘s internal behavior, a line we can read because we built the script ourselves.

3-5. Automation Framework — AutoRecon (Lab Practice, Output Example)

Do this on the Kali↔MS2 lab. Outputs are output examples.

pipx install git+https://github.com/Tib3rius/AutoRecon.git   # install (or use the Kali package)
sudo autorecon MS2_IP
[*] Scanning target MS2_IP
[!] [nmap-full-tcp] finished ... results/results/MS2_IP/scans/
...

How to read it: AutoRecon automatically chains a full port scan → per-discovered-service follow-up scans (path scans for web, share enumeration for SMB) and organizes results under results/target/scans/. It’s an aggressive scan — lab only. When done, move on to 3-6.

3-6. Automation vs My Hands — The Comparison Table Is Today’s Artifact

Put the AutoRecon results folder, 3-3’s script results, and 2-2’s checklist side by side and compare them in a table.

Item What automation found What I found Note
Open ports 〇 (all of them) 〇 (only the common ones) Tools are wider
Service versions △ (only ones with banners) -sV’s probes are powerful
Hidden paths 〇 (big wordlist) △ (as big as my wordlist) Wordlist quality decides
Priority judgment × "what to look at first" is a human’s job

How to read it: the last row is the core. The more results there are, the more a human eye reading in the order "odd version → non-standard port → anonymous access" decides the match. Tools wide, humans deep — this division of labor is the workflow today confirms.


4. Missions & Exercises

Mission — Completing My Own Enumeration Script and Checklist

  1. Complete the 3-2~3-3 script, run it against my target server (3-1), and record the full output including banner-grab results
  2. Add a "save results to a file" feature to the script (saved inside the code, not my_enum.py > enum_results.txt)
  3. Complete "my enumeration checklist" — 2-2’s tool map rewritten in my words: minimum three stages (external/web/internal), each stage including commands and "when to use it"
  4. (Lab) Scan MS2 with AutoRecon and find one "thing I would have missed" in the results, then record it
  5. At the bottom of the checklist, write three lines of "priority for reading results"

Exercises

Exercise 1. Define the difference between a port scan and enumeration without the "door" metaphor.

Exercise 2. Explain why the v1 script died in 3-2, and why that accident became the discovery of the formal technique called "banner grabbing."

Exercise 3. When /admin returns 403 instead of 404, what information does an enumerator gain?

Exercise 4. Using 3-6’s comparison table as evidence, explain why you must maintain "my own checklist" separately even while using automation tools.


5. Model Answers & Completion Criteria

Mission Model Answer

Checklist skeleton example:

### My Enumeration Checklist v1
1. External: nmap -sV -sC target → open ports and versions
   - When: the first 5 minutes with a new target
2. When web is found: path wordlist enumeration (gobuster family / my script)
   - When: immediately when 80, 443, or 8000-series ports appear
3. After internal intrusion: ss -tlnp, find / -perm -4000, linpeas.sh
   - When: right after gaining a shell
### Priority for reading results
1. Odd/old versions → 2. Non-standard ports → 3. Anonymous access allowed

How to verify: ① Does the script run to the end (without errors), banner grab included? ② Does it record 403 and 404 distinguished? ③ Does each checklist row have "when to use it" — a checklist without this column is just a command collection? ④ Does the save-results feature work?

Exercise Answers

Answer 1. A port scan checks "which ports accept connections"; enumeration harvests concrete information beyond the open ports (service versions, paths, users, shares). Scanning builds the list; enumeration dissects each item.

Answer 2. It sent HTTP requests to every open port, and BadStatusLine occurred trying to interpret the banner the SSH port sent first as an HTTP response. But since the error message itself was the service’s identity, flipping it into a formal procedure of "read the data that comes first after connecting" (banner grabbing) turns the failure into an identification feature.

Answer 3. The very fact that "the path exists." 404 is absence; 403 is "exists but blocked" — the attacker can target an existing admin page, gaining a candidate for authentication bypass or access through another path.

Answer 4. Tools collect wide but can’t judge priorities (3-6’s last row). And in environments without tools, or where tools fail, the only one who can move is the person with the checklist in their head. A checklist is not a substitute for tools — it’s the procedure manual for reading tool output.

Completion Criteria Checklist

  • [ ] I built the target server and enumeration script myself and ran them
  • [ ] I reproduced v1’s BadStatusLine accident and fixed it in v1.1
  • [ ] I actually received an SSH banner via banner grabbing
  • [ ] I can explain the information difference between 403 and 404
  • [ ] I completed my enumeration checklist with "when to use it" attached
  • [ ] (Lab) I compared AutoRecon results with manual results
  • [ ] I wrote three lines of result-reading priorities in my own words

6. Common Pitfalls & Fixes

Wall 1. The script dies midway — http.client.BadStatusLine: SSH-2.0-...

Symptom: it stops with a traceback during path enumeration.
Cause: you treated every open port as HTTP. SSH, FTP, etc. speak first in their own protocols.
Fix: widen the exception to Exception as in 3-3, handling it as "not HTTP → skip," and identify that port with banner grabbing. The error is itself information.

Wall 2. All banners come back blank

Symptom: grab_banner returns "" for everything.
Cause: that can be normal — HTTP, SMB, etc. answer only when the client speaks first. If no data arrives within the timeout, blank is correct.
Fix: if blank, record it as a "passive service" and move to a probe-sending stage like nmap -sV. No banner is also a clue to protocol identification.

Wall 3. The port scan is endlessly slow

Symptom: 13 ports take tens of seconds.
Cause: closed ports wait the full timeout (default 0.5 s). Port count × timeout is the total time.
Fix: that’s normal for your own lab practice. When widening the range, reduce the timeout or use threads — but note that mass scanning outside the lab is itself a detection/blocking target.

Wall 4. Too many results — no idea what to read first

Symptom: the AutoRecon results folder has dozens of files.
Cause: you haven’t decided a reading order.
Fix: use a fixed order — ① odd or old versions, ② non-standard ports (web on 8080? SSH on 2222?), ③ anonymous access allowed (FTP anonymous, SMB guest). Embedding these three lines in your checklist is Mission #5 today.

Wall 5. I feel like running AutoRecon on a real network

Symptom: you want to run it as a "test" on a company or school network.
Cause: the tool’s convenience numbs your sense of boundaries.
Fix: don’t. AutoRecon pours full-port scans plus aggressive scripts; running it without permission is clearly illegal and an IDS detects it instantly. Run it only in the lab (MS2, vulnerable VMs).


7. Summary

Today’s Concepts

Concept One-line explanation
Enumeration The stage of harvesting concrete information (versions, paths, users) beyond open doors
Banner grab Identifying a service by the greeting it sends first upon connection
403 vs 404 "Exists but forbidden" vs "doesn’t exist" — 403 is also information to an enumerator
Automation framework Runs nmap + path scans + service enumeration in one go and organizes results (AutoRecon, etc.)
Wide/deep division Collection is the tool’s job; priority judgment is the human’s

Today’s Commands

Command What it does
ss -tln See TCP ports this host is listening on (internal enumeration)
nmap -sV -p port target Send probes to identify service versions
find / -perm -4000 Enumerate setuid files (privilege-escalation material)
sudo autorecon target Lab-only enumeration automation
socket().connect_ex() (Python) The port-scan heart of my script

An Instinct More Important Than Commands

Today’s real harvest is not the script but the experience of flipping one error into a technique. BadStatusLine was not a failure — it was a service’s self-introduction. In the world of enumeration, this flipping is everyday life: 403 is both a refusal and a map, robots.txt is both a ban list and a treasure map, and an empty banner is both silence and proof of a protocol’s personality.

And one fact that doesn’t change no matter how good tools get: machines collect better, but it’s the person with a checklist who knows "where in these hundreds of lines is something odd." The checklist you made today is a document that will open as the first page in every penetration exercise ahead, like Step 105’s technique classification table. Write down its update rules alongside it.


Once every box is checked, Step 164 is complete.