Nmap NSE 库分析 >>> http

时间:2022-07-23
本文章向大家介绍Nmap NSE 库分析 >>> http,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Nmap 使用lua 语言作为脚本进行扩展功能,并且写了一些集成功能的 lib 文件,重新定义了很多函数 具体官方给出的地址如见 :https://nmap.org/nsedoc/

可以看到 Nmap 文件的结构,其中nselib 文件夹就存放着所有的预先设计好的库文件

可以看到一共有141 个库文件,接下来的工作就是对其中比较常用的进行分析

Nmap 其实给了一份库文件函数参考: https://nmap.org/nsedoc/lib/http.html (这个是http库的地址)

nse_main.lua

调用NSE脚本时会首先调用 nse_main.lua 由这个文件进行统一的调控

这个脚本的作用主要为:

  • 检查系统环境
  • 声明一些变量
  • 定义脚本总的执行规则
  • 加载NSE核心库(nselib)
  • 定义一些最常用的方法
  • 定义线程相关的类和方法
  • 定义输出格式化
  • 定义数据存储并且与C语言数据交互方式
  • 定义运行等级制度

http

官方文档 https://nmap.org/nsedoc/lib/http.html

0x00 基本介绍

http 库可以完成基本的http协议的数据包构造以及发送,基本的get、post、head方法都可以实现 ,如果想要实现更多的控制可以使用 generic_request 方法。get_url 这个方法可以用来实现 url格式化,获取一个完整的字符串

https 可以使用 comm.tryssl 去判断使用ssl的版本等

这些函数的返回值是一个表,表中包括以下内容:

  • status-line - 状态行,例如:HTTP/1.1 200 OK
  • status - 状态码
  • version - http版本,比如 1.1
  • header - 响应头,是一个数组,key都是小写的
  • rawheader - 响应头原始代码,这个是保存带有大小写原始响应头
  • cookies - 一个数组,包含着Cookie中的各个字段
  • rawbody - 原始body
  • body - 处理后的body,处理完Content-Encoding头(如果有的话)后的整个正文。调整内容编码和内容长度头以与处理的正文保持一致。
  • incomplete - 发生错误时已经接收到的那部分内容
  • truncated - 表示响应包被截断了,可能是太长了
  • decoded - 已处理的命名内容编码列表(如“identity”或“gzip”)
  • undecoded - 表示不支持接码,如果为 nil 表示正常接码了
  • location - 一个饱含重定向后的地址的数组

很多函数支持一个可选择的参数option,这个参数是一个表,可以定制化http包的内容,比如设置header,设置timeout等,具体可用的key如下:

  • timeout - socket超时时间
  • header - 一个包含其他header的表. For example, options['header']['Content-Type'] = 'text/xml'
  • content - 消息的内容。这可以是一个字符串,它将直接添加为消息的主体,也可以是一个表,它将添加每个key=value对(就像一个普通的POST请求)。(将自动添加相应的内容长度标题。设置header['Content-Length']以覆盖它)。
  • cookies - cookie的值,可以是字符串也可以是一个表,如果是一个表,会有 name, value 字段.
  • auth - 一个用来认证的表,包含username,password 两个key, 这种适用于 HTTP Basic 认证,如果是HTTP Digest 认证需要有一个 digest 的key,并且值为 true;如果采用 ntlm 认证,那么需要有一个 ntlm 的key,值为true。
  • bypass_cache - 不在本地缓存中进行查找相关内容
  • no_cache - 不在本地进行缓存
  • no_cache_body - 不在本地缓存http body
  • max_body_size - 限制接收body的字节大小,可以覆盖 http.max-body-size
  • truncated_ok - 不把过大的body当做错误,覆盖参数 http.truncated-ok
  • any_af - 允许连接到任意的地址簇,ipv4和ipv6都可以。默认这些方法只会使用 nmap.address_family 解析到的地址簇。
  • redirect_ok - 覆盖用于验证是否遵循HTTP重定向的默认redirect_ok的闭包。如果不遵循HTTP重定向,则为False。或者,可以传递一个数字来更改要遵循的重定向的数量。以下示例显示了如何编写跟随5个连续重定向的自定义闭包,而不对默认的redirect_ok进行安全检查: redirect_ok = function(host,port) local c = 5 return function(url) if ( c==0 ) then return false end c = c - 1 return true end end

