# How port scanning / service reconnaissance (nmap) works and its defense [2026] — visualizing the attack surface and RFC-compliant reduction

> An explanation of 'port scanning,' the core of network reconnaissance, faithful to the nmap official documentation and NIST SP 800-115. It explains, from TCP's state transitions, how host discovery, TCP SYN scan, and version/OS detection work, and why a stealth scan is detected. With all-legal procedures confined to your own lab, it shows the defenses — attack-surface minimization, IDS detection, and security-group design — in type-safe code.

- Published: 2026-06-28
- Author: 友田 陽大
- Tags: セキュリティ, ネットワーク, TCP/IP, 脆弱性診断, ホワイトハッカー, 可観測性
- URL: https://tomodahinata.com/en/blog/network-reconnaissance-port-scanning-nmap-service-detection-defense-guide
- Category: 実践ネットワーク攻撃と防御
- Pillar guide: https://tomodahinata.com/en/blog/network-penetration-testing-methodology-attack-defense-guide

## Key points

- Port scanning is the starting point of all attacks. It's the reconnaissance that exposes 'which ports are open and what service is running,' the core of NIST SP 800-115's discovery phase. Without seeing the attack surface, you can neither attack nor defend.
- The TCP SYN scan (-sS) judges 'open' on SYN→SYN/ACK without completing the three-way handshake and aborts with RST. It's called 'stealth' because it doesn't establish a full connection, but it's transparent to modern IDS/firewalls.
- Port state is three: open/closed/filtered. filtered (no response) indicates the presence of a firewall. nmap estimates the service version with -sV, the OS with -O, and lightly pokes known weaknesses with -sC.
- The first defense is 'attack-surface minimization' — keep open ports to the minimum necessary and don't expose management ports. On AWS, write Security Groups with minimal permission and go via a bastion/SSM. This is achievable mechanically with configuration.
- The second is 'detection.' A scan has the characteristic of trying to connect to many ports in a short time, detectable with an IDS (Suricata) or flow logs. Defend in two tiers: attack-surface reduction (prevention) and detection (discovery).

---

