事实上,在你看这篇译文之前,我必须要解释两点
- 按照这篇译文写出的代码并没有发挥作用
- 你可以通过这篇文章了解到script的框架
假设您想要从identification服务器提取信息以确定侦听TCP端口的进程的所有者。 这不是identd的目的(它是为了查询传出连接的所有者,而不是监听守护进程),但许多identd服务器都允许它。Nmap曾经拥有这种功能(称为ident扫描),但在过渡到新的扫描引擎架构时被删除。 identd使用的协议非常简单,但仍然太复杂,无法处理Nmap的版本检测语言。 首先,连接到标识服务器并发送<port-on-server>,<port-on-client>格式的查询,并以换行符结尾。 然后,服务器应该使用包含服务器端口,客户端端口,响应类型和地址信息的字符串进行响应。 如果出现错误,地址信息将被省略。 更多细节可在RFC 1413中找到,但这种描述对我们的目的来说已经足够了。该协议不能用Nmap的版本检测语言建模,原因有两个。 首先,您需要知道连接的本地端口和远程端口。 版本检测不提供此数据。 第二个更严重的障碍是,您需要两个打开的连接到目标 - 一个连接到识别服务器,一个连接到您希望查询的侦听端口。 NSE很容易克服这两种障碍。
脚本的解剖部分在脚本格式一节中进行了描述。 在本节中,我们将介绍如何使用所描述的结构。
The Head
脚本的head本质上是它的元信息。包含下列字段:description
,categories
,dependencies
,author
,license
以及初始NSEDoc信息,例如用法,参数和输出标签参考编写脚本文档一节。
description
应包含一段或更多段描述脚本的功能。 如果有关脚本结果的任何信息可能会混淆或误导用户,并且您无法通过改进脚本或结果文本来消除此问题,则应将其记录在description
中。 如果有多个段落,第一个在必要时用作简短摘要。 确保第一段可以作为一个独立的摘要。 这个描述很简短,因为它是一个如此简单的脚本:
description = [[
Attempts to find the owner of an open TCP port by querying an auth
(identd - port 113) daemon which must also be open on the target system.
]]
接下来是NSEDoc信息。 此脚本缺少常见的@usage和@args标记,因为它非常简单,但它确实有一个NSEDoc @output标记:
---
--@output
-- 21/tcp open ftp ProFTPD 1.3.1
-- |_ auth-owners: nobody
-- 22/tcp open ssh OpenSSH 4.3p2 Debian 9etch2 (protocol 2.0)
-- |_ auth-owners: root
-- 25/tcp open smtp Postfix smtpd
-- |_ auth-owners: postfix
-- 80/tcp open http Apache httpd 2.0.61 ((Unix) PHP/4.4.7 ...)
-- |_ auth-owners: dhapache
-- 113/tcp open auth?
-- |_ auth-owners: nobody
-- 587/tcp open submission Postfix smtpd
-- |_ auth-owners: postfix
-- 5666/tcp open unknown
-- |_ auth-owners: root
接下来是author
,license
,categories
标签。 此脚本属于safe
,因为我们没有将该服务用于任何不适用的内容。 由于此脚本是默认运行的脚本,因此它也位于default
类别中。 以下是上下文中的变量:
author = "Diman Todorov"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "safe"}
The Rule
rule
是一个Lua方法,决定跳过还是执行脚本动作。 该决定通常基于规则的类型以及传递给它的主机和端口信息。 prerule或postrule将始终评估为true。 在识别脚本的情况下,它比这稍微复杂一些。 要决定是否针对给定端口运行识别脚本,我们需要知道目标机器上是否存在运行auth服务器。 换句话说,只有在当前扫描的TCP端口打开并且TCP端口113也打开的情况下,脚本才能运行。 现在我们将依赖识别服务器在TCP端口113上侦听的事实。不幸的是,NSE只给我们提供关于当前扫描端口的信息。
要知道端口113是否打开,我们使用nmap.get_port_state函数。 如果未扫描auth端口,则get_port_state函数返回nil。 所以我们检查表不是零。 我们还检查两个端口是否处于打开状态。 如果是这种情况,则执行该动作,否则我们跳过该动作。
portrule = function(host, port)
local auth_port = { number=113, protocol="tcp" }
local identd = nmap.get_port_state(host, auth_port)
return identd ~= nil
and identd.state == "open"
and port.protocol == "tcp"
and port.state == "open"
end
The Action
最后我们实现了实际的功能! 该脚本首先连接到我们期望找到标识服务器的端口,然后它将连接到我们想要获取信息的端口。 这样做首先通过调用nmap.new_socket创建两个套接字选项。 接下来我们定义一个错误处理catch函数,如果检测到失败,它会关闭这些套接字。 在这一点上,我们可以安全地使用诸如打开,关闭,发送和接收之类的对象方法来在网络套接字上操作。 在这种情况下,我们称connect为连接。 NSE的异常处理机制用于避免过多的错误处理代码。 我们只需在尝试调用中打包网络调用,如果出现任何问题,我们会调用catch函数。
如果两个连接成功,我们构造一个查询字符串并解析响应。 如果我们收到满意的答复,我们会返回检索到的信息。
action = function(host, port)
local owner = ""
local client_ident = nmap.new_socket()
local client_service = nmap.new_socket()
local catch = function()
client_ident:close()
client_service:close()
end
local try = nmap.new_try(catch)
try(client_ident:connect(host.ip, 113))
try(client_service:connect(host.ip, port.number))
local localip, localport, remoteip, remoteport =
try(client_service:get_info())
local request = port.number .. ", " .. localport .. "\r\n"
try(client_ident:send(request))
owner = try(client_ident:receive_lines(1))
if string.match(owner, "ERROR") then
owner = nil
else
owner = string.match(owner,
"%d+%s*,%s*%d+%s*:%s*USERID%s*:%s*.+%s*:%s*(.+)\r?\n")
end
try(client_ident:close())
try(client_service:close())
return owner
end
请注意,因为我们知道远程端口存储在port.number中,所以我们可以忽略client_service的最后两个返回值:get_info(),如下所示:
local localip, localport = try(client_service:get_info())
在这个例子中,如果服务响应错误,我们会安静地退出。 这是通过将nil分配给将返回的所有者变量来完成的。 NSE脚本通常只在成功时才会返回消息,因此它们不会用无意义的警报泛滥用户。