一个脚本如果并行发送很多http包,那么管道功能就很有用了。可以使用pipeline_add 将请求添加进队列,使用 pipeline_go 来发起队列中的请求,最终返回一个数组,顺序与添加时候一样

-- Start by defining the 'all' variable as nil
 local all = nil

 -- Add two GET requests and one HEAD to the queue but these requests are
 -- not performed yet. The second parameter represents the "options" table
 -- (which we don't need in this example).
 all = http.pipeline_add('/book',    nil, all)
 all = http.pipeline_add('/test',    nil, all)
 all = http.pipeline_add('/monkeys', nil, all, 'HEAD')

 -- Perform all three requests as parallel as Nmap is able to
 local results = http.pipeline_go('nmap.org', 80, all)

HTTP库提供的另一个接口可帮助脚本确定页面是否存在。identify_404 函数将尝试服务器上的几个URL,以确定服务器的404页面的外观。它将尝试识别可能不会返回实际状态代码404的自定义404页面。如果成功,则可以使用 page_exists 函数确定页面是否存在

0x01 参数

  • http.useragent useragent的值,默认是 "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)" ,空字符串就是不发user-agent
  • http.host 主要针对一些不能没有host字段的资产,默认会从stdnse.get_hostname() 获取
  • http.max-body-size 限制body的大小,最大2m
  • http.pipeline 如果设置,则表示将通过一个连接进行发送的HTTP请求数。可以将其设置为较低以简化调试,也可以将其设置为较高以测试服务器的反应(忽略其选择的最大值)。
  • http.max-cache-size
  • 最大的缓存大小,单位 bytes
  • http.max-pipeline
  • 在一个连接套中并发的连接数
  • http.truncated-ok
  • 不把过大的body做错误处理

0x02 方法

探测服务器是否支持 head 方法

在 http.lua 文件中有近3000 行代码,除了以上的这些方法,还有一些帮助实现上面对外发布的功能的函数,比如用来处理表与表之间复制粘贴、处理空格,获取token等等一系列

http 包对于渗透测试者来说可能是最重要的一个数据包,其中的额外方法在我们编写脚本的时候也会用到,所以我把剩下的方法按照出现的顺序也都列出来,挑选重要的部分进行解释。

方法名

描述

tcopy (t)

递归复制表。仅当值是表时递归,其他值则通过赋值复制。最终返回复制后的表

table_augment(to, from)

将一个表中数据粘贴并覆盖进另一个表

get_host_field(host, port)

获取host header字段,可能是IP,或者是hostname等

skip_space(s, offset)

根据指定偏移量跳过空格。返回空格后的第一个索引以及跳过的空格

get_token(s, offset)

根据指定偏移量查找token(其实就是一个字符串段,不是普遍意义的token),返回token后的一个索引以及找到的token,没有匹配到则返回nil

get_quoted_string(s, offset, crlf)

获取指定偏移量的双引号中的内容,返回匹配内容和匹配内容后的第一个索引

skip_lws(s, pos)

跳过 t、r、n后,返回第一个索引

validate_options(options)

函数用来检查http的options表的各个字段是否填写规范,如果都规范则返回true

recv_line(s, partial)

接收一行数据

line_is_empty(line)

判断一行是否为空行

recv_header(s, partial)

接收数据直到遇到一个空白行,可以看为http 的 header 信息

recv_all(s, partial)

一直接收数据,直到连接被关闭

recv_length(s, length, partial)

接收 n 个bytes 数据

recv_chunked(s, partial)

接收直到 chunked 的结尾块,返回拼接后的代码

recv_body(s, response, method, partial)

接收http的响应body体,分为几种情况,最后会返回接受到的内容

parse_status_line(status_line, response)

解析响应包状态行,并且将值赋给 response.version ,response.status

parse_header(header, response)

解析响应头,赋值给 response.header, response.rawheader

next_response(s, method, partial)

读取一个响应包,并在解析后返回

getPipelineMax(response)

尝试根据“ Keep-Alive:timeout = xx,max = yy”响应标头提取在保持活动连接上应发出的最大请求数并将这个数字返回出来

buildCookies(cookies, path)

就是将解析过后的cookie进行拼接,返回一个字符串

check_size (cache)

检查最大缓存数值是否会有各处的冲突

lookup_cache (method, host, port, path, options)

从缓存中查找数据,如果找到,返回数据组成的表,找不到返回 nil以及连接状态

response_is_cacheable(response)

判断响应包是否可以缓存

insert_cache (state, response)

将提供的response 加入缓存

request_method_needs_content_length(method)

判断是否请求方法一定需要 content-length 字段

http_error(status_line, fragment)

http发生错误时可以调用这个函数返回错误状态以及接收到的片段

build_request(host, port, method, path, options)

组合一个http请求,并将其作为字符串返回

request(host, port, data, options)

用于发送一个确切的请求包,不要拼接的那种,之后返回next_response() 解析后的响应包

get_redirect_ok(host, port, options)

返回一个正确处理HTTP重定向函数

pGet( host, port, path, options, ignored, allReqs )

被遗弃了

pHead( host, port, path, options, ignored, allReqs )

被遗弃了

addPipeline(host, port, path, options, ignored, allReqs, method)

被遗弃了

pipeline(host, port, allReqs)

被遗弃了

skip_space(s, pos)

与上面的同名函数相同,不知道开发者为什么会定义相同函数

read_token(s, pos)

与get_token相同功能

read_quoted_string(s, pos)

与 get_quoted_string 相同

read_token_or_quoted_string(s, pos)

read_token_or_quoted_string 上面两个函数结合一起了

get_attr (html, name)

根据html代码和属性名,获取属性的值

read_auth_challenge(s, pos)

在Basic Authentication 认证的http header 中读取挑战码

cache_404_response(host, port, response)

将 response 对应的端口缓存到 host.registry.http_404[portnum] 中

0x03 方法调用实战

说来惭愧,仅仅翻译以上这些函数的作用就用了我一周的时间,但是我还是没有完全理解这些函数。

Nmap 官方为处理 http 包定义了上面一系列私有函数,每个函数有特定格式的参数,之后函数进行处理,之后再返回处理后的一个特定格式的返回值。所以在翻译或过程如果不去打印这些返回值的话,很难去真正理解这些函数。

下面我们就来将这些对外提供功能的函数进行调用输出,我们按照实际发生http数据交换的顺序来。

0x030 准备工作

调试 Nmap NSE源码与其他程序不太一样,需要我们去自定义脚本引用http.lua 库,并调用其中的功能,之后使用 nmap --script=xxx 来进行触发

下面我们一步一步将一个基础的代码写好

  • 首先是倒入相关的包
local http = require "http"
local nmap = require "nmap"
local shortport = require "shortport"
local strbuf = require "strbuf"
local table = require "table"
  • description 字段
description = [[
This is a test for http.lua's functions
]]
  • 输出类型注释。这个字段就是来表示这个脚本输出是什么样的,通过注释来体现,这个部分不太适合这次测试,就不书写了
  • 作者
author = "test94"
  • license 信息
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
  • 所属类别目录
categories = {"default"}
  • 脚本运行前规则
prerule = function()
    print("functest running")
end
  • 脚本检测完一个端口后规则
portrule = shortport.http
  • action 规则
action = function(host, port)
    local output = stdnse.output_table()
    -- some codes
    -- result = function return
    output.result = result
end

这样一个基本的脚本就完成了,上个全家福

首先测试一些发送请求包的函数

0x001 get

get 函数一共四个参数:host, port, path, options

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function test "
    local result = http.get(host, port, "/", options )
    output.restype = type(result)
    output.result = result
    return output
end

使用 wireshark 进行抓包测试

可以看到我们通过 options 参数改变了发包的 User-Agent ,nmap这边输出的 get参数返回值的类型,以及返回值如下:

可以看到,我们定义的 prerule 在脚本执行前执行了,get函数返回值是一个表,从内容来看就是上面所说的标准响应表。这样可以方便我们至今通过表的key 来获取相应的值

0x002 post

post 函数有6个参数:host, port, path, options, ignored, postdata

其中 ignored 这个参数是表示忽略向后兼容

postdata 是被post传输的数据, 这个参数支持字符串也可以是一个表,之后会被加密后使用 application/x-www-form-encoded 传输

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local result = http.post(host, port, "/system/login.aspx", options,true,"__VIEWSTATE=/wEPDwULLTE5MjI3NzkxNzhkZHK9sGpoX3RVtc+0YqzClfMjawUumJw8RY5FnscWa7bX&__VIEWSTATEGENERATOR=75191122&__EVENTVALIDATION=/wEdAAJ6olSjOPTwBl7ZcvEIibuSshCT4q90vziZH2FxDPZECOC2310GU2/iSO5H3shSHSnbo4hUCMLPsPpUfW17N+Fi&LOGINCAT=Y&userid=admin&userpwd=password&logintype=Y" )
    output.restype = type(result)
    output.result = result
    return output
end

这里使用 nc 来模拟服务器 ,nc接收到数据包如下:

我们的脚本接收到返回值为

可以看到post的返回值也是一个标准响应表

0x003 head

head 函数一共有四个参数:host, port, path, options

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local result = http.head(host, port, "/", options )
    output.restype = type(result)
    output.result = result
    return output
end

head 函数的返回值如下:

head 函数也是一样的,返回一个标准响应表

0x004 put

put 函数有5个参数:host, port, path, options, putdata

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local result = http.put(host, port, "/shell.php", options, "<?php eval($_GET['cmd'];) ?>")
    output.restype = type(result)
    output.result = result
    return output
end

使用nc 进行模拟,put 函数发出的数据包如下:

函数的返回值为

可以看到,put 函数的返回值也是一个标准响应表

0x005 generic_request

所有的发送http包的函数都是调用这个函数进行发包的

generic_request 函数有5个参数,host, port, method, path, options

大家一定有和我一样的疑问,post,put方法的数据值在这个函数的参数中并没有体现,我们查找 post 函数调用 generic_request 函数的片段看一下

可以看到,post的提交数据的地方直接放在 options 字段中进行提交了,在 mod_options 表中 content 字段中保存提交。

那么 generic_request 是如何处理的呢?

可以看到,交给了 build_request 函数进行处理了,我们继续跟进

在 build_request 函数的注释中可以看到,options 参数中一般有四个参数:header, content, cookies, auth。其中 content 字段中就包含我们的数据段,我们跟进一下如何处理的

可以看到,在这一部分进行了拼接组合,最终按照下面返回

到这里以后就解开了,这个 generic_request 的 options 参数与之前的get, post, put, head 的 options 是不一样的,这里可以完成定制头,定制内容,定制cookie,定制认证方案。


这个函数以后单拿出一个小节来讲解。

0x006 identify_404

这个函数只有两个默认参数: host, port

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    -- options["header"]["User-Agent"] = "function post test "
    local result1,result2 = http.identify_404(host, port)
    output.restype1 = type(result1)
    output.restype2 = type(result2)
    output.result1 = result1
    output.result2 = result2
    return output
end

identify_404 返回值为两个三个,result1 是 true / false,表示是否可以分辨404页面;result2 表示已经404 页面返回的状态码;第三个返回值是我从其他代码里发现的,如果不存在页面返回值为 200 ,那么这个返回值为这个页面,如果是其他状态码,那这个参数为 nil

发出的数据包如下:

接受到的结果为

可以看到接收到的第二个参数为 nil ,这是因为nc模拟的原因,我们对百度进行检测

百度的返回值就比较完整了,可以看到两个参数的值和类型

0x007 can_use_head

这个函数是用来检测是否可以使用head方法的,有四个参数:host, port, result_404, path

其中 result_404 是由 identify_404 检测的,如果404 页面返回200状态码,那么就不使用head 方法,这个参数是数值型

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    -- options["header"]["User-Agent"] = "function post test "
    local result_status,result_404 = http.identify_404(host, port)
    local use_head, head_data = http.can_use_head(host, port, result_404, "/")
    output.restype_use_head = type(use_head)
    output.restype_head_data = type(head_data)
    output.result_user_head = use_head
    output.result_head_data = head_data
    return output
end



结果如下:

可以看出,返回值有两个,第一个是 boolean型,表示是否可以使用head,第二个是table 类型,其值为一个标准响应表。

0x008 clean_404

这个函数是配合 identify_404 的,用来将数据包中的一些影响比较的数据去掉,其中包括:时间、日期、路径。然后将剩下的内容返回。

参数为标准响应表的body字段的值或者md5后的字符串

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local result_table = http.get(host, port, "/", options)
    local cleaned_body = http.clean_404(result_table.body)
    output.restype = type(cleaned_body)
    output.result = cleaned_body
    output.old_result = result_table.body
    return output
end

上面代码我是将去处相关信息后的结果称为 result , 未去除的称为 old_result .

可以看到去除后相关信息后的结果非常短,原本的body字段非常长。开始我怀疑是作者写错了,可能把哪条正则给写大了,之后我就将每条正则都注释掉,之后再查看结果,最后发现不是正则的原因,是下面这段

如果我们的电脑环境中支持ssl ,那么直接返回 openssl.md5() 之后的值。

0x009 get_url

get_url 函数有两个参数:u, options

u 是 url , options 就是之前get等方法的options参数了,其中包含 header ,timeout等

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local result = http.get_url("http://183.60.107.44:8080/", options)
    output.restype = type(result)
    output.result = result
    return output
end

我们还是对这个目标进行测试

可以看到返回值也是一个标准响应表,其解析url 使用的是url库的parse方法。

0x0010 page_exists

page_exists 函数有5个参数:data, result_404, known_404, page, displayall

  • data 是一个标准响应表
  • result_404 是identify_404 函数第二个返回值,为不存在页面的实际返回值
  • known_404是identify_404 的第三个返回值,如果不存在页面返回状态码是200,那么这个返回值就是这个页面本身,如果是其他的状态码,那么这个返回值为 nil
  • page 参数为被检测的path路径
  • displayall 参数是一个boolean值,如果设置true,那么就会将非404 的错误页面也显示出来
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    response = http.get(host, port, "/robots.txt", {redirect_ok=false})
    local status_404, result_404, known_404 = http.identify_404(host,port)
    pages_existed = http.page_exists(response, result_404, known_404, "/robots.txt", false)
    output.restype = type(pages_existed)
    output.result = pages_existed
    return output
end

对 https://www.baidu.com/robots.txt 进行检测

可以看到返回值类型为 boolean, https://www.baidu.com/robots.txt 存在,所以返回 true

0x0011 redirect_ok

redirect_ok 有三个参数: host, port, counter 。其中 counter 为follow重定向的次数

这个函数返回值是一个函数,这个函数的功能是按照相应的规则判断是否可以跳转

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local result = http.redirect_ok(host, port, 1)
    output.restype = type(result)
    output.result = result
    return output
end

找一个默认会跳转的目标进行测试

可以看到返回值是一个函数

0x0012 save_path

save_path 函数有7个参数: host, port, path, status, links_to, linked_from, contenttype

  • path 发现的 path
  • status 状态码
  • link_to 此页面链接到的路径表
  • linked_from 链接到此页面的路径表
  • contenttype 这个路径的content-type

这个函数没有返回值,这个函数将所有相关结果都保存进入 nmap.registry 中

其实这个函数被nse中使用极少,在nse中,与http相关的脚本有 132个

将所有的都打开查找 save_path 函数的调用情况

可以看到,只有一个脚本使用了这个函数,在我看来,只有对 NSE 十分了解,并且有多个脚本联合作战共享数据的情况下才需要这个函数


发送数据包相关的函数基本结束了,下面就是对响应包进行相关操作的函数了

0x0013 get_status_string

get_status_string 只有一个参数:data,这个参数是一个标准响应表,返回值是状态栏字符串

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local data = http.get(host, port, "/", options)
    local result = http.get_status_string(data)
    output.restype = type(result)
    output.result = result
    return output
end

发送数据包,获取状态栏

可以看到返回值类型是字符串,为响应行

0x0014 grab_forms

grab_forms函数只有一个参数:body ,就是http响应包的body

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local data = http.get(host, port, "/login.jsp;jsessionid=F3391B85627E6B4C711A825F921C015D", options)
    local result = http.grab_forms(data.body)
    output.restype = type(result)
    output.result = result
    return output
end

找一个带表格的测试一下返回值:

可以看到返回值是一个表,其中包含整个表格的代码内容

0x0015 parse_date

parse_date 只有一个参数 s, 参数为日期字符串,具体支持如下

* Sun, 06 Nov 1994 08:49:37 GMT  (RFC 822, updated by RFC 1123)
* Sunday, 06-Nov-94 08:49:37 GMT (RFC 850, obsoleted by RFC 1036)
* Sun Nov  6 08:49:37 1994

返回值为一个带有年月日等字段的表

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local data = "Sun, 06 Nov 1994 08:49:37 GMT"
    local result = http.parse_date(data)
    output.restype = type(result)
    output.result = result
    return output
end

使用 "Sun, 06 Nov 1994 08:49:37 GMT" 字符串测试一下

可以看到返回值是一个表,表中有 month、hour、isdst、year、day、sec、min

0x0016 parse_form

parse_form 只有一个参数,form 是一个字符串格式的form,一般也就是 grab_forms 返回值表中的内容

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local data = http.get(host, port, "/login.jsp;jsessionid=F3391B85627E6B4C711A825F921C015D", options)
    local result = http.grab_forms(data.body)
    local result = http.parse_form(result[1])
    output.restype = type(result)
    output.result = result
    return output
end

测试一下这个函数

可以看到返回值是一个表,表中key包含 action,method,fields

0x0017 parse_redirect

parse_redirect 函数有四个参数: host, port, path, response

其中 response 就是 http.get 和 http.head 的标准响应表

返回值就是重定向响应包的元素组成的表

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.包含http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local data = http.get(host, port, "/", options)
    local result = http.parse_redirect(host,port,"/",data)
    output.restype = type(result)
    output.result = result
    return output
end

对 38.115.60.125 的 81 端口进行测试

可以看到返回值为一个表

0x0018 parse_www_authenticate

parse_www_authenticate 函数只有一个参数 s ,s 为响应头字符串,这个函数中可以从变量s 中查找挑战码并保存到表里,之后返回这个表

0x0019 response_contains

这个函数有三个参数: response, pattern, case_sensitive

  • response 标准响应表
  • pattern 模式,也就是其他语言中的正则表达式
  • case_sensitive boolean值,是否大小写敏感,默认是不敏感的
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local data = http.get(host, port, "/", options)
    local contain, content = http.response_contains(data,'[a-z](.+?)',false)
    output.contain_type = type(contain)
    output.contain = contain
    output.content_type = type(content)
    output.result = content
    return output
end

对百度网站进行测试

有两个返回值,第一个是 boolean 类型,表示响应包是否包含我们查找的内容;第二个值为一个表,表中为匹配到的内容。

0x0020 tag_pattern

tag_pattern 函数有两个参数: tag, endtag

  • tag 要查找的 tag 名称
  • endtag boolean 值,true表示创造结尾标签的模式,false表示开始标签
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    --local data = http.get(host, port, "/", options)
    --local contain, content = http.response_contains(data,'[a-z](.+?)',false)

    local result = http.tag_pattern("div",true)
    output.restype = type(result)
    output.result = result
    return output
end

我们以 div 标签为例

0x0021 pipeline_add

pipeline_add 函数有四个参数: path, options, all_requests, method

  • path 请求的路径
  • options 可选参数,正常 http.get 的这个options
  • all_requests 可选参数,由pipeline_add 函数添加后的返回的队列
  • method http方法
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    result = http.pipeline_add("/robots.txt", options, nil,"GET")
    result = http.pipeline_add("/www.zip", options, result,"GET")
    output.restype = type(result)
    output.result = result
    return output
end

我们添加相关的请求后输出一下,如下:

可以看到结果是一个表,表中存在一些字段,比如 header , method, path

0x0022 pipeline_go

pipeline_go 有三个参数:host, port, all_requests

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by root.
--- DateTime: 2020/1/2 上午9:39
---

local http = require "http"
local stdnse = require "stdnse"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end
portrule = function () return true end --shortport.http

action = function(host, port)
    local output = stdnse.output_table()
    local options = {header={}}
    options["header"]["User-Agent"] = "function post test "
    local pipeline1 = http.pipeline_add("/robots.txt", options, nil,"GET")
    local pipeline2 = http.pipeline_add("/www.zip", options, pipeline1,"GET")
    local result = http.pipeline_go(host, port, pipeline2)
    output.restype = type(result)
    output.result = result
    return output
end

使用 pipeline_go 函数进行发送数据包

可以看到的是返回值是一个表,表中包含n个标准响应表