nmap脚本

原文地址:http://resources.infosecinstitute.com/nmap-scripting-example/
http://resources.infosecinstitute.com/nmap-scripting-engine-categories/

basic
dependencies:作为iyige包含脚本名字的数组来列举依赖关系,这些脚本需要在我们的脚本执行之前来执行。依赖关系仅仅是通过–script选项来强制脚本的执行顺序。
rule:用来确定脚本是否运行。每一个rule必须是返回true或false,如果rule是真,那么action将会被调用。一个脚本必须含有prerule, hostrule, portrule和postrule中的一个来确定action是否运行。prerule在主机扫描之前运行,hostrule和portrule在每一个扫描完成之后运行,而postrule在所有的扫描完成之后运行。postrule被用于做报告。例如
---
-- Script is executed for any TCP port.
portrule = function( host, port )
    return port.protocol == "tcp"
end


每一个脚本包含三个本地变量。SCRIPT_PATH指明脚本的路径,SCRIPT_NAME 指明脚本的名字,SCRIPT_TYPE 指明脚本的类型。

Nmap API
首先演示一个banner.nse的例子:
action = function( host, port )
    local out = grab_banner(host, port)
    return output( out )
end

我们可以看到action函数接收两个参数,host和port。host传递的是我们要扫描的主机,而port参数则是要扫描的端口。然后我们可以在nse脚本中使用host和post object来请求我们需要的信息。
host object包含下列属性:
host.iphost.ip: 目标主机IP
host.name: 目标主机的反向DNS记录
host.targetname: nmap命令中声明的host名
host.directly_connected: 指明被扫描的主机和扫描的主机是否在同一内网
host.mac_addr: 目标主机的MAC地址(host.directly_connected为真时可用)
host.mac_addr_src: 我们自己的MAC地址
host.interface: 我们的网卡接口
host.interface_mtu: 我们网卡的最大传输单元
host.bin_ip: 目标的IP
host.bin_ip_src: 我们的IP
host.times: 包含关于目标主机的RTT, SRTT, RTTVAR 和timeout的信息。
port object包含如下成员:
port.number: 目标端口
port.protocol: 目标协议
port.service: 目标主机运行的协议
port.version: 目标主机上运行的服务的额外信息
port.state: 目标端口的状态: open, open|filtered, filtered, closed。
当写NSE脚本的时候,我们直接访问nsock,它是一个nmap socket lib。我们可以使用nsock lib来实现并行非阻塞操作,它提供有力的机制来运行同时运行多个脚本。当使用nsock lib的时候,我们可以实现下列情况:我们可以创建一个socket然后连接到一个指定的ip和端口。发送/接收数据,然后关闭端口。我们可以调用new_socket函数,然后一个socket object,含有下列方法:
send
receive
close
set_timeout
receive_bytes
receive_lines
receive_buf
在nsock中,我们可以创建由libpcap处理的原始套接字。
当脚本执行完成后,我们需要将结果打印在显示器上,因此我们需要组织一个包含特殊keys的表,被用于自动化格式,然后将数据打印在显示器上。当我们运行时,LUA自动的将字符串转换成通常我们可以看见的输出。每一个嵌套的表是一个新的层级。
还有一个叫registry的特殊表,它存有所有脚本都使用的变量。这是一个全局的存储位置,可以用来让所有脚本设置/获得变量的值,提供我们在不同脚本之间共享变量的能力。registry中的值只存储当前运行的nmap中的值。当写一个key到全局registry中,我们需要确保这个key是唯一的从而不会被其他脚本覆盖该值。当某一个脚本依赖其他脚本的输出时,我们必须在开始的时候声明依赖关系,来确保脚本的正确执行。
2. An Example
本本例子我们写一个脚本来parse一个服务器能处理的所有http方法。我们可以通过执行如下的请求,然后从服务器获得的响应来知道结果:
OPTIONS / HTTP/1.0
我们需要创建一个socket,将会连接到目标机器,80端口,然后发送由CRLF结尾的 “OPTIONS / HTTP/1.0″命令。服务器将会相应他支持的HTTP方法。例子如下:
# telnet www.gentoo.org 80
Trying 89.16.167.134...
Connected to www.gentoo.org.
Escape character is '^]'.
OPTIONS / HTTP/1.0
 
HTTP/1.1 200 OK
Date: Thu, 27 Sep 2012 21:11:04 GMT
Server: Apache
Allow: GET,HEAD,POST,OPTIONS
Content-Length: 0
Connection: close
Content-Type: text/html; charset=utf-8
 
Connection closed by foreign host.

我们使用telnet来连接到www.gentoo.org的80端口,然后执行“OPTIONS / HTTP/1.0″命令。请求被接受,然后响应支持的http方法GET, HEAD, POST and OPTIONS.
现在我们现在开始写NSE脚本,我们必须首先定义description, categories, dependencies, license和author。如下
description = [[
Attempts to find the HTTP methods available on the target HTTP server.
]]

author = "Dejan Lukan"
license = "GPL 2.0"
categories = {"default"}
脚本的rule来决定脚本是否将被执行。如果我们没有接收正确的host和port信息,我们可以退出脚本什么也不做。在rule函数中,我们坚持host和port来确定该指定端口是否开放。一个脚本必须包含下列中的一条来决定脚本是否运行:prerule, hostrule, portrule或者postrule。本例中,我们使用psotrule来决定端口是否开放,如下:
-- returns true if port is likely to be HTTP, false otherwise
portrule = shortport.http
当nmap认为端口使用http协议时,将会返回true。然后我们需要指定action,用来指定脚本的功能。如下:
local http = require "http"
local nmap = require "nmap"
local stdnse = require "stdnse"
local shortport = require "shortport"
local table = require "table"

