Step 82. Project — Network Map and Risk Assessment

Step 82. Project — Network Map and Risk Assessment

Level 1 — Programming and the Inside of a Computer | Difficulty ★★★★☆ | Estimated time: 4 hours

Prerequisites: Step 81 complete. You can run an -sV scan with nmap and save -oN files. You know basic Python syntax (file reading, regular expressions, lists).

  • What you need: a Linux terminal, nmap, Python 3. And the scan report file you saved in Step 81.
  • Caution: ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. Every hands-on measurement in this chapter was performed against 127.0.0.1 scan files only.

Do you know the very first thing an attacker does after entering a network? "Draw a map of what’s there." A defender’s first job is exactly the same — because you can’t protect what you don’t know. Today, using Step 81’s scan results as raw material, we run through the entire skeleton of a security assessment once: "discover → organize → prioritize → act." If scanning is nmap’s job, turning the results into a risk table is the job of your Python skills.


1. Learning Objectives

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

  • Define the three terms — asset, attack surface, and risk — and apply them in practice
  • Parse an nmap -oN output file with Python into a structured list
  • Assign a risk level to each service with rule-based logic and attach grounds
  • Build the habit of separating "unidentified" items into an investigation list
  • Complete a one-page "asset inventory + risk assessment table," your first security assessment document

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Linux terminal + Python 3 (script file)
Today’s commands/features nmap -oN (gathering material), Python re.match() (line parsing), lists of dictionaries, rule-based classification
Concepts needed Asset, attack surface, risk (impact × likelihood), the scan output format from Step 81
Today’s artifacts riskmap.py — a converter that turns a scan file into a risk table + one finished assessment table

2-1. Asset, Attack Surface, Risk

Let’s organize the three terms we’ll use today.

  • Asset: the complete inventory of what must be protected. Every device connected to the network, and every service running on it, is an asset.
  • Attack surface: the sum total of contact points the asset has opened to the outside. Each open port is one square of the attack surface. The fewer openings, the narrower the surface; the narrower, the easier to defend.
  • Risk: a sense that weighs together "how much it hurts if breached (impact)" and "how likely it is to be breached (likelihood)." Today, three grades — high/medium/low — are enough.

2-2. Why Parse — A File Is Data

Step 81’s scan output is a human-friendly table, but it’s actually regular text. Regular text can be read by a program. Parsing the scan file with Python makes things like this possible:

  • Automatically repeat the same processing even across a hundred devices
  • Automatically detect "newly opened things" compared with last month’s file
  • Write risk rules as code to secure reproducibility of judgment

Today’s material is a single 127.0.0.1 scan file, but this code expands as-is into a hundred-machine assessment.

2-3. How to Think About Risk

Risk isn’t a hunch — it’s a comparison. Try asking these questions:

  • Does this service travel encrypted, or in plaintext? (telnet/ftp/http are plaintext)
  • Is authentication in place? Is it not a default password?
  • Do I know why this port is open? Not knowing is the most dangerous of all.
  • If this service is breached, what leaks out?

The worse the answers, the higher the risk. Today’s goal isn’t a precise score — it’s deciding the order of "where do we fix first."


3. Follow Along

3-1. Preparing the Material — Reviewing the Scan File

Open the full-scan file you saved in Step 81. If you don’t have it, make it again.

Input

nmap -sV --version-intensity 2 -p- -oN scan_full.txt 127.0.0.1
cat scan_full.txt

Output (measured 2026-09-09, key parts):

# Nmap 7.94SVN scan initiated Wed Sep  9 13:37:10 2026 as: nmap -sV ... -p- -oN scan_full.txt 127.0.0.1
Nmap scan report for localhost (127.0.0.1)
Host is up (0.0000010s latency).
Not shown: 65533 closed tcp ports (reset)
PORT      STATE SERVICE VERSION
8000/tcp  open  http    SimpleHTTPServer 0.6 (Python 3.12.3)
33211/tcp open  unknown

How to read it: from this file, the only lines we’ll have Python read are those of the form number/protocol state service version. The rest (headers, informational messages) must be filtered out — that’s the work of parsing.

3-2. Building the Parser — Picking Out Only the Regular Lines

Input (riskmap.py, stage 1)

import re

