Nmap NSE 脚本开发实战指南(附完整示例与问答)
目录导读
NSE脚本基础架构
NSE(Nmap Scripting Engine)是Nmap内置的脚本引擎,允许用户通过Lua语言编写自定义网络探测、漏洞检测、服务识别等功能,一个标准的NSE脚本包含三个核心部分:

- 脚本元数据:描述脚本名称、类别、作者、依赖版本等。
- 主执行逻辑:即
action函数,脚本被调用时自动执行。 - 辅助函数库:调用Nmap内置的
nmap、ssl、http等库完成网络交互。
最小化脚本示例(保存为hello.nse):
description = [[一个简单的探测脚本,验证NSE引擎是否工作]]
author = "Your Name"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"safe", "discovery"}
prerule = function() end
hostrule = function(host) return true end
action = function(host, port)
return "Hello from NSE! Host: " .. host.ip
end
- 关键点:
hostrule定义哪些主机或端口被处理,action返回结果字符串。
Lua语言核心速览
NSE脚本完全基于Lua 5.3,你需要掌握以下常用语法:
| 语法 | 示例 | 说明 |
|---|---|---|
| 变量声明 | local port_num = 80 |
强制使用local避免全局污染 |
| 表(Table) | local t = {key="value", [1]="array"} |
Lua唯一数据结构,兼作数组和字典 |
| 字符串操作 | string.match(data, "HTTP/(%d%.%d)") |
配合模式匹配提取信息 |
| 函数定义 | local function check_port(host, port) |
支持匿名函数和闭包 |
| 错误处理 | local ok, err = pcall(f) |
使用pcall捕获异常 |
高级技巧:利用NSE库nmap.new_socket()创建TCP/UDP连接,使用stdnse模块进行格式化输出。
脚本分类与模板解析
NSE脚本分为六类,选择类别需考虑脚本行为:
- safe:安全探测,无副作用(如服务版本检测)
- intrusive:可能触发告警(如暴力破解)
- discovery:信息收集(如子域名扫描)
- vuln:漏洞探测(如CVE检测)
- exploit:漏洞利用(谨慎使用)
- auth:认证绕过测试
标准脚本模板结构:
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
portrule = shortport.port_or_service(443, "https")
action = function(host, port)
local socket = nmap.new_socket()
socket:set_timeout(5000)
local status, response = socket:connect(host.ip, port.number)
-- 发送探测数据并解析
socket:send("GET / HTTP/1.1\r\nHost: " .. host.ip .. "\r\n\r\n")
local data = socket:receive_lines(10)
socket:close()
return stdnse.format_output(true, "Received %d bytes", #data)
end
实战开发:端口扫描与协议探测脚本
场景需求:识别目标主机开放的HTTP服务,并提取Server版本。
步骤分解:
-
定义规则:只扫描80/8080端口
portrule = function(host, port) return port.number == 80 or port.number == 8080 end -
建立连接:使用
socket:connect三次握手 -
发送HTTP请求:
local request = "GET / HTTP/1.0\r\nHost: " .. host.ip .. "\r\n\r\n" socket:send(request)
-
解析响应:使用正则匹配
Server:字段local server_header = string.match(data, "Server: ([^\r\n]+)") if server_header then return "HTTP Server version: " .. server_header end
完整脚本(保存为http-server-detect.nse):
description = [[简易HTTP服务器版本探测]]
author = "Dev@nse"
license = "Standard NSE License"
categories = {"safe", "discovery"}
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
portrule = shortport.port_or_service({80,8080}, "http")
action = function(host, port)
local socket = nmap.new_socket()
socket:set_timeout(5000)
local status, err = socket:connect(host, port)
if not status then
return stdnse.format_output(false, "Connection failed: " .. err)
end
local request = "GET / HTTP/1.0\r\nHost: " .. host.ip .. "\r\n\r\n"
socket:send(request)
local data, part = socket:receive_bytes(2048)
socket:close()
if not data then
return stdnse.format_output(false, "No response")
end
local server_header = string.match(data, "Server: ([^\r\n]+)")
if server_header then
return stdnse.format_output(true, "Server: %s", server_header)
else
return stdnse.format_output(true, "HTTP service detected, but no Server header found")
end
end
参数传递与结果输出优化
1 脚本参数
通过--script-args传递参数,在脚本中读取:
local config_timeout = nmap.registry.args.timeout or 3000
使用示例:nmap -sV --script=http-server-detect --script-args timeout=5000
2 多样化输出格式
- 简单字符串:直接返回字符串(如
"Result: OK") - 结构化输出:使用
stdnse.format_output(true, "key", "value") - 表格输出:返回表对象:
return { name = "HTTP Detection", state = "open", product = server_header or "unknown" }
3 多主机扫描结果合并
利用nmap.registry存储全局数据,分阶段输出:
action = function(host, port)
local result = custom_check(host)
nmap.registry.results = nmap.registry.results or {}
table.insert(nmap.registry.results, {ip=host.ip, result=result})
end
postrule = function()
-- 输出汇总结果
for _, entry in ipairs(nmap.registry.results) do ... end
end
安全性与错误处理
1 超时与资源控制
- 设置
socket:set_timeout(ms)防止阻塞 - 使用
nmap.set_limit("max_sockets", 20)限制并发连接数
2 探测数据过滤
避免注入攻击:
local sanitized_input = string.gsub(user_input, "[\r\n]", "")
3 异常捕获
local ok, err = pcall(function()
socket:send(payload)
end)
if not ok then
stdnse.debug(1, "Send error: %s", err)
end
4 避免被反扫描
- 添加随机延迟:
nmap.sleep(math.random(100,500)) - 随机化探测顺序(使用
shuffle算法)
常见问答Q&A
Q1: 脚本运行后没有输出,可能原因是什么?
A: 检查hostrule或portrule是否返回true;确认目标端口是否开放;使用nmap -d2 --script yourscript开启调试模式查看错误日志。
Q2: 如何复用其他NSE脚本的函数?
A: 脚本位于/usr/share/nmap/scripts/,使用require "script_name"导入(去除.nse后缀),例如local http = require "http"。
Q3: 脚本能在Windows系统运行吗?
A: 完全支持,但需确保Nmap安装路径包含Lua解释器(默认携带),跨平台兼容性良好。
Q4: 脚本性能优化建议?
A: 使用nmap.new_socket()的异步模式connect配合nmap.set_payload();减少不必要的字符串拼接;利用stdnse.nmap_output函数替代直接print。
Q5: 如何发布自己的NSE脚本?
A: 提交到官方GitHub仓库:https://github.com/nmap/nmap 或通过社区论坛分享,确保脚本通过nmap --script-updatedb测试。
NSE脚本开发的核心在于理解Lua网络编程模型与Nmap提供的丰富库函数,从简单的端口探测到复杂的漏洞利用,掌握上述模板与技巧后,你已能够编写出功能完善、安全健壮的脚本,建议将本文中的http-server-detect.nse保存并测试,逐步添加更多功能(如SSL检测、重定向跟踪),持续参考官方文档(https://nmap.org/book/nse.html)和现有脚本源码,是提升水平的最快路径。
标签: NSE脚本开发