Network attacks begin, almost without exception, with a **port scan.** The first thing an attacker does is learn "which hosts are alive, which ports are open, and what's running there" — the core of [NIST SP 800-115](https://csrc.nist.gov/pubs/sp/800/115/final)'s **Discovery phase.** Conversely, **if the defender doesn't accurately grasp their own attack surface with `nmap`, they're in a dangerous state where the attacker knows their network better than they do.**

Following [the big picture of network pentesting](/blog/network-penetration-testing-methodology-attack-defense-guide), this article explains how port scanning works faithfully to the [nmap official documentation](https://nmap.org/book/man.html), and on top of that shows the defenses of **attack-surface minimization and detection** in type-safe code.

> **Strict safe zone**: all the `nmap` commands in this article are run only against **a VM you manage yourself** (e.g., `10.10.10.10`) in your [home lab](/blog/ethical-hacking-home-lab-kali-juice-shop-ctf-self-study-roadmap-guide). **Scanning an unauthorized third-party host or public server**, beyond being an issue under the Unauthorized Access Act, is a **harmful act that can be recorded by the other party's IDS and reported.** You may scan only "your own assets, CTFs, or a scope authorized in writing" — always read the [law article](/blog/ethical-hacker-law-japan-unauthorized-access-act-active-cyber-defense-disclosure-guide) first.

---

## 1. The principle of port scanning — "quitting the TCP handshake midway"

Port scanning exploits the behavior of the [TCP three-way handshake](/blog/tcp-three-way-handshake-state-transition-retransmission-congestion-control-guide). When you send SYN to a port, the other side's reaction reveals the state.

```text
■ ポートが「開いている（open）」場合
  scanner ── SYN ──► target:port      "開いてる？"
  scanner ◄─ SYN/ACK ─ target         "うん、開いてるよ"   ← これで「open」と判定
  scanner ── RST ──► target           "やっぱやめた"（接続を張らずに中断）

■ ポートが「閉じている（closed）」場合
  scanner ── SYN ──► target:port
  scanner ◄─ RST ──── target          "そのポートは閉じてる"   ← 「closed」と判定

■ ファイアウォールに「遮断されている（filtered）」場合
  scanner ── SYN ──► target:port
  （無応答 / ICMP unreachable）          ← 「filtered」と判定（FWの存在が分かる）
```

This — "send SYN, judge open if SYN/ACK returns, but don't return ACK and abort with RST" — is the **TCP SYN scan (`-sS`, half-open scan).** Because it doesn't establish a full connection (ESTABLISHED), it was once called "stealth."

### 1.1 "Stealth scan" is transparent nowadays

Let me correct an important misconception. **A SYN scan is easily detected by modern IDS/firewalls.** The reason is simple: the very behavior of "sending only SYN to many ports in a short time, and not returning ACK to SYN/ACK but aborting with RST" is **a clear signature impossible in normal communication.** Not letting the attacking side hold the illusion of "stealth" is the defender's starting point (we implement this detection in §4).

### 1.2 The three states become "a map for defense"

| State | Meaning | The defender's reading |
|---|---|---|
| **open** | A service is listening | Attack surface. Ask **whether exposure is truly needed** |
| **closed** | Responds but no service | The host's existence is revealed. Room to hide with a FW |
| **filtered** | Blocked by FW/SG | **The ideal state.** Gives the attacker no information |

The goal of defense is clear — **move ports that shouldn't be exposed from `open` to `filtered`.**

---

## 2. The reconnaissance procedure — see "your own attack surface" in the lab

Against your own VM, visualize the attack surface from the same viewpoint as an attacker.

```bash
# ① ホスト発見：ラボのセグメントで「生きているホスト」を洗い出す（自分のVMのみ）
nmap -sn 10.10.10.0/24
#   -sn = ポートスキャンせずホスト発見だけ（ping sweep）

# ② TCP SYN スキャン：開いているポートを特定（自分のVM 10.10.10.10 のみ）
sudo nmap -sS 10.10.10.10
#   sudo が要るのは raw socket で SYN を直接組み立てるため

# ③ サービス・バージョン検出：開いたポートで「何が動いているか」を特定
nmap -sV 10.10.10.10
#   例: 22/tcp open ssh OpenSSH 8.9 / 80/tcp open http nginx 1.24.0
#   ↑ 版数が分かると、その版の既知脆弱性(CVE)に直結する＝最も危険な情報漏れ

# ④ OS 推定 + デフォルトスクリプト（軽い既知チェック）
sudo nmap -O -sC 10.10.10.10
#   -O  = TCP/IP スタックの癖から OS を推定
#   -sC = 安全寄りのデフォルトNSEスクリプト（バナー取得等）
```

**The biggest lesson for the defender** is ③. When `-sV` **exposes even the version** like `nginx 1.24.0`, an attacker can immediately look up "the known CVEs of that version." You can feel how important it is to **hide / keep up to date the version in the service banner.**

> `nmap` output can be saved as XML with `-oX scan.xml`. It can be used for a diagnostic report or for the "continuous monitoring of your own attack surface" described later. `nmap` as **an inventory of your own assets** is a legitimate, essential skill for the defender.

---

## 3. Defense ①: attack-surface minimization — crush it mechanically with configuration

The strongest defense against scanning is to **reduce the open ports in the first place.** This is achievable mechanically with design and configuration.

### 3.1 The principle — "minimum necessary exposure"

- **Don't expose management ports (SSH:22, RDP:3389, DB:5432/3306).** Use a bastion, or on AWS, [SSM Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html) (connect without opening any port).
- **Put databases and internal APIs in a private subnet.** Make them unreachable from the internet.
- **Consolidate exposure to 443 (HTTPS)** and branch the route behind it.

### 3.2 Write an AWS Security Group with "minimal permission"

In the cloud, the design of the firewall = security group (SG) is itself the attack surface. **Opening SSH with `0.0.0.0/0`** is a typical accident.

```hcl
# ❌ 事故の典型：SSH を全世界に開放（スキャンで即発見され総当たりの的に）
resource "aws_security_group_rule" "bad_ssh" {
  type        = "ingress"
  from_port   = 22
  to_port     = 22
  protocol    = "tcp"
  cidr_blocks = ["0.0.0.0/0"] # ← 攻撃面を全世界に晒している
  security_group_id = aws_security_group.app.id
}

# ✅ 正：公開は 443 のみ。管理は SSM 経由でポートを開けない
resource "aws_security_group" "app" {
  name        = "app-minimal-surface"
  description = "Public 443 only; admin via SSM (no inbound SSH)"
  vpc_id      = var.vpc_id

  ingress {
    description = "HTTPS from anywhere (terminate at ALB/WAF)"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  # SSH(22) の ingress は「無い」のが正解。SSM がポートレスで代替する。

  egress {
    description = "Allow outbound (tighten per workload)"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = { Name = "app-minimal-surface" }
}
```

**The point**: attack-surface minimization isn't "be careful" but **expressing it in code (IaC) and guarding it with review and diffs.** This is the core thinking from my designing and operating multi-layer networks on AWS.

---

## 4. Defense ②: detect the scan — type-safe detection logic

Even if prevention (attack-surface reduction) is slipped past, **a scan has a clear behavioral signature**: "in a short time, one source attempts connections to many different ports/hosts." Detect this.

### 4.1 In production, an IDS (Suricata) is the standard

In production, an IDS/IPS like [Suricata](https://suricata.io/) or [Snort](https://www.snort.org/) detects port scans with off-the-shelf rules. On AWS, VPC flow logs + GuardDuty raise `Recon:*` findings. **Riding on managed/off-the-shelf detection first** is the correct answer.

### 4.2 A minimal implementation to understand the mechanism (type-safe)

To understand the principle of detection, write a pure function that judges "scan-likeness" from flow logs (or connection events). This isn't production logic but **visualization of the principle.**

```ts
/** 1件の接続試行（フローログの1行を正規化したもの）。 */
interface ConnectionAttempt {
  readonly srcIp: string;
  readonly dstPort: number;
  readonly timestamp: number; // epoch ms
}

interface ScanVerdict {
  readonly srcIp: string;
  readonly distinctPorts: number;
  readonly isLikelyScan: boolean;
}

/**
 * 「短時間に同一送信元が多数の異なるポートへ接続を試みた」かを判定する純粋関数。
 * 副作用なし＝単体テスト・ゴールデンベクタ固定が容易（テスト容易性）。
 *
 * @param windowMs 観測窓（既定60秒）
 * @param portThreshold この窓で触れた異なるポート数の閾値（既定15）
 */
export function detectPortScan(
  attempts: readonly ConnectionAttempt[],
  now: number,
  windowMs = 60_000,
  portThreshold = 15,
): readonly ScanVerdict[] {
  const since = now - windowMs;
  const portsBySrc = new Map<string, Set<number>>();

  for (const a of attempts) {
    if (a.timestamp < since) continue; // 窓の外は無視
    const ports = portsBySrc.get(a.srcIp) ?? new Set<number>();
    ports.add(a.dstPort);
    portsBySrc.set(a.srcIp, ports);
  }

  return [...portsBySrc.entries()]
    .map(([srcIp, ports]) => ({
      srcIp,
      distinctPorts: ports.size,
      isLikelyScan: ports.size >= portThreshold,
    }))
    .filter((v) => v.isLikelyScan)
    .sort((a, b) => b.distinctPorts - a.distinctPorts); // 重い順に並べる（決定的）
}
```

With this pure function, you can call it from a flow-log batch or a CloudWatch Logs subscription and connect it to a Slack notification or automatic blocking (within a range that doesn't break the allowlist). By **decoupling the detection logic from side effects**, you can fix the behavior in tests and safely exclude false positives (your own legitimate scans, health checks) with an allowlist.

> **Prevention and detection are different, and both are needed**: attack-surface minimization (§3) is the prevention of "don't show it in the first place," and scan detection (§4) is the discovery of "notice when you're seen." Either one alone is insufficient. This is the basic stance of [defense in depth](/blog/network-penetration-testing-methodology-attack-defense-guide).

---

## 5. Conclusion

- **Port scanning is the starting point of all attacks.** The SYN scan (-sS) quits the handshake midway and judges open/closed/filtered. "Stealth" is an illusion, transparent to modern IDS.
- **The most dangerous information leak is the service version** (`-sV`). It connects directly to known CVEs. Hide the version and keep it up to date.
- **Defense ① = attack-surface minimization**: don't expose management ports, IaC the SG with minimal permission, and substitute with SSM/bastion. Move `open` to `filtered`.
- **Defense ② = detection**: capture the scan's behavioral signature (short time, many ports, single source) with an IDS/flow logs. Separate the detection logic into a pure function to make it testable.
- **Taking inventory of your own company with your own `nmap`** is a legitimate, essential skill for the defender. Be more knowledgeable about your company than the attacker.

Next, we cover **[ARP spoofing / man-in-the-middle (MITM)](/blog/arp-spoofing-mitm-attack-detection-defense-guide)**, which intrudes on the route itself at L2.

---

I (Yudai Tomoda) have IaC'd AWS security groups, NACLs, and VPC design with least privilege, and implemented attack-surface management including reconnaissance detection with GuardDuty and flow logs. "I want to take inventory of our public ports," "I'm anxious that the SGs are full of `0.0.0.0/0`," "I want to detect scans/reconnaissance" — I diagnose this visualization and reduction of the attack surface from an attacker's viewpoint and cure it at the root with both configuration (IaC) and detection. Feel free to consult me.
