description = [[ Retrieves device information from Huawei AX3 router via /api/system/deviceinfo endpoint. Queries the router's HTTP/HTTPS admin API and extracts device details including model, serial number, hardware/software versions, uptime, and capability flags. ]] author = "gbfdhenr (Liang Xiangan)" license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"discovery", "safe", "default"} local http = require "http" local shortport = require "shortport" local json = require "json" local stdnse = require "stdnse" -- Port rule: match ports 80/443 with http/https service (version detection preferred) -- Falls back to port number if version detection not run portrule = function(host, port) -- Try version-based match first (requires -sV) local svc_match = shortport.port_or_service({80, 443}, "http", "https")(host, port) if svc_match then return true end -- Fallback: match by port number alone return shortport.portnumber({80, 443})(host, port) end action = function(host, port) -- Custom headers with timeout local options = { header = { ["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", ["Accept"] = "application/json, text/plain, */*", ["Accept-Language"] = "zh-CN,zh;q=0.9,en;q=0.8", }, timeout = 10000, -- 10 second timeout } local response = http.get(host, port, "/api/system/deviceinfo", options) if not response then return "ERROR: No HTTP response (connection failed or timeout)" end if response.status ~= 200 then return string.format("ERROR: HTTP status %d", response.status) end local body = response.body or "" if body == "" then return "ERROR: Empty response body" end -- json.parse returns (success, data) on success, (false, error) on failure local ok, data = json.parse(body) if not ok or not data or type(data) ~= "table" then return string.format("ERROR: JSON parse failed: %s", tostring(data)) end local lines = {} local ip = host.ip -- Header table.insert(lines, string.format("Huawei Router AX3(%s) Info:", ip)) -- Extract fields of interest local fields = { {key = "CustZHFriendlyName", label = "Chinese Name"}, {key = "CustENFriendlyName", label = "English Name"}, {key = "CustDeviceName", label = "Device Model"}, {key = "CustDeviceType", label = "Device Type"}, {key = "SerialNumber", label = "Serial Number"}, {key = "HardwareVersion", label = "Hardware Version"}, {key = "SoftwareVersion", label = "Software Version"}, {key = "UpTime", label = "Uptime (seconds)"}, {key = "FriendlyName", label = "Friendly Name"}, {key = "uuid", label = "UUID"}, {key = "ManufacturerOUI", label = "Manufacturer OUI"}, {key = "DeviceIconType", label = "Device Icon Type"}, } for _, f in ipairs(fields) do local value = data[f.key] if value ~= nil then table.insert(lines, string.format(" %s: %s", f.label, tostring(value))) end end -- custinfo sub-object if data.custinfo and type(data.custinfo) == "table" then table.insert(lines, " [Vendor Info]") for k, v in pairs(data.custinfo) do table.insert(lines, string.format(" %s: %s", k, tostring(v))) end end -- devcap sub-object: iterate ALL SoftwareCapability keys (not hardcoded) if data.devcap and data.devcap.SoftwareCapability and type(data.devcap.SoftwareCapability) == "table" then table.insert(lines, " [Software Capabilities]") local cap = data.devcap.SoftwareCapability -- Collect and sort keys numerically local keys = {} for k, _ in pairs(cap) do table.insert(keys, k) end table.sort(keys, function(a, b) local na, nb = tonumber(a), tonumber(b) if na and nb then return na < nb end return tostring(a) < tostring(b) end) for _, k in ipairs(keys) do table.insert(lines, string.format(" Capability %s: %s", k, tostring(cap[k]))) end end return table.concat(lines, "\n") end