description = [[
Attempts to find the HTTP methods available on the target HTTP server.
]]

author = "Dejan Lukan"
license = "GPL 2.0"
categories = {"default"}

-- returns true if port is likely to be HTTP, false otherwise
portrule = shortport.http

action = function(host, port)
  local out = {}

  -- make the "OPTIONS" request
  response = http.generic_request(host, port, "OPTIONS", "/")

  -- form the output
  table.insert(out, string.format("Request         : OPTIONS / HTTP/1.0"))
  table.insert(out, string.format("Host            : %s (%s)", host.ip, host.name))
  table.insert(out, string.format("Port            : %s", port.number))
  table.insert(out, string.format("Allowed Methods : %s", response.header['allow']))

  return stdnse.format_output(true, out)
end
当指定端口使用http协议时,脚本首先发送“OPTIONS” http方法到server上,然后它将会通过将结果插入table中来构建输出。然后使用stdnse.format_output来输出结果。
脚本输出如下:
nmap --script=/home/eleanor/testing/http_options.nse www.gentoo.org -p 80
 
Starting Nmap 6.01 ( http://nmap.org ) at 2012-10-01 22:05 CEST
Nmap scan report for www.gentoo.org (89.16.167.134)
Host is up (0.051s latency).
PORT   STATE SERVICE
80/tcp open  http
| http_options:
|   Request         : OPTIONS / HTTP/1.0
|   Host            : 89.16.167.134 (www.gentoo.org)
|   Port            : 80
|_  Allowed Methods : GET,HEAD
 
Nmap done: 1 IP address (1 host up) scanned in 0.31 seconds

我们可以看到nmap脚本只返回GET和HEAD方法,而telnet显示支持GET, HEAD, POST和OPTIONS。来检查一下原因,我们使用 –script-trace-dd选项:
NSE: TCP 192.168.1.2:57257 > 89.16.167.134:80 | CONNECT
NSE: TCP 192.168.1.2:57257 > 89.16.167.134:80 | 00000000: 4f 50 54 49 4f 4e 53 20 2f 20 48 54 54 50 2f 31 OPTIONS / HTTP/1
00000010: 2e 31 0d 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 .1  Connection:
00000020: 63 6c 6f 73 65 0d 0a 55 73 65 72 2d 41 67 65 6e close  User-Agen
00000030: 74 3a 20 4d 6f 7a 69 6c 6c 61 2f 35 2e 30 20 28 t: Mozilla/5.0 (
00000040: 63 6f 6d 70 61 74 69 62 6c 65 3b 20 4e 6d 61 70 compatible; Nmap
00000050: 20 53 63 72 69 70 74 69 6e 67 20 45 6e 67 69 6e  Scripting Engin
00000060: 65 3b 20 68 74 74 70 3a 2f 2f 6e 6d 61 70 2e 6f e; http://nmap.o
00000070: 72 67 2f 62 6f 6f 6b 2f 6e 73 65 2e 68 74 6d 6c rg/book/nse.html
00000080: 29 0d 0a 48 6f 73 74 3a 20 77 77 77 2e 67 65 6e )  Host: www.gen
00000090: 74 6f 6f 2e 6f 72 67 0d 0a 0d 0a                too.org


我们可以看到nmap使用 “OPTIONS / HTTP/1.1″命令而不是 “OPTIONS / HTTP/1.0″命令。看起来http.generic_request不支持http/1.0版本。但是不要担心,我们可以使用raw socket来发送请求。如下:
local http = require "http"
local nmap = require "nmap"
local stdnse = require "stdnse"
local shortport = require "shortport"
local table = require "table"

description = [[
Attempts to find the HTTP methods available on the target HTTP server.
]]

author = "Dejan Lukan"
license = "GPL 2.0"
categories = {"default"}

-- returns true if port is likely to be HTTP, false otherwise
portrule = shortport.http

action = function(host, port)
  local out = {}

  -- make the "OPTIONS / HTTP/1.0" request
  local socket = nmap.new_socket()
  socket:connect(host, port)
  socket:send("OPTIONS / HTTP/1.0rnrn")
  s,response = socket:receive()
  socket:close()

  -- form the output
  table.insert(out, string.format("Request         : OPTIONS / HTTP/1.0"))
  table.insert(out, string.format("Host            : %s (%s)", host.ip, host.name))
  table.insert(out, string.format("Port            : %s", port.number))
  table.insert(out, string.match(response, "Allow: [^r]*rn"));

  return stdnse.format_output(true, out)
end

执行脚本,显示结果如下:
nmap --script=/home/user/testing/http_options.nse www.gentoo.org -p 80

Starting Nmap 6.01 ( http://nmap.org ) at 2012-10-01 22:12 CEST
Nmap scan report for www.gentoo.org (89.16.167.134)
Host is up (0.052s latency).
PORT   STATE SERVICE
80/tcp open  http
| http_options2:
|   Request         : OPTIONS / HTTP/1.0
|   Host            : 89.16.167.134 (www.gentoo.org)
|   Port            : 80
|_  Allow: GET,HEAD,POST,OPTIONS

Nmap done: 1 IP address (1 host up) scanned in 0.30 seconds

猜你喜欢

转载自j4s0nh4ck.iteye.com/blog/2148113