This commit is contained in:
Ada Greymire 2026-08-21 00:40:04 -07:00 committed by GitHub
commit 0958464eea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 327 additions and 0 deletions

207
nselib/wccp.lua Normal file
View file

@ -0,0 +1,207 @@
---
-- A module implementing WCCP protocol (the code is a porting of:
-- https://github.com/benjamin-jones/wccpscan)
-- @class module
-- @name wccp
-- @author "Benjamin Jones <ben@benjaminjones.me>"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
_ENV = stdnse.module("wccp", stdnse.seeall)
WCCP2_HERE_I_AM = string.pack(">I4", 10)
WCCP2_I_SEE_YOU = string.pack(">I4", 11)
WCCP2_REDIRECT_ASSIGN = string.pack(">I4", 12)
WCCP2_REMOVAL_QUERY = string.pack(">I4", 13)
WCCP2_VERSION = string.pack(">H", 0x200)
WCCP2_SECURITY_INFO = string.pack(">H", 0)
WCCP2_NO_SECURITY = string.pack(">I4", 0)
WCCP2_SERVICE_INFO = string.pack(">H", 1)
WCCP2_SERVICE_STANDARD = string.pack(">B", 0)
WCCP2_WC_ID_INFO = string.pack(">H", 3)
WCCP2_WC_VIEW_INFO = string.pack(">H", 5)
WCCP2_REDIRECT_ASSIGN2 = string.pack(">H", 6)
WCCP2_ROUTER_ID_INFO = string.pack(">H", 2)
WCCP2_ROUTER_VIEW_INFO = string.pack(">H", 4)
function list_iter (t)
local i = 0
local n = #t
return function ()
i = i + 1
if i <= n then return t[i] end
end
end
ip_address = function(ip)
-- convert IPv4 address to binary in network byte order
local o1,o2,o3,o4 = ip:match("(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)" )
local num = 2^24*o1 + 2^16*o2 + 2^8*o3 + o4
return string.pack(">I4", num)
end
wccp_web_cache_view_info_component = function(rip, ip)
local mtype = WCCP2_WC_VIEW_INFO
local change = string.pack(">I4", 1)
local nRouter = string.pack(">I4", 1)
local router_list = ip_address(rip)
local rID = string.pack(">I4", 0xFFFFFFFF)
local nCaches = string.pack(">I4", 1)
local cache = ip_address(ip)
local data = (
change ..
nRouter ..
router_list ..
rID ..
nCaches ..
cache
)
local length = string.pack(">H", string.len(data))
local payload = mtype .. length .. data
return payload
end
wccp_web_cache_identity_info_component = function(ip)
local mtype = WCCP2_WC_ID_INFO
local identity_element = ip_address(ip)
local rht = string.pack(">I4", 0)
for i=1,8 do
rht = rht .. string.pack(">I4",0xFFFFFFFF)
end
rht = rht .. string.pack(">H", 10000)
rht = rht .. string.pack(">H", 0)
data = (
identity_element ..
rht
)
local length = string.pack(">H", string.len(data))
local payload = (
mtype .. length .. data
)
return payload
end
wccp_service_info_component = function(sid, port_spec)
local mtype = WCCP2_SERVICE_INFO
local service_id = string.pack(">B", sid)
local service_type = WCCP2_SERVICE_STANDARD
if service_id > WCCP2_SERVICE_STANDARD then
service_type = string.pack(">B", 0x1) -- Dynamic service
end
local priority = string.pack(">B", 0)
local mprotocol = 0x0 -- IPv6 (will give everything!)
local ports_defined = 0
local dest_port_hash = 0
local redirect_only = 0
if port_spec ~= nil then
mprotocol = 0x06 -- TCP
ports_defined = 0x10
dest_port_hash = 0x08
redirect_only = 0x40
end
local protocol = string.pack(">B", mprotocol)
local service_flags = string.pack(">I4",0+ports_defined+redirect_only+dest_port_hash)
local ports = ""
if port_spec ~= nil then
for port in list_iter(port_spec) do
ports = ports .. string.pack(">H", port)
end
else
for i=1,8 do
ports = ports .. string.pack(">H", 0x0)
end
end
local data = (
service_type ..
service_id ..
priority ..
protocol ..
service_flags ..
ports
)
local length = string.pack(">H", string.len(data))
local payload = (
mtype .. length .. data
)
return payload
end
wccp_security_component = function()
local mtype = WCCP2_SECURITY_INFO
local option = WCCP2_NO_SECURITY
local data = (
option
)
local length = string.pack(">H", string.len(data))
local security_component = (
mtype ..
length ..
data
)
return security_component
end
wccp_hia_header = function(message)
local mtype = WCCP2_HERE_I_AM
local version = WCCP2_VERSION
local data = message
local length = string.pack(">H", string.len(data))
local header = (
mtype ..
version ..
length ..
data
)
return header
end
wccp_hia_message = function(rip, ip, sid, port_spec)
local security = wccp_security_component()
local service_info = wccp_service_info_component(sid, port_spec)
local identity_info = wccp_web_cache_identity_info_component(ip)
local view_info = wccp_web_cache_view_info_component(rip, ip)
local msg = (
security ..
service_info ..
identity_info ..
view_info
)
return wccp_hia_header(msg)
end
wccp_parse_isy = function(reply)
local data = {}
local pos = 1
data.mtype, data.version = string.unpack("=IH", reply, pos)
stdnse.debug1(string.format("type=%x version=%x", data.mtype, data.version))
if data.mtype ~= 0xb000000 then
return nil
end
if data.version ~= 0x2 then
return nil
end
return true
end
return _ENV;

120
scripts/wccp-detect.nse Normal file
View file

@ -0,0 +1,120 @@
local wccp = require "wccp"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
description = [[
Performs WCCP Discovery through Hear I Am probes.
]]
---
-- @usage
-- nmap -sU --script wccp-detect -p 2048 <host>
--
-- @output
-- PORT STATE SERVICE REASON
-- 2048/udp open|filtered unknown
-- | wccp-detect:
-- | ServiceType: Standard, Dynamic
-- | Security: None
-- |_ ServiceIDs: 0,...,255
--
-- @xmloutput
-- <table>
-- <table key="ServiceType">
-- <elem>Standard</elem>
-- <elem>Dynamic</elem>
-- </table>
--
-- <table key="Security">
-- <elem>None</elem>
-- </table>
--
-- <table key="ServiceIDs">
-- <elem>0</elem>
-- <elem>...</elem>
-- <elem>255</elem>
-- </table>
--
-- <table key="Level">
-- <elem>1.2</elem>
-- <elem>2.0</elem>
-- </table>
-- </table>
--
author = "Benjamin Jones <ben@benjaminjones.me>"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "intrusive"}
portrule = shortport.version_port_or_service(2048, "dls-monitor", "udp", {"open", "open|filtered"})
local comma_separated = {
__tostring = function(t) return table.concat(t, ", ") end
}
action = function(host, port)
local socket = nmap.new_socket()
socket:set_timeout(500)
socket:bind("0.0.0.0", 2048)
socket:connect(host, 2048, "udp")
local status, lhost, lport, rhost, rport = socket:get_info()
stdnse.debug1(string.format("lhost = %s", lhost))
-- Send a probe to each service ID
local svcid_response = {}
local request = wccp.wccp_hia_message(host.ip, lhost, 0x0, nil)
for id=0,255 do
if id ~= 0 then
local port_spec = {443,0,0,0,0,0,0,0} -- Doesn't really matter
request = wccp.wccp_hia_message(host.ip, lhost, id, port_spec)
end
socket:send(request)
local status, reply = socket:receive()
if status then
svcid_response[id] = wccp.wccp_parse_isy(reply)
end
end
socket:close()
if next(svcid_response) == nil then
return nil
end
nmap.set_port_state(host, port, "open")
local ServType = {}
local ServIDs = {}
setmetatable(ServType, comma_separated)
setmetatable(ServIDs, comma_separated)
if svcid_response[0] == true then
table.insert(ServType, "Standard")
end
for i=1,255 do
if svcid_response[i] ~= nil then
table.insert(ServType, "Dynamic")
break
end
end
for i=0,255 do
if svcid_response[i] ~= nil then
table.insert(ServIDs, i)
end
end
local SecType = {}
table.insert(SecType, "None")
local output = stdnse.output_table()
output["ServiceType"] = ServType
output["Security"] = SecType
output["ServiceIDs"] = ServIDs
return output
end