nmap脚本写作教程

原文:Script Writing Tutorial

事实上,在你看这篇译文之前,我必须要解释两点

  1. 按照这篇译文写出的代码并没有发挥作用
  2. 你可以通过这篇文章了解到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脚本通常只在成功时才会返回消息,因此它们不会用无意义的警报泛滥用户。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容