def parse_nmap(path):
    """Extract 'port/protocol state service version' lines from an -oN scan file."""
    rows = []
    with open(path, encoding="utf-8") as f:
        for line in f:
            m = re.match(r"^(\d+)/(tcp|udp)\s+(\S+)\s+(\S+)\s*(.*)$", line.strip())
            if m:
                rows.append({
                    "port": int(m.group(1)),
                    "proto": m.group(2),
                    "state": m.group(3),
                    "service": m.group(4),
                    "version": m.group(5).strip(),
                })
    return rows

rows = parse_nmap("scan_full.txt")
for r in rows:
    print(r)

Output (measured 2026-09-09):

{'port': 8000, 'proto': 'tcp', 'state': 'open', 'service': 'http', 'version': 'SimpleHTTPServer 0.6 (Python 3.12.3)'}
{'port': 33211, 'proto': 'tcp', 'state': 'open', 'service': 'unknown', 'version': ''}

How to read it: the regular expression ^(\d+)/(tcp|udp)\s+(\S+)\s+(\S+)\s*(.*)$ matches only lines that "start with number/protocol followed by at least three words." Header lines (starting with #) and lines like Not shown: don’t fit the pattern and are filtered out automatically. Each line becomes one dictionary — now you can cook this data however you like.

Why: "turning a document into data" is the first step of all automation. The regular expressions from Step 48 make their field debut here.

3-3. Planting Risk Rules — Judgment as Code

Input (riskmap.py, complete version)

import re

def parse_nmap(path):
    rows = []
    with open(path, encoding="utf-8") as f:
        for line in f:
            m = re.match(r"^(\d+)/(tcp|udp)\s+(\S+)\s+(\S+)\s*(.*)$", line.strip())
            if m:
                rows.append({
                    "port": int(m.group(1)), "proto": m.group(2),
                    "state": m.group(3), "service": m.group(4),
                    "version": m.group(5).strip(),
                })
    return rows

RISK_RULES = [
    (["telnet", "ftp"], "HIGH", "legacy protocol with no encryption"),
    (["http", "http-alt"], "MED", "plaintext web service — contents may be exposed"),
    (["ssh"], "LOW", "encrypted — just keep the version up to date"),
]

def assess(service):
    for names, grade, reason in RISK_RULES:
        if service in names:
            return grade, reason
    return "?", "unidentified — needs investigation"

rows = parse_nmap("scan_full.txt")
print(f"{'Port':<11}{'State':<8}{'Service':<12}{'Risk':<6}Basis")
for r in rows:
    grade, reason = assess(r["service"])
    print(f"{r['port']}/{r['proto']:<7}{r['state']:<8}{r['service']:<12}{grade:<6}{reason}")

unknown = [r for r in rows if assess(r["service"])[0] == "?"]
print(f"\nUnidentified services: {len(unknown)} — added to investigation list")

Output (measured 2026-09-09):

Port       State   Service     Risk  Basis
8000/tcp   open    http        MED   plaintext web service — contents may be exposed
33211/tcp  open    unknown     ?     unidentified — needs investigation

Unidentified services: 1 — added to investigation list

How to read it: RISK_RULES is "judgment put into writing." Services that match a rule receive a grade and grounds, while unknown, which matches nothing, is separated into the investigation list with a ?. The line under the table — "Unidentified services: 1" — is this tool’s true protagonist: because the most dangerous square on the map is the square you don’t know.

Why: when risk is assigned by code, feeding the same file next month produces the same judgment. Reproducible judgment is a report’s credibility.

3-4. Make a Prediction — How Should We Handle unknown?

Here’s a prediction. If you assigned a risk level to an unknown service, which would be right: ① LOW (unknown, so low) ② MED ③ HIGH (unknown, so high) ④ don’t assign one and separate it into an investigation list?

Check for yourself: our code chose ④. And in Step 81, section 3-7, we traced this port’s identity — confirming an HTTP/1.0 404 Not Found response in the -sV fingerprint, and finally confirming with ss -tlnp that the port was opened by a container management program (measured 2026-09-09).

How to read it: ④’s power is that it doesn’t paper over "don’t know" with a grade, but connects it to an investigative action. The ? in a risk table isn’t a blank — it’s a to-do.

Why: real breaches usually begin with "a service nobody knows who opened." The habit of recording what you don’t know as unknown and chasing it to the end is what makes a map accurate.

3-5. Completing the Map — Turning the Table into a Document

Finally, transfer Python’s output into an assessment table for humans to read. In a spreadsheet or Markdown, in a format like this:

Asset Port Service/Version Risk Action
My PC (127.0.0.1) 8000 SimpleHTTPServer 0.6 (Python 3.12.3) MED For practice — shut down when done
My PC (127.0.0.1) 33211 unknown → investigated: container management service LOW (after verification) Identity confirmed; keep local-only

How to read it: if the action column is empty, that map is a wall ornament. However refined the risk levels, it’s only an assessment when there’s "so what will we do." The very process by which a port that was unknown gets corrected to "LOW (after verification)" through investigation is a good report row.

Why: the flow discover (scan) → organize (table) → prioritize (risk) → act (action) is exactly the same skeleton as a corporate security review. Today’s single page is its miniature.


4. Missions & Exercises

Mission — Your Home Network Map (after getting consent)

Only those who are ready: get your family’s understanding before proceeding. If you only have a lab, a lab network works too.

  1. Find your address and subnet with ip a (Linux) or ipconfig (Windows) (e.g., 192.168.0.10/24).
  2. Draft an asset inventory with a ping scan: sudo nmap -sn 192.168.0.0/24 -oN step82_ping.txt
  3. Investigate each discovered device with sudo nmap -sV -oN step82_devicename.txt address.
  4. Extend riskmap.py to read multiple files at once (take file paths as a list) and print a per-device, per-port risk table.
  5. Fill in the "action" column of the finished table, and investigate until no unidentified (?) items remain. Don’t leave anything unknown.

Exercises

Exercise 1. Explain the relationship among asset, attack surface, and risk in one sentence each.

Exercise 2. From the pattern’s perspective, explain why riskmap.py’s regular expression skips lines like Not shown: 65533 closed tcp ports (reset).

Exercise 3. Why is the design that gives unknown a ? and separates it into an investigation list better than a design that gives it LOW?

Exercise 4. You scan the same network again a month later. What feature could you add to riskmap.py to automatically find "ports newly opened compared to last month"? Explain the idea without code.


5. Model Answers & Completion Criteria

Mission Model Answer

Skeleton of the extension that reads multiple files:

import re, sys

def parse_nmap(path):
    rows = []
    with open(path, encoding="utf-8") as f:
        for line in f:
            m = re.match(r"^(\d+)/(tcp|udp)\s+(\S+)\s+(\S+)\s*(.*)$", line.strip())
            if m:
                rows.append({"port": int(m.group(1)), "proto": m.group(2),
                             "state": m.group(3), "service": m.group(4),
                             "version": m.group(5).strip()})
    return rows

for path in sys.argv[1:]:          # python3 riskmap.py file1 file2 ...
    print(f"=== {path} ===")
    for r in parse_nmap(path):
        ...                        # the assessment code from 3-3, as-is

Verification example based on measurement: feed it the 127.0.0.1 full-scan file and you should get two rows — 8000 (http, MED) and 33211 (unknown, ?) — and the last line should read "Unidentified services: 1" (measured 2026-09-09). In the home-network mission, success is the same output repeating for each device file.

How to verify: ① does python3 riskmap.py scan_full.txt print the table in one shot? ② are unknown items marked ? and caught in the investigation-list count? ③ is an action filled in for every row of the final table? ④ is family consent recorded on the first line of your notes?

Exercise Answers

Answer 1. An asset is the complete inventory of what must be protected; the attack surface is the sum of contact points (open ports) that asset has opened to the outside; risk is a prioritization sense that weighs, per asset, "impact if breached" together with "likelihood of being breached." The map is a document that attaches attack surface and risk to an asset inventory.

Answer 2. Because the pattern starts with ^(\d+)/(tcp|udp). Not shown: doesn’t start with a digit, and it lacks the "number/protocol" head of the 8000/tcp form, so the match fails at the very first character. The same goes for the header’s # lines.

Answer 3. Giving it LOW makes it look like "a verified, safe item," and the investigation stops. Something unknown is in a state where risk assessment is impossible — not a state of low risk. A ? is assessment deferred + an action (investigation) scheduled, so the gap remains explicitly on the map and necessarily leads to follow-up action. Given that real incidents start with "services nobody knows who opened," explicitly marking unknown squares is itself defense.

Answer 4. Parse each of the two files, build a set of (port, service) pairs, then print the difference — this month’s set minus last month’s set. You can implement it with Python’s set and the - operator, and "disappeared ports" (last month − this month) works the same way. That is the algorithm of change monitoring.

Completion Criteria Checklist

  • [ ] I can define asset / attack surface / risk in my own words
  • [ ] I can parse an nmap -oN file with Python regular expressions
  • [ ] I can express risk rules as code and attach grounds
  • [ ] I separated unknown items as ? and connected them to investigation
  • [ ] I completed one risk assessment table with the action column filled in
  • [ ] (If doing the mission) I got family consent and completed a map of the whole subnet

6. Common Pitfalls & Fixes

Wall 1. My parse results are completely empty

Symptom: rows comes out as an empty list.

Cause: the file path is wrong, or if you saved screen output copied as-is instead of -oN, the line shapes may differ subtly.
Fix: check the file contents with your eyes using cat scan_full.txt, and shrink the regular expression one step at a time (starting from ^(\d+)) to find where it stops matching. Debugging is reconnaissance too.

Wall 2. Output containing Korean gets garbled

Symptom: the table’s Korean column alignment is off, or characters are broken.

Cause: Korean characters occupy two screen cells, but f-string width alignment only counts characters. Opening a cp949-encoded file as UTF-8 also garbles it (review Step 50).
Fix: alignment doesn’t have to be pretty as long as the data is right. When opening files, specify encoding="utf-8" explicitly.

Wall 3. My criteria for assigning risk are fuzzy

Symptom: you hesitate every time over where to put high/medium/low.

Cause: that’s normal. Risk is a sense refined by experience.
Fix: ask only three things. ① Does it travel in plaintext? ② Is authentication absent or a default password? ③ Do I not know this port? If two or more of the three are bad, it’s HIGH. And if you write the rules in code (RISK_RULES), the same criteria hold next month.

Wall 4. Scanning at home, fewer devices show up than expected

Symptom: the TV is clearly on, but it’s not in the list (Screen example situation).

Cause: it’s asleep, or the device doesn’t answer pings, or it’s on a different subnet like a guest network.
Fix: wake the device and rescan. Cross-checking with the router admin page’s client list helps you find missing devices. "Not detected" isn’t "absent" — it’s "didn’t answer."

Wall 5. A security warning popped up on a family member’s device while scanning

Symptom: a security alert went off on a device you scanned.

Cause: modern devices detect port scans and report them. Your scan got "seen."
Fix: if you told them in advance, there’s nothing to worry about — which is why condition number 0 of the mission was consent. That detection is exactly the defender’s eye. It’s a great opportunity to learn how your scans look, and in Step 84 we’ll see these footprints again at the packet level.


7. Summary

Today’s Concepts

Concept One-line explanation
Asset The complete inventory of what must be protected — devices and the services on them
Attack surface The sum of contact points an asset has opened — one square per open port
Risk A comparative sense of impact × likelihood — high/medium/low is enough
The meaning of ? Assessment deferred + investigation scheduled. The most dangerous square on the map
Reproducible judgment Writing rules as code so the same conclusion comes out next month too

Today’s Commands and Code

Command/Code What it does
nmap -sV -p- -oN file target Gather material (scan report)
`re.match(r"^(\d+)/(tcp udp)\s+(\S+)\s+(\S+)\s*(.*)$", line)`
List of dictionaries + list of rules Convert discoveries into a risk table
sys.argv[1:] Process multiple scan files at once
Set difference (this month − last month) Change monitoring for "newly opened ports" (mission extension)

An Instinct More Important Than Commands

An assessment doesn’t end as a perfect document — it ends as "a meeting that decides the next action." In today’s table, the most important column is the action column, and the most dangerous column is the ? column. And a map isn’t drawn once and done — it’s a document you redraw regularly to monitor change. If the map you redraw a month later has one fewer ?, that is your first security achievement report.

Lastly, this map is double-sided. To an intruder, the same map becomes a list of weaknesses, so the map file itself is also something to protect. And never draw a map of a network you don’t have permission to map in the first place — that is the usage license for this skill.


Once every box is checked, Step 82 is complete. Click the checkbox in the sidebar to save your progress.