NSEスクリプトの書き方を学ぶ
Nmapでポートを見つけたあと、そのサービスにリクエストを送り、応答を調べたいことがあります。 その処理をLuaで記述してNmapから実行できるのが、NSE(Nmap Scripting Engine) です。 スキャンで得たホストやポートの情報を受け取り、調査結果をNmapの出力にまとめられます。
今回は、まず小さなスクリプトでNSEの書き方を確認します。
NSEスクリプトの基本構成
NSEスクリプトは、拡張子を.nseにしたLuaのファイルです。
主に、説明などのメタデータ、実行条件を決めるルール、処理本体のaction関数で構成します。
ポートを対象とする場合は、portruleが真を返したホストとポートについて、Nmapがaction(host, port)を呼び出します。
ルールの種類や各フィールドの定義は、公式ドキュメントのScript Formatで確認できます。
NSEのカテゴリーは、公式ドキュメントでは次の14種類です。1つのスクリプトに複数のカテゴリーを指定できます。公式のカテゴリー一覧
ポートスキャンをしてHTTPが存在している場合、サーバーのヘッダー情報を取得するNSEを書いてみます。
次のコードをhttp-server-info.nseという名前で保存します。
local http = require "http"
local shortport = require "shortport"
description = [[
Retrieves the HTTP status and Server header from /.
]]
author = "0x6d61"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
-- HTTPと推定されるポートで実行する
portrule = shortport.http
action = function(host, port)
local response = http.get(host, port, "/")
-- HTTP応答を取得できなかった場合は結果を表示しない
if not response or not response.status then
return nil
end
return {
status = response.status,
server = response.header["server"] or "(not provided)"
}
end
requireで読み込んでいるshortportは、ポート番号やサービス名から実行条件を作るためのライブラリです。
この例では開いているTCPポートのうち、HTTPっぽいプロトコルを対象としています。actionではhttp.getでリクエストを送り
HTTPレスポンスが取得できなかった場合はnilで取得できた場合はステータスコードとヘッダー情報を返すようにしています。
ちなみにluaでは[[...]]が複数行文字列リテラルだそうです。初めて知りました。
licenseはnmapと同じ!みたいなやつが使えるっぽいです。少し雑で面白い。
実際にHTTP サーバーを立ててヘッダーとステータスコードが取得できるか確認してみます。
python3 -m http.server 8000 --bind 127.0.0.1
nmapの実行結果
nmap -p 8000 --script ./http-server-info.nse 127.0.0.1
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-09-10 12:07 JST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00040s latency).
PORT STATE SERVICE
8000/tcp open http-alt
| http-server-info:
| status: 200
|_ server: SimpleHTTP/0.6 Python/3.12.3
Nmap done: 1 IP address (1 host up) scanned in 0.14 second
基本的なNSEを書いて機能を確認することができました。 多分あんまり使う機会ないと思うけど…