284 lines
9.1 KiB
Lua
284 lines
9.1 KiB
Lua
local ipOps = require "ipOps"
|
|
local nmap = require "nmap"
|
|
local stdnse = require "stdnse"
|
|
local string = require "string"
|
|
local stringaux = require "stringaux"
|
|
local target = require "target"
|
|
local datafiles = require "datafiles"
|
|
local table = require "table"
|
|
local math = require "math"
|
|
|
|
description = [[
|
|
Adds IPv6 addresses to the scan queue using a wordlist of hexadecimal "words"
|
|
that form addresses in a given subnet.
|
|
]]
|
|
|
|
---
|
|
-- @usage
|
|
-- nmap -6 -p 80 --script targets-ipv6-wordlist --script-args newtargets,targets-ipv6-subnet={2001:db8:c0ca::/64}
|
|
--
|
|
-- @output
|
|
-- Pre-scan script results:
|
|
-- | targets-ipv6-wordlist:
|
|
-- |_ node count: 1254
|
|
--
|
|
-- @args targets-ipv6-wordlist.wordlist File containing hexadecimal words for
|
|
-- building addresses, one per line. Default:
|
|
-- nselib/data/targets-ipv6-wordlist
|
|
-- @args targets-ipv6-wordlist.nsegments Number User can
|
|
-- indicate exactly how big the word must be on
|
|
-- Segments of 16 bits.
|
|
-- @args targets-ipv6-wordlist.fillright With this argument
|
|
-- the script will fill remaining zeros to the right
|
|
-- instead of left (2001:db8:c0a:dead:: instead of
|
|
-- 2001:db8:c0ca::dead)
|
|
-- @args targets-ipv6-subnet table/single IPv6
|
|
-- address with prefix (Ex. 2001:db8:c0ca::/48 or
|
|
-- { 2001:db8:c0ca::/48, 2001:db8:FEA::/48 } )
|
|
|
|
-- Updated 03/12/2014 - V1.4 Update for inclusion in Nmap
|
|
-- Updated 21/05/2014 - V1.3 Eliminate the host phase.
|
|
-- Updated 06/05/2014 - V1.2 Minor corrections and standardization.
|
|
-- Created 29/04/2013 - v1.0 Created by Raul Fuentes <ra.fuentess.sam+nmap@gmail.com>
|
|
--
|
|
|
|
author = "Raúl Fuentes"
|
|
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
|
categories = {
|
|
"discovery"
|
|
}
|
|
|
|
local function split_prefix (net)
|
|
local split = stringaux.strsplit("/", net)
|
|
return split[1], tonumber(split[2])
|
|
end
|
|
|
|
---
|
|
-- Get a Prefix and for that one will add all the valid words we known.
|
|
--
|
|
-- However two arguments from the user can affect how calculated the hosts.
|
|
-- n-segments fix to pick a number of segments (by default is any segment
|
|
-- enough small for be inside of the subnet prefix) and fill-right which alter
|
|
-- where we place the remaining zeros (Default the left).
|
|
-- @param Direccion String IPv6 address (Subnet)
|
|
-- @param Prefijo Number Prefix value of subnet
|
|
-- @param TablaPalabras Table containing all the elements to search.
|
|
-- @param User_Segs Number of segments to search.
|
|
-- @param User_Right Boolean for fill right or left (Default)
|
|
-- @return Boolean True if was successful the operation
|
|
-- @return Number Total of successfully nodes added to the scan list.
|
|
-- @return Error Any error generated, default: "" not nil.
|
|
local CrearRangoHosts = function (Direccion, Prefijo, TablaPalabras,
|
|
User_Segs, User_Right)
|
|
|
|
local IPv6Bin, Error = ipOps.ip_to_bin(Direccion)
|
|
|
|
if IPv6Bin == nil then
|
|
return false, 0, Error
|
|
end
|
|
|
|
-- We have (128 - n ) / ( 16 )
|
|
-- The first part are how many bits are left to hosts portion
|
|
-- The Second part is the size of the segments (16 bits).
|
|
local MaxRangoSegmentos
|
|
if User_Segs == nil then
|
|
MaxRangoSegmentos = math.ceil((128 - Prefijo) / 16)
|
|
User_Segs = false
|
|
else
|
|
MaxRangoSegmentos = tonumber(User_Segs)
|
|
end
|
|
|
|
stdnse.debug1("Will be calculated %d hosts for the subnet: %s/%s", #TablaPalabras, Direccion, Prefijo)
|
|
|
|
local iTotal = 0
|
|
-- Palabras is a table with two elements Segmento & Binario
|
|
for Indice, Palabras in ipairs(TablaPalabras) do
|
|
|
|
if ((tonumber(Palabras.Segmento) <= MaxRangoSegmentos) and
|
|
User_Segs == false) or
|
|
(User_Segs and (tonumber(Palabras.Segmento) == MaxRangoSegmentos)) then
|
|
|
|
-- We are going to add binaries values but the question is
|
|
-- whenever must fill with zeros?
|
|
local Filler = string.rep("0", 128 - (Prefijo + #Palabras.Binario))
|
|
|
|
local Host
|
|
if User_Right ~= nil then
|
|
Host = IPv6Bin:sub(1, Prefijo) .. Palabras.Binario .. Filler
|
|
else
|
|
Host = IPv6Bin:sub(1, Prefijo) .. Filler .. Palabras.Binario
|
|
end
|
|
|
|
-- We pass the binaries to valid IPv6
|
|
local Error
|
|
Host, Error = ipOps.bin_to_ip(Host)
|
|
if Host == nil then
|
|
-- Something is very wrong but we don-t stop
|
|
stdnse.debug1("Failed to create IPv6 address: %s", Error)
|
|
else
|
|
if target.ALLOW_NEW_TARGETS then
|
|
local bAux, sAux = target.add(Host)
|
|
if bAux then
|
|
iTotal = iTotal + 1
|
|
else
|
|
stdnse.debug1("Had been a error adding the node %s: %s", Host, sAux)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return true, iTotal
|
|
end
|
|
|
|
---
|
|
-- Parsing process of concatenate each word on the dictionary with subnetworks.
|
|
--
|
|
--@param filename The name of the file to parse
|
|
-- @return Table Table of elements returned (Nil if there was a error)
|
|
-- @return String Empty if there is no error, otherwise the error message.
|
|
local LeerArchivo = function (filename)
|
|
-- [ "^%s*(%w+)%s+[^#]+" ] = "^%s*%w+%s+([^#]+)" }
|
|
local bBoolean, Archivo = datafiles.parse_file(filename,
|
|
{"^([0-9a-fA-F]+)$",})
|
|
if bBoolean ~= true then
|
|
return nil, Archivo
|
|
end
|
|
|
|
local Candidatos = {}
|
|
local Registro = {
|
|
["Segmento"] = 0,
|
|
["Binario"] = "0",
|
|
}
|
|
|
|
for index, reg in pairs(Archivo) do
|
|
Registro = {
|
|
["Segmento"] = 0,
|
|
["Binario"] = "0",
|
|
}
|
|
|
|
Registro.Segmento = math.ceil(#reg / 4)
|
|
Registro.Binario = ipOps.hex_to_bin(reg)
|
|
table.insert(Candidatos, Registro)
|
|
|
|
end
|
|
|
|
stdnse.debug1("%d candidate words", #Candidatos)
|
|
return Candidatos, ""
|
|
end
|
|
|
|
---
|
|
-- We get the info we need from the user and other scripts then we add them to
|
|
-- our file!
|
|
--
|
|
-- (So easy that seem we need to make them obscure)
|
|
local Prescanning = function ()
|
|
local tSalida = {
|
|
Nodos = 0,
|
|
Error = "",
|
|
}
|
|
|
|
-- First we get the info from known prefixes because we need those Prefixes
|
|
local IPv6PrefijoUsuario = stdnse.get_script_args "targets-ipv6-subnet"
|
|
local User_Segs = stdnse.get_script_args "targets-ipv6-wordlist.nsegments"
|
|
local User_Right = stdnse.get_script_args "targets-ipv6-wordlist.fillright"
|
|
local wordlist = (stdnse.get_script_args("targets-ipv6-wordlist.wordlist")
|
|
or "nselib/data/targets-ipv6-wordlist")
|
|
|
|
-- Second, we read our vital table
|
|
local TablaPalabras, sError = LeerArchivo(wordlist)
|
|
|
|
if TablaPalabras == nil then
|
|
tSalida.Error = sError
|
|
return false, tSalida
|
|
end
|
|
|
|
-- We pass all the prefixes to one single table (health for the eyes)
|
|
if IPv6PrefijoUsuario == nil then
|
|
tSalida.Error = "There is not IPv6 subnets to try to scan!." ..
|
|
" You can run a script for discovering or adding your own" ..
|
|
" with the arg: targets-ipv6-subnet."
|
|
return false, tSalida
|
|
end
|
|
|
|
local IPv6PrefijosTotales = {}
|
|
if IPv6PrefijoUsuario ~= nil then
|
|
if type(IPv6PrefijoUsuario) == "string" then
|
|
stdnse.verbose2("Number of Prefixes Known from other sources: 1 ")
|
|
table.insert(IPv6PrefijosTotales, IPv6PrefijoUsuario)
|
|
elseif type(IPv6PrefijoUsuario) == "table" then
|
|
stdnse.verbose2("Number of Prefixes Known from other sources: " .. #IPv6PrefijoUsuario)
|
|
for _, PrefixAux in ipairs(IPv6PrefijoUsuario) do
|
|
table.insert(IPv6PrefijosTotales, PrefixAux)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- We begin to explore all thoses prefixes and retrieve our work here
|
|
for _, PrefixAux in ipairs(IPv6PrefijosTotales) do
|
|
local Direccion, Prefijo = split_prefix(PrefixAux)
|
|
local bSalida, nodes, sError = CrearRangoHosts(Direccion, Prefijo,
|
|
TablaPalabras, User_Segs, User_Right)
|
|
|
|
if bSalida ~= true then
|
|
stdnse.debug1("There was a error for the prefix %s: %s", PrefixAux, sError)
|
|
end
|
|
|
|
if sError and sError ~= "" then
|
|
-- Not all the error are fatal for the script.
|
|
tSalida.Error = tSalida.Error .. "\n" .. sError
|
|
end
|
|
|
|
tSalida.Nodos = tSalida.Nodos + nodes
|
|
end
|
|
|
|
|
|
return true, tSalida
|
|
end
|
|
|
|
|
|
---
|
|
-- The script need to be working with IPv6
|
|
function prerule ()
|
|
if not (nmap.address_family() == "inet6") then
|
|
stdnse.verbose1("Need to be executed for IPv6.")
|
|
return false
|
|
end
|
|
|
|
if stdnse.get_script_args 'newtargets' == nil then
|
|
stdnse.verbose1(" Will only work on " ..
|
|
"pre-scanning. The argument newtargets is needed for the host-scanning" ..
|
|
" to work.")
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
function action ()
|
|
|
|
--Vars for created the final report
|
|
local tOutput = stdnse.output_table()
|
|
|
|
local bExito, tSalida = Prescanning()
|
|
|
|
-- Now we adapt the exit to tOutput and add the hosts to the target!
|
|
if tSalida.Error and tSalida.Error ~= "" then
|
|
tOutput.warning = tSalida.Error
|
|
stdnse.debug1("Was unable to add nodes to the scan list due this error: %s",
|
|
tSalida.Error)
|
|
end
|
|
|
|
if bExito then
|
|
if tSalida.Nodos == 0 then
|
|
stdnse.verbose2("No nodes were added " ..
|
|
" to scan list! You can increase verbosity for more information" ..
|
|
" (maybe not newtargets argument?) ")
|
|
end
|
|
tOutput["node count"] = tSalida.Nodos
|
|
end
|
|
|
|
|
|
return tOutput
|
|
end
|