mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
Merge r26341:26417 from /nmap-exp/david/nmap-cpe.
This adds CPE output support.
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o Added Common Platform Enumeration (CPE, http://cpe.mitre.org/)
|
||||
output for OS and service versions. These show up in normal output
|
||||
with the headings "OS CPE:" and "Service Info:":
|
||||
OS CPE: cpe:/o:linux:kernel:2.6.39
|
||||
Service Info: OS: Linux; CPE: cpe:/o:linux:kernel
|
||||
These also appear in XML output, which additionally has CPE entries
|
||||
for service versions. [David, Henri]
|
||||
|
||||
o [NSE] Added new default credential list for Oracle and modified the
|
||||
oracle-brute script to make use of it. [Patrik]
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@
|
||||
<!ELEMENT owner EMPTY >
|
||||
<!ATTLIST owner name CDATA #REQUIRED >
|
||||
|
||||
<!ELEMENT service EMPTY >
|
||||
<!ELEMENT service (cpe*) >
|
||||
<!ATTLIST service
|
||||
name CDATA #REQUIRED
|
||||
conf %service_confs; #REQUIRED
|
||||
@@ -221,6 +221,8 @@
|
||||
servicefp CDATA #IMPLIED
|
||||
>
|
||||
|
||||
<!ELEMENT cpe (#PCDATA)>
|
||||
|
||||
<!ELEMENT script EMPTY >
|
||||
<!ATTLIST script
|
||||
id CDATA #REQUIRED
|
||||
@@ -235,7 +237,7 @@
|
||||
proto %port_protocols; #REQUIRED
|
||||
portid %attr_numeric; #REQUIRED
|
||||
>
|
||||
<!ELEMENT osclass EMPTY >
|
||||
<!ELEMENT osclass (cpe*) >
|
||||
<!ATTLIST osclass
|
||||
vendor CDATA #REQUIRED
|
||||
osgen CDATA #IMPLIED
|
||||
|
||||
@@ -98,35 +98,32 @@
|
||||
<screen>
|
||||
# <userinput>nmap -A -T4 scanme.nmap.org</userinput>
|
||||
|
||||
Nmap scan report for scanme.nmap.org (64.13.134.52)
|
||||
Host is up (0.045s latency).
|
||||
Not shown: 993 filtered ports
|
||||
PORT STATE SERVICE VERSION
|
||||
22/tcp open ssh OpenSSH 4.3 (protocol 2.0)
|
||||
| ssh-hostkey: 1024 60:ac:4d:51:b1:cd:85:09:12:16:92:76:1d:5d:27:6e (DSA)
|
||||
|_2048 2c:22:75:60:4b:c3:3b:18:a2:97:2c:96:7e:28:dc:dd (RSA)
|
||||
25/tcp closed smtp
|
||||
53/tcp open domain
|
||||
70/tcp closed gopher
|
||||
80/tcp open http Apache httpd 2.2.3 ((CentOS))
|
||||
|_html-title: Go ahead and ScanMe!
|
||||
| http-methods: Potentially risky methods: TRACE
|
||||
|_See http://nmap.org/nsedoc/scripts/http-methods.html
|
||||
113/tcp closed auth
|
||||
31337/tcp closed Elite
|
||||
Nmap scan report for scanme.nmap.org (74.207.244.221)
|
||||
Host is up (0.029s latency).
|
||||
rDNS record for 74.207.244.221: li86-221.members.linode.com
|
||||
Not shown: 995 closed ports
|
||||
PORT STATE SERVICE VERSION
|
||||
22/tcp open ssh OpenSSH 5.3p1 Debian 3ubuntu7 (protocol 2.0)
|
||||
| ssh-hostkey: 1024 8d:60:f1:7c:ca:b7:3d:0a:d6:67:54:9d:69:d9:b9:dd (DSA)
|
||||
|_2048 79:f8:09:ac:d4:e2:32:42:10:49:d3:bd:20:82:85:ec (RSA)
|
||||
80/tcp open http Apache httpd 2.2.14 ((Ubuntu))
|
||||
|_http-title: Go ahead and ScanMe!
|
||||
646/tcp filtered ldp
|
||||
1720/tcp filtered H.323/Q.931
|
||||
9929/tcp open nping-echo Nping echo
|
||||
Device type: general purpose
|
||||
Running: Linux 2.6.X
|
||||
OS details: Linux 2.6.13 - 2.6.31, Linux 2.6.18
|
||||
Network Distance: 13 hops
|
||||
OS CPE: cpe:/o:linux:kernel:2.6.39
|
||||
OS details: Linux 2.6.39
|
||||
Network Distance: 11 hops
|
||||
Service Info: OS: Linux; CPE: cpe:/o:linux:kernel
|
||||
|
||||
TRACEROUTE (using port 80/tcp)
|
||||
HOP RTT ADDRESS
|
||||
TRACEROUTE (using port 53/tcp)
|
||||
HOP RTT ADDRESS
|
||||
[Cut first 10 hops for brevity]
|
||||
11 80.33 ms layer42.car2.sanjose2.level3.net (4.59.4.78)
|
||||
12 137.52 ms xe6-2.core1.svk.layer42.net (69.36.239.221)
|
||||
13 44.15 ms scanme.nmap.org (64.13.134.52)
|
||||
11 17.65 ms li86-221.members.linode.com (74.207.244.221)
|
||||
|
||||
Nmap done: 1 IP address (1 host up) scanned in 22.19 seconds
|
||||
Nmap done: 1 IP address (1 host up) scanned in 14.40 seconds
|
||||
</screen>
|
||||
</example>
|
||||
|
||||
@@ -1859,9 +1856,11 @@ way.</para>
|
||||
(e.g. FTP, SSH, Telnet, HTTP), the application name (e.g. ISC
|
||||
BIND, Apache httpd, Solaris telnetd), the version number,
|
||||
hostname, device type (e.g. printer, router), the OS family
|
||||
(e.g. Windows, Linux) and sometimes miscellaneous details like
|
||||
(e.g. Windows, Linux). When possible, Nmap also gets the
|
||||
Common Platform Enumeration (CPE)<indexterm><primary>Common Platform Enumeration</primary><secondary>service</secondary></indexterm>
|
||||
representation of this information. Sometimes miscellaneous details like
|
||||
whether an X server is open to connections, the SSH protocol
|
||||
version, or the KaZaA user name). Of course, most services don't
|
||||
version, or the KaZaA user name, are available. Of course, most services don't
|
||||
provide all of this information. If Nmap was compiled with
|
||||
OpenSSL support, it will connect to SSL servers to deduce the
|
||||
service listening behind that encryption layer.<indexterm><primary>SSL</primary><secondary>in version detection</secondary></indexterm>
|
||||
@@ -2027,6 +2026,7 @@ way.</para>
|
||||
<title>OS Detection</title>
|
||||
<indexterm class="startofrange" id="man-os-detection-indexterm"><primary>OS detection</primary></indexterm>
|
||||
|
||||
<indexterm><primary>CPE</primary><see>Common Platform Enumeration</see></indexterm>
|
||||
<para>One of Nmap's best-known features is remote OS detection
|
||||
using TCP/IP stack fingerprinting. Nmap sends a series of TCP and
|
||||
UDP packets to the remote host and examines practically every bit
|
||||
@@ -2040,7 +2040,10 @@ way.</para>
|
||||
OS, and a classification which provides the vendor name
|
||||
(e.g. Sun), underlying OS (e.g. Solaris), OS generation (e.g. 10),
|
||||
and device type (general purpose, router, switch, game console,
|
||||
etc).</para>
|
||||
etc). Most fingerprints also have a Common Platform Enumeration
|
||||
(CPE)<indexterm><primary>Common Platform Enumeration</primary><secondary>operating system</secondary></indexterm>
|
||||
representation, like
|
||||
<literal>cpe:/o:linux:kernel:2.6</literal>.</para>
|
||||
|
||||
<para>If Nmap is unable to guess the OS of a machine, and
|
||||
conditions are good (e.g. at least one open port and one closed
|
||||
|
||||
@@ -166,6 +166,14 @@ struct OS_Classification {
|
||||
const char *OS_Family;
|
||||
const char *OS_Generation; /* Can be NULL if unclassified */
|
||||
const char *Device_Type;
|
||||
std::vector<const char *> cpe;
|
||||
|
||||
OS_Classification() {
|
||||
OS_Vendor = NULL;
|
||||
OS_Family = NULL;
|
||||
OS_Generation = NULL;
|
||||
Device_Type = NULL;
|
||||
}
|
||||
};
|
||||
|
||||
struct FingerTest {
|
||||
|
||||
2491
nmap-os-db
2491
nmap-os-db
File diff suppressed because it is too large
Load Diff
4180
nmap-service-probes
4180
nmap-service-probes
File diff suppressed because it is too large
Load Diff
@@ -540,11 +540,11 @@ static int l_set_port_version (lua_State *L)
|
||||
if (o.servicescan)
|
||||
target->ports.setServiceProbeResults(p->portno, p->proto,
|
||||
probestate, name, tunnel, product,
|
||||
version, extrainfo, hostname, ostype, devicetype, NULL);
|
||||
version, extrainfo, hostname, ostype, devicetype, NULL, NULL, NULL, NULL);
|
||||
else
|
||||
target->ports.setServiceProbeResults(p->portno, p->proto,
|
||||
probestate, name, tunnel, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL);
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
41
osscan.cc
41
osscan.cc
@@ -145,6 +145,23 @@ const char *string_pool_substr_strip(const char *s, const char *t) {
|
||||
return string_pool_substr(s, t);
|
||||
}
|
||||
|
||||
/* Skip over whitespace to find the beginning of a word, then read until the
|
||||
next whilespace character. Returns NULL if only whitespace is found. */
|
||||
static const char *string_pool_strip_word(const char *s) {
|
||||
const char *t;
|
||||
|
||||
while (isspace((int) (unsigned char) *s))
|
||||
s++;
|
||||
t = s;
|
||||
while (*t != '\0' && !isspace((int) (unsigned char) *t))
|
||||
t++;
|
||||
|
||||
if (s == t)
|
||||
return NULL;
|
||||
|
||||
return string_pool_substr(s, t);
|
||||
}
|
||||
|
||||
/* Format a string with sprintf and insert it with string_pool_insert. */
|
||||
const char *string_pool_sprintf(const char *fmt, ...)
|
||||
{
|
||||
@@ -983,6 +1000,24 @@ static void parse_classline(FingerPrint *FP, char *thisline, int lineno) {
|
||||
FP->OS_class.push_back(os_class);
|
||||
}
|
||||
|
||||
static void parse_cpeline(FingerPrint *FP, char *thisline, int lineno) {
|
||||
const char *cpe;
|
||||
|
||||
if (FP->OS_class.empty())
|
||||
fatal("\"CPE\" line without preceding \"Class\" at line %d", lineno);
|
||||
|
||||
OS_Classification& osc = FP->OS_class.back();
|
||||
|
||||
if (thisline == NULL || strncmp(thisline, "CPE ", 4) != 0)
|
||||
fatal("Bogus line #%d (%s) passed to %s()", lineno, thisline, __func__);
|
||||
|
||||
/* The cpe part may be followed by whitespace-separated flags (like "auto"),
|
||||
which we ignore. */
|
||||
cpe = string_pool_strip_word(thisline + 4);
|
||||
assert(cpe != NULL);
|
||||
osc.cpe.push_back(cpe);
|
||||
}
|
||||
|
||||
/* Parses a single fingerprint from the memory region given. If a
|
||||
non-null fingerprint is returned, the user is in charge of freeing it
|
||||
when done. This function does not require the fingerprint to be 100%
|
||||
@@ -1042,6 +1077,10 @@ FingerPrint *parse_single_fingerprint(char *fprint_orig) {
|
||||
|
||||
parse_classline(FP, thisline, lineno);
|
||||
|
||||
} else if (strncmp(thisline, "CPE ", 4) == 0) {
|
||||
|
||||
parse_cpeline(FP, thisline, lineno);
|
||||
|
||||
} else if ((q = strchr(thisline, '('))) {
|
||||
FingerTest test;
|
||||
*q = '\0';
|
||||
@@ -1148,6 +1187,8 @@ fparse:
|
||||
goto fparse;
|
||||
} else if (strncmp(line, "Class ", 6) == 0) {
|
||||
parse_classline(current, line, lineno);
|
||||
} else if (strncmp(line, "CPE ", 4) == 0) {
|
||||
parse_cpeline(current, line, lineno);
|
||||
} else {
|
||||
FingerTest test;
|
||||
p = line;
|
||||
|
||||
83
output.cc
83
output.cc
@@ -237,7 +237,19 @@ static void print_xml_service(const struct serviceDeductions *sd) {
|
||||
xml_attribute("proto", "rpc");
|
||||
}
|
||||
|
||||
xml_close_empty_tag();
|
||||
if (sd->cpe.empty()) {
|
||||
xml_close_empty_tag();
|
||||
} else {
|
||||
unsigned int i;
|
||||
|
||||
xml_close_start_tag();
|
||||
for (i = 0; i < sd->cpe.size(); i++) {
|
||||
xml_start_tag("cpe");
|
||||
xml_write_escaped("%s", sd->cpe[i]);
|
||||
xml_end_tag();
|
||||
}
|
||||
xml_end_tag();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
@@ -1442,13 +1454,15 @@ static int addtochararrayifnew(const char *arr[], int *numentries, int arrsize,
|
||||
static void printosclassificationoutput(const struct
|
||||
OS_Classification_Results *OSR,
|
||||
bool guess) {
|
||||
int classno, i, familyno;
|
||||
int classno, cpeno, familyno;
|
||||
unsigned int i;
|
||||
int overflow = 0; /* Whether we have too many devices to list */
|
||||
const char *types[MAX_OS_CLASSMEMBERS];
|
||||
const char *cpes[MAX_OS_CLASSMEMBERS];
|
||||
char fullfamily[MAX_OS_CLASSMEMBERS][128]; // "[vendor] [os family]"
|
||||
double familyaccuracy[MAX_OS_CLASSMEMBERS]; // highest accuracy for this fullfamily
|
||||
char familygenerations[MAX_OS_CLASSMEMBERS][48]; // example: "4.X|5.X|6.X"
|
||||
int numtypes = 0, numfamilies = 0;
|
||||
int numtypes = 0, numcpes = 0, numfamilies = 0;
|
||||
char tmpbuf[1024];
|
||||
|
||||
for (i = 0; i < MAX_OS_CLASSMEMBERS; i++) {
|
||||
@@ -1468,7 +1482,17 @@ static void printosclassificationoutput(const struct
|
||||
if (OSR->OSC[classno]->OS_Generation)
|
||||
xml_attribute("osgen", "%s", OSR->OSC[classno]->OS_Generation);
|
||||
xml_attribute("accuracy", "%d", (int) (OSR->OSC_Accuracy[classno] * 100));
|
||||
xml_close_empty_tag();
|
||||
if (OSR->OSC[classno]->cpe.empty()) {
|
||||
xml_close_empty_tag();
|
||||
} else {
|
||||
xml_close_start_tag();
|
||||
for (i = 0; i < OSR->OSC[classno]->cpe.size(); i++) {
|
||||
xml_start_tag("cpe");
|
||||
xml_write_escaped("%s", OSR->OSC[classno]->cpe[i]);
|
||||
xml_end_tag();
|
||||
}
|
||||
xml_end_tag();
|
||||
}
|
||||
xml_newline();
|
||||
}
|
||||
|
||||
@@ -1483,6 +1507,12 @@ static void printosclassificationoutput(const struct
|
||||
OSR->OSC[classno]->Device_Type) == -1) {
|
||||
overflow = 1;
|
||||
}
|
||||
for (i = 0; i < OSR->OSC[classno]->cpe.size(); i++) {
|
||||
if (addtochararrayifnew(cpes, &numcpes, MAX_OS_CLASSMEMBERS,
|
||||
OSR->OSC[classno]->cpe[i]) == -1) {
|
||||
overflow = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If family and vendor names are the same, no point being redundant
|
||||
if (strcmp(OSR->OSC[classno]->OS_Vendor, OSR->OSC[classno]->OS_Family) == 0)
|
||||
@@ -1545,6 +1575,13 @@ static void printosclassificationoutput(const struct
|
||||
floor(familyaccuracy[familyno] * 100));
|
||||
}
|
||||
log_write(LOG_PLAIN, "\n");
|
||||
|
||||
if (numcpes > 0) {
|
||||
log_write(LOG_PLAIN, "OS CPE:");
|
||||
for (cpeno = 0; cpeno < numcpes; cpeno++)
|
||||
log_write(LOG_PLAIN, " %s", cpes[cpeno]);
|
||||
log_write(LOG_PLAIN, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
log_flush_all();
|
||||
@@ -1846,16 +1883,19 @@ void printserviceinfooutput(Target *currenths) {
|
||||
Port *p = NULL;
|
||||
Port port;
|
||||
struct serviceDeductions sd;
|
||||
int i, numhostnames = 0, numostypes = 0, numdevicetypes = 0;
|
||||
int i, numhostnames = 0, numostypes = 0, numdevicetypes = 0, numcpes = 0;
|
||||
char hostname_tbl[MAX_SERVICE_INFO_FIELDS][MAXHOSTNAMELEN];
|
||||
char ostype_tbl[MAX_SERVICE_INFO_FIELDS][64];
|
||||
char devicetype_tbl[MAX_SERVICE_INFO_FIELDS][64];
|
||||
char cpe_tbl[MAX_SERVICE_INFO_FIELDS][80];
|
||||
const char *delim;
|
||||
|
||||
for (i = 0; i < MAX_SERVICE_INFO_FIELDS; i++)
|
||||
hostname_tbl[i][0] = ostype_tbl[i][0] = devicetype_tbl[i][0] = '\0';
|
||||
hostname_tbl[i][0] = ostype_tbl[i][0] = devicetype_tbl[i][0] = cpe_tbl[i][0] = '\0';
|
||||
|
||||
while ((p = currenths->ports.nextPort(p, &port, TCPANDUDPANDSCTP, PORT_OPEN))) {
|
||||
std::vector<char *>::iterator it;
|
||||
|
||||
// The following 2 lines (from portlist.h) tell us that we don't need to
|
||||
// worry about free()ing anything in the serviceDeductions struct. pass in
|
||||
// an allocated struct serviceDeductions (don't wory about initializing, and
|
||||
@@ -1901,9 +1941,29 @@ void printserviceinfooutput(Target *currenths) {
|
||||
}
|
||||
}
|
||||
|
||||
for (it = sd.cpe.begin(); it != sd.cpe.end(); it++) {
|
||||
for (i = 0; i < MAX_SERVICE_INFO_FIELDS; i++) {
|
||||
if (cpe_tbl[i][0] && !strcmp(&cpe_tbl[i][0], *it))
|
||||
break;
|
||||
/* Applications (CPE part "a") aren't shown in this summary list in
|
||||
normal output. "a" classifications belong to an individual port, not
|
||||
the entire host, unlike "h" (hardware) and "o" (operating system).
|
||||
There isn't a good place to put the "a" classifications, so they are
|
||||
written to XML only. */
|
||||
if (cpe_get_part(*it) == 'a')
|
||||
break;
|
||||
|
||||
if (!cpe_tbl[i][0]) {
|
||||
numcpes++;
|
||||
strncpy(&cpe_tbl[i][0], *it, sizeof(cpe_tbl[i]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!numhostnames && !numostypes && !numdevicetypes)
|
||||
if (!numhostnames && !numostypes && !numdevicetypes && !numcpes)
|
||||
return;
|
||||
|
||||
log_write(LOG_PLAIN, "Service Info:");
|
||||
@@ -1938,6 +1998,15 @@ void printserviceinfooutput(Target *currenths) {
|
||||
delim = "; ";
|
||||
}
|
||||
|
||||
if (numcpes > 0) {
|
||||
log_write(LOG_PLAIN, "%sCPE: %s", delim, &cpe_tbl[0][0]);
|
||||
for (i = 1; i < MAX_SERVICE_INFO_FIELDS; i++) {
|
||||
if (cpe_tbl[i][0])
|
||||
log_write(LOG_PLAIN, ", %s", &cpe_tbl[i][0]);
|
||||
}
|
||||
delim = "; ";
|
||||
}
|
||||
|
||||
log_write(LOG_PLAIN, "\n");
|
||||
log_flush_all();
|
||||
}
|
||||
|
||||
18
portlist.cc
18
portlist.cc
@@ -117,6 +117,8 @@ Port::Port() {
|
||||
|
||||
void Port::freeService() {
|
||||
if (service != NULL) {
|
||||
std::vector<char *>::iterator it;
|
||||
|
||||
if (service->name)
|
||||
free(service->name);
|
||||
if (service->product)
|
||||
@@ -133,6 +135,8 @@ void Port::freeService() {
|
||||
free(service->devicetype);
|
||||
if (service->service_fp)
|
||||
free(service->service_fp);
|
||||
for (it = service->cpe.begin(); it != service->cpe.end(); it++)
|
||||
free(*it);
|
||||
delete service;
|
||||
}
|
||||
}
|
||||
@@ -332,8 +336,10 @@ void PortList::setServiceProbeResults(u16 portno, int protocol,
|
||||
enum serviceprobestate sres, const char *sname,
|
||||
enum service_tunnel_type tunnel, const char *product, const char *version,
|
||||
const char *extrainfo, const char *hostname, const char *ostype,
|
||||
const char *devicetype, const char *fingerprint) {
|
||||
const char *devicetype, const char *cpe_a, const char *cpe_h, const char *cpe_o,
|
||||
const char *fingerprint) {
|
||||
Port *port;
|
||||
char *p;
|
||||
|
||||
port = createPort(portno, protocol);
|
||||
if (port->service == NULL)
|
||||
@@ -380,6 +386,16 @@ void PortList::setServiceProbeResults(u16 portno, int protocol,
|
||||
port->service->hostname = cstringSanityCheck(hostname, 80);
|
||||
port->service->ostype = cstringSanityCheck(ostype, 32);
|
||||
port->service->devicetype = cstringSanityCheck(devicetype, 32);
|
||||
|
||||
p = cstringSanityCheck(cpe_a, 80);
|
||||
if (p != NULL)
|
||||
port->service->cpe.push_back(p);
|
||||
p = cstringSanityCheck(cpe_h, 80);
|
||||
if (p != NULL)
|
||||
port->service->cpe.push_back(p);
|
||||
p = cstringSanityCheck(cpe_o, 80);
|
||||
if (p != NULL)
|
||||
port->service->cpe.push_back(p);
|
||||
}
|
||||
|
||||
/* Sets the results of an RPC scan. if rpc_status is not
|
||||
|
||||
@@ -155,6 +155,7 @@ struct serviceDeductions {
|
||||
char *hostname;
|
||||
char *ostype;
|
||||
char *devicetype;
|
||||
std::vector<char *> cpe;
|
||||
// SERVICE_TUNNEL_NONE or SERVICE_TUNNEL_SSL
|
||||
enum service_tunnel_type service_tunnel;
|
||||
// if we should give the user a service fingerprint to submit, here it is. Otherwise NULL.
|
||||
@@ -265,7 +266,9 @@ class PortList {
|
||||
enum service_tunnel_type tunnel, const char *product,
|
||||
const char *version, const char *hostname,
|
||||
const char *ostype, const char *devicetype,
|
||||
const char *extrainfo, const char *fingerprint);
|
||||
const char *extrainfo,
|
||||
const char *cpe_a, const char *cpe_h, const char *cpe_o,
|
||||
const char *fingerprint);
|
||||
|
||||
// pass in an allocated struct serviceDeductions (don't worry about initializing, and
|
||||
// you don't have to free any internal ptrs. See the serviceDeductions definition for
|
||||
|
||||
488
service_scan.cc
488
service_scan.cc
@@ -166,6 +166,9 @@ public:
|
||||
char hostname_matched[80];
|
||||
char ostype_matched[32];
|
||||
char devicetype_matched[32];
|
||||
char cpe_a_matched[80];
|
||||
char cpe_h_matched[80];
|
||||
char cpe_o_matched[80];
|
||||
enum service_tunnel_type tunnel; /* SERVICE_TUNNEL_NONE, SERVICE_TUNNEL_SSL */
|
||||
// This stores our SSL session id, which will help speed up subsequent
|
||||
// SSL connections. It's overwritten each time. void* is used so we don't
|
||||
@@ -270,6 +273,7 @@ ServiceProbeMatch::ServiceProbeMatch() {
|
||||
}
|
||||
|
||||
ServiceProbeMatch::~ServiceProbeMatch() {
|
||||
std::vector<char *>::iterator it;
|
||||
if (!isInitialized) return;
|
||||
if (servicename) free(servicename);
|
||||
if (matchstr) free(matchstr);
|
||||
@@ -279,6 +283,8 @@ ServiceProbeMatch::~ServiceProbeMatch() {
|
||||
if (hostname_template) free(hostname_template);
|
||||
if (ostype_template) free(ostype_template);
|
||||
if (devicetype_template) free(devicetype_template);
|
||||
for (it = cpe_templates.begin(); it != cpe_templates.end(); it++)
|
||||
free(*it);
|
||||
matchstrlen = 0;
|
||||
if (regex_compiled) pcre_free(regex_compiled);
|
||||
if (regex_extra) pcre_free(regex_extra);
|
||||
@@ -286,6 +292,83 @@ ServiceProbeMatch::~ServiceProbeMatch() {
|
||||
matchops_anchor = -1;
|
||||
}
|
||||
|
||||
/* Make a new allocated null-terminated string from the bytes [start, end). */
|
||||
static char *mkstr(const char *start, const char *end)
|
||||
{
|
||||
char *s;
|
||||
|
||||
assert(end >= start);
|
||||
s = (char *) safe_malloc(end - start + 1);
|
||||
memcpy(s, start, end - start);
|
||||
s[end - start] = '\0';
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Realloc a malloc-allocated string and put a given prefix at the front. */
|
||||
static char *string_prefix(char *string, const char *prefix)
|
||||
{
|
||||
size_t slen, plen;
|
||||
|
||||
slen = strlen(string);
|
||||
plen = strlen(prefix);
|
||||
string = (char *) safe_realloc(string, plen + slen + 1);
|
||||
memmove(string + plen, string, slen + 1);
|
||||
memmove(string, prefix, plen);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
/* Read the next tmplt from *matchtext and update *matchtext. Return true iff
|
||||
a template was read. For example, after
|
||||
matchtext = "p/123/ d/456/";
|
||||
next_template(&matchtext, &modestr, &flags, &tmplt);
|
||||
then
|
||||
matchtext == " d/456/"
|
||||
modestr == "p"
|
||||
tmplt == "123"
|
||||
flags == ""
|
||||
*modestr and *tmplt must be freed if the return value is true. */
|
||||
static bool next_template(const char **matchtext, char **modestr, char **tmplt,
|
||||
char **flags, int lineno) {
|
||||
const char *p, *q;
|
||||
char delimchar;
|
||||
|
||||
p = *matchtext;
|
||||
while(isspace((int) (unsigned char) *p))
|
||||
p++;
|
||||
if (*p == '\0')
|
||||
return false;
|
||||
|
||||
q = p;
|
||||
while (isalpha(*q) || *q == ':')
|
||||
q++;
|
||||
if (*q == '\0' || isspace(*q))
|
||||
fatal("%s: parse error on line %d of nmap-service-probes", __func__, lineno);
|
||||
|
||||
*modestr = mkstr(p, q);
|
||||
|
||||
delimchar = *q;
|
||||
p = q + 1;
|
||||
|
||||
q = strchr(p, delimchar);
|
||||
if (q == NULL)
|
||||
fatal("%s: parse error on line %d of nmap-service-probes", __func__, lineno);
|
||||
|
||||
*tmplt = mkstr(p, q);
|
||||
p = q + 1;
|
||||
|
||||
q = p;
|
||||
while (isalpha(*q))
|
||||
q++;
|
||||
*flags = mkstr(p, q);
|
||||
|
||||
/* Update pointer for caller. */
|
||||
*matchtext = q;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// match text from the nmap-service-probes file. This must be called
|
||||
// before you try and do anything with this match. This function
|
||||
// should be passed the whole line starting with "match" or
|
||||
@@ -294,12 +377,10 @@ ServiceProbeMatch::~ServiceProbeMatch() {
|
||||
// function will abort the program if there is a syntax problem.
|
||||
void ServiceProbeMatch::InitMatch(const char *matchtext, int lineno) {
|
||||
const char *p;
|
||||
char *tmptemplate;
|
||||
char delimchar, modechar;
|
||||
char *modestr, *tmptemplate, *flags;
|
||||
int pcre_compile_ops = 0;
|
||||
const char *pcre_errptr = NULL;
|
||||
int pcre_erroffset = 0;
|
||||
unsigned int tmpbuflen = 0;
|
||||
char **curr_tmp = NULL;
|
||||
|
||||
if (isInitialized) fatal("Sorry ... %s does not yet support reinitializion", __func__);
|
||||
@@ -336,103 +417,80 @@ void ServiceProbeMatch::InitMatch(const char *matchtext, int lineno) {
|
||||
// options. ('i' means "case insensitive", 's' means that . matches
|
||||
// newlines (both are just as in perl)
|
||||
matchtext = p;
|
||||
while(isspace((int) (unsigned char) *matchtext)) matchtext++;
|
||||
if (*matchtext == 'm') {
|
||||
if (!*(matchtext+1))
|
||||
fatal("%s: parse error on line %d of nmap-service-probes: matchtext must begin with 'm'", __func__, lineno);
|
||||
matchtype = SERVICEMATCH_REGEX;
|
||||
delimchar = *(++matchtext);
|
||||
++matchtext;
|
||||
// find the end of the regex
|
||||
p = strchr(matchtext, delimchar);
|
||||
if (!p) fatal("%s: parse error on line %d of nmap-service-probes: could not find end delimiter for regex", __func__, lineno);
|
||||
matchstrlen = p - matchtext;
|
||||
matchstr = (char *) safe_malloc(matchstrlen + 1);
|
||||
memcpy(matchstr, matchtext, matchstrlen);
|
||||
matchstr[matchstrlen] = '\0';
|
||||
if (!next_template(&matchtext, &modestr, &matchstr, &flags, lineno))
|
||||
fatal("%s: parse error on line %d of nmap-service-probes", __func__, lineno);
|
||||
|
||||
matchtext = p + 1; // skip past the delim
|
||||
// any options?
|
||||
while(*matchtext && !isspace((int) (unsigned char) *matchtext)) {
|
||||
if (*matchtext == 'i')
|
||||
matchops_ignorecase = true;
|
||||
else if (*matchtext == 's')
|
||||
matchops_dotall = true;
|
||||
else fatal("%s: illegal regexp option on line %d of nmap-service-probes", __func__, lineno);
|
||||
matchtext++;
|
||||
}
|
||||
if (strcmp(modestr, "m") != 0)
|
||||
fatal("%s: parse error on line %d of nmap-service-probes: matchtext must begin with 'm'", __func__, lineno);
|
||||
matchtype = SERVICEMATCH_REGEX;
|
||||
|
||||
// Next we compile and study the regular expression to match
|
||||
if (matchops_ignorecase)
|
||||
pcre_compile_ops |= PCRE_CASELESS;
|
||||
|
||||
if (matchops_dotall)
|
||||
pcre_compile_ops |= PCRE_DOTALL;
|
||||
|
||||
regex_compiled = pcre_compile(matchstr, pcre_compile_ops, &pcre_errptr,
|
||||
&pcre_erroffset, NULL);
|
||||
|
||||
if (regex_compiled == NULL)
|
||||
fatal("%s: illegal regexp on line %d of nmap-service-probes (at regexp offset %d): %s\n", __func__, lineno, pcre_erroffset, pcre_errptr);
|
||||
|
||||
|
||||
// Now study the regexp for greater efficiency
|
||||
regex_extra = pcre_study(regex_compiled, 0, &pcre_errptr);
|
||||
if (pcre_errptr != NULL)
|
||||
fatal("%s: failed to pcre_study regexp on line %d of nmap-service-probes: %s\n", __func__, lineno, pcre_errptr);
|
||||
} else {
|
||||
/* Invalid matchtext */
|
||||
fatal("%s: parse error on line %d of nmap-service-probes: match string must begin with 'm'", __func__, lineno);
|
||||
// any options?
|
||||
for (p = flags; *p != '\0'; p++) {
|
||||
if (*p == 'i')
|
||||
matchops_ignorecase = true;
|
||||
else if (*p == 's')
|
||||
matchops_dotall = true;
|
||||
else
|
||||
fatal("%s: illegal regexp option on line %d of nmap-service-probes", __func__, lineno);
|
||||
}
|
||||
|
||||
// Next we compile and study the regular expression to match
|
||||
if (matchops_ignorecase)
|
||||
pcre_compile_ops |= PCRE_CASELESS;
|
||||
|
||||
if (matchops_dotall)
|
||||
pcre_compile_ops |= PCRE_DOTALL;
|
||||
|
||||
regex_compiled = pcre_compile(matchstr, pcre_compile_ops, &pcre_errptr,
|
||||
&pcre_erroffset, NULL);
|
||||
|
||||
if (regex_compiled == NULL)
|
||||
fatal("%s: illegal regexp on line %d of nmap-service-probes (at regexp offset %d): %s\n", __func__, lineno, pcre_erroffset, pcre_errptr);
|
||||
|
||||
// Now study the regexp for greater efficiency
|
||||
regex_extra = pcre_study(regex_compiled, 0, &pcre_errptr);
|
||||
if (pcre_errptr != NULL)
|
||||
fatal("%s: failed to pcre_study regexp on line %d of nmap-service-probes: %s\n", __func__, lineno, pcre_errptr);
|
||||
|
||||
free(modestr);
|
||||
free(flags);
|
||||
|
||||
/* OK! Now we look for any templates of the form ?/.../
|
||||
* where ? is either p, v, i, h, o, or d. / is any
|
||||
* delimiter character and ... is a template */
|
||||
|
||||
while(1) {
|
||||
while(isspace((int) (unsigned char) *matchtext)) matchtext++;
|
||||
if (*matchtext == '\0' || *matchtext == '\r' || *matchtext == '\n') break;
|
||||
while (next_template(&matchtext, &modestr, &tmptemplate, &flags, lineno)) {
|
||||
if (strcmp(modestr, "p") == 0)
|
||||
curr_tmp = &product_template;
|
||||
else if (strcmp(modestr, "v") == 0)
|
||||
curr_tmp = &version_template;
|
||||
else if (strcmp(modestr, "i") == 0)
|
||||
curr_tmp = &info_template;
|
||||
else if (strcmp(modestr, "h") == 0)
|
||||
curr_tmp = &hostname_template;
|
||||
else if (strcmp(modestr, "o") == 0)
|
||||
curr_tmp = &ostype_template;
|
||||
else if (strcmp(modestr, "d") == 0)
|
||||
curr_tmp = &devicetype_template;
|
||||
else if (strcmp(modestr, "cpe:") == 0) {
|
||||
tmptemplate = string_prefix(tmptemplate, "cpe:/");
|
||||
cpe_templates.push_back(NULL);
|
||||
curr_tmp = &cpe_templates.back();
|
||||
} else
|
||||
fatal("%s: Unknown template specifier '%s' on line %d of nmap-service-probes", __func__, modestr, lineno);
|
||||
|
||||
modechar = *(matchtext++);
|
||||
if (*matchtext == 0 || *matchtext == '\r' || *matchtext == '\n')
|
||||
fatal("%s: parse error on line %d of nmap-service-probes", __func__, lineno);
|
||||
|
||||
delimchar = *(matchtext++);
|
||||
|
||||
p = strchr(matchtext, delimchar);
|
||||
if (!p) fatal("%s: parse error on line %d of nmap-service-probes", __func__, lineno);
|
||||
|
||||
tmptemplate = NULL;
|
||||
tmpbuflen = p - matchtext;
|
||||
if (tmpbuflen > 0) {
|
||||
tmptemplate = (char *) safe_malloc(tmpbuflen + 1);
|
||||
memcpy(tmptemplate, matchtext, tmpbuflen);
|
||||
tmptemplate[tmpbuflen] = '\0';
|
||||
}
|
||||
|
||||
switch(modechar){
|
||||
case 'p': curr_tmp = &product_template; break;
|
||||
case 'v': curr_tmp = &version_template; break;
|
||||
case 'i': curr_tmp = &info_template; break;
|
||||
case 'h': curr_tmp = &hostname_template; break;
|
||||
case 'o': curr_tmp = &ostype_template; break;
|
||||
case 'd': curr_tmp = &devicetype_template; break;
|
||||
default:
|
||||
fatal("%s: Unknown template specifier '%c' on line %d of nmap-service-probes", __func__, modechar, lineno);
|
||||
}
|
||||
if(*curr_tmp){
|
||||
if(o.debugging)
|
||||
error("WARNING: Template \"%c/%s/\" replaced with \"%c/%s/\" on line %d of nmap-service-probes",
|
||||
modechar,
|
||||
*curr_tmp,
|
||||
modechar,
|
||||
tmptemplate,
|
||||
lineno);
|
||||
free(*curr_tmp);
|
||||
/* This one already defined? */
|
||||
if (*curr_tmp) {
|
||||
if (o.debugging) {
|
||||
error("WARNING: Template \"%s/%s/\" replaced with \"%s/%s/\" on line %d of nmap-service-probes",
|
||||
modestr, *curr_tmp, modestr, tmptemplate, lineno);
|
||||
}
|
||||
*curr_tmp = tmptemplate;
|
||||
free(*curr_tmp);
|
||||
}
|
||||
|
||||
matchtext = p + 1;
|
||||
*curr_tmp = tmptemplate;
|
||||
free(modestr);
|
||||
free(flags);
|
||||
}
|
||||
|
||||
isInitialized = 1;
|
||||
@@ -455,6 +513,7 @@ const struct MatchDetails *ServiceProbeMatch::testMatch(const u8 *buf, int bufle
|
||||
static char hostname[80];
|
||||
static char ostype[32];
|
||||
static char devicetype[32];
|
||||
static char cpe_a[80], cpe_h[80], cpe_o[80];
|
||||
char *bufc = (char *) buf;
|
||||
int ovector[150]; // allows 50 substring matches (including the overall match)
|
||||
assert(isInitialized);
|
||||
@@ -480,13 +539,17 @@ const struct MatchDetails *ServiceProbeMatch::testMatch(const u8 *buf, int bufle
|
||||
// Yeah! Match apparently succeeded.
|
||||
// Now lets get the version number if available
|
||||
getVersionStr(buf, buflen, ovector, rc, product, sizeof(product), version, sizeof(version), info, sizeof(info),
|
||||
hostname, sizeof(hostname), ostype, sizeof(ostype), devicetype, sizeof(devicetype));
|
||||
hostname, sizeof(hostname), ostype, sizeof(ostype), devicetype, sizeof(devicetype),
|
||||
cpe_a, sizeof(cpe_a), cpe_h, sizeof(cpe_h), cpe_o, sizeof(cpe_o));
|
||||
if (*product) MD_return.product = product;
|
||||
if (*version) MD_return.version = version;
|
||||
if (*info) MD_return.info = info;
|
||||
if (*hostname) MD_return.hostname = hostname;
|
||||
if (*ostype) MD_return.ostype = ostype;
|
||||
if (*devicetype) MD_return.devicetype = devicetype;
|
||||
if (*cpe_a) MD_return.cpe_a = cpe_a;
|
||||
if (*cpe_h) MD_return.cpe_h = cpe_h;
|
||||
if (*cpe_o) MD_return.cpe_o = cpe_o;
|
||||
|
||||
MD_return.serviceName = servicename;
|
||||
MD_return.lineno = getLineNo();
|
||||
@@ -561,44 +624,103 @@ static int getsubstcommandargs(struct substargs *args, char *args_start,
|
||||
return args->num_args;
|
||||
}
|
||||
|
||||
// This function does the actual substitution of a placeholder like $2
|
||||
// or $P(4) into the given buffer. It returns the number of chars
|
||||
// written, or -1 if it fails. tmplvar is a template variable, such
|
||||
// as "$P(2)". We determine the appropriate string representing that,
|
||||
// and place it in newstr (as long as it doesn't exceed newstrlen).
|
||||
// We then set *tmplvarend to the character after the
|
||||
// variable. subject, subjectlen, ovector, and nummatches mean the
|
||||
// same as in dotmplsubst().
|
||||
static int substvar(char *tmplvar, char **tmplvarend, char *newstr,
|
||||
int newstrlen, const u8 *subject, int subjectlen, int *ovector,
|
||||
/* These three functions manage a growing string buffer, appended to at the end.
|
||||
Begin with strbuf_init, follow with any number of strbuf_append, and end with
|
||||
strbuf_finish. */
|
||||
static void strbuf_init(char **buf, size_t *n, size_t *len) {
|
||||
*buf = NULL;
|
||||
*n = 0;
|
||||
*len = 0;
|
||||
}
|
||||
|
||||
static void strbuf_append(char **buf, size_t *n, size_t *len,
|
||||
const char *from, size_t fromlen) {
|
||||
/* Double the size of the buffer if necessary. */
|
||||
if (*len == 0 || *len + fromlen > *n) {
|
||||
*n = (*len + fromlen) * 2;
|
||||
*buf = (char *) safe_realloc(*buf, *n + 1);
|
||||
}
|
||||
memcpy(*buf + *len, from, fromlen);
|
||||
*len += fromlen;
|
||||
}
|
||||
|
||||
/* Trim to length. (Also does initial allocation when *buf is empty.) */
|
||||
static void strbuf_finish(char **buf, size_t *n, size_t *len) {
|
||||
*buf = (char *) safe_realloc(*buf, *len + 1);
|
||||
(*buf)[*len] = '\0';
|
||||
}
|
||||
|
||||
/* Transform a string so that it is safe to insert into the middle of a CPE URL. */
|
||||
static char *transform_cpe(const char *s) {
|
||||
char *result;
|
||||
size_t n, len, repllen;
|
||||
const char *p;
|
||||
|
||||
strbuf_init(&result, &n, &len);
|
||||
for (p = s; *p != '\0'; p++) {
|
||||
const char *repl;
|
||||
char buf[32];
|
||||
|
||||
/* Section 5.4 of the CPE specification lists these characters to be
|
||||
escaped. */
|
||||
if (strchr(":/?#[]@!$&'()*+,;=%<>\"", *p) != NULL) {
|
||||
Snprintf(buf, sizeof(buf), "%%%02X", *p);
|
||||
repl = buf;
|
||||
/* Replacing spaces with underscores is also a convention. */
|
||||
} else if (*p == ' ') {
|
||||
repl = "_";
|
||||
/* Otherwise just make lower-case. */
|
||||
} else {
|
||||
buf[0] = tolower(*p);
|
||||
buf[1] = '\0';
|
||||
repl = buf;
|
||||
}
|
||||
|
||||
repllen = strlen(repl);
|
||||
strbuf_append(&result, &n, &len, repl, repllen);
|
||||
}
|
||||
strbuf_finish(&result, &n, &len);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// This function does the substitution of a placeholder like $2 or $P(4). It
|
||||
// returns a newly allocated string, or NULL if it fails. tmplvar is a template
|
||||
// variable, such as "$P(2)". We set *tmplvarend to the character after the
|
||||
// variable. subject, subjectlen, ovector, and nummatches mean the same as in
|
||||
// dotmplsubst().
|
||||
static char *substvar(char *tmplvar, char **tmplvarend,
|
||||
const u8 *subject, int subjectlen, int *ovector,
|
||||
int nummatches) {
|
||||
char substcommand[16];
|
||||
char *p = NULL;
|
||||
char *p_end;
|
||||
int len;
|
||||
int subnum = 0;
|
||||
int offstart, offend;
|
||||
int byteswritten = 0; // for return val
|
||||
int rc;
|
||||
int i;
|
||||
struct substargs command_args;
|
||||
char *result;
|
||||
size_t n, len;
|
||||
|
||||
// skip the '$'
|
||||
if (*tmplvar != '$') return -1;
|
||||
if (*tmplvar != '$') return NULL;
|
||||
tmplvar++;
|
||||
|
||||
if (!isdigit((int) (unsigned char) *tmplvar)) {
|
||||
int commandlen;
|
||||
/* This is a command like $P(1). */
|
||||
p = strchr(tmplvar, '(');
|
||||
if (!p) return -1;
|
||||
len = p - tmplvar;
|
||||
if (!len || len >= (int) sizeof(substcommand))
|
||||
return -1;
|
||||
memcpy(substcommand, tmplvar, len);
|
||||
substcommand[len] = '\0';
|
||||
if (!p) return NULL;
|
||||
commandlen = p - tmplvar;
|
||||
if (!commandlen || commandlen >= (int) sizeof(substcommand))
|
||||
return NULL;
|
||||
memcpy(substcommand, tmplvar, commandlen);
|
||||
substcommand[commandlen] = '\0';
|
||||
tmplvar = p+1;
|
||||
// Now we grab the arguments.
|
||||
rc = getsubstcommandargs(&command_args, tmplvar, &p_end);
|
||||
if (rc <= 0) return -1;
|
||||
if (rc <= 0) return NULL;
|
||||
tmplvar = p_end;
|
||||
} else {
|
||||
/* This is a placeholder like $2. */
|
||||
@@ -609,28 +731,25 @@ static int substvar(char *tmplvar, char **tmplvarend, char *newstr,
|
||||
|
||||
if (tmplvarend) *tmplvarend = tmplvar;
|
||||
|
||||
strbuf_init(&result, &n, &len);
|
||||
if (!*substcommand) {
|
||||
/* Handler for a placeholder like $2. */
|
||||
if (subnum > 9 || subnum <= 0) return -1;
|
||||
if (subnum >= nummatches) return -1;
|
||||
if (subnum > 9 || subnum <= 0) return NULL;
|
||||
if (subnum >= nummatches) return NULL;
|
||||
offstart = ovector[subnum * 2];
|
||||
offend = ovector[subnum * 2 + 1];
|
||||
assert(offstart >= 0 && offstart < subjectlen);
|
||||
assert(offend >= 0 && offend <= subjectlen);
|
||||
len = offend - offstart;
|
||||
// A plain-jane copy
|
||||
if (newstrlen <= len - 1)
|
||||
return -1;
|
||||
memcpy(newstr, subject + offstart, len);
|
||||
byteswritten = len;
|
||||
strbuf_append(&result, &n, &len, (const char *) subject + offstart, offend - offstart);
|
||||
} else if (strcmp(substcommand, "P") == 0) {
|
||||
if (command_args.num_args != 1 ||
|
||||
command_args.arg_types[0] != SUBSTARGS_ARGTYPE_INT) {
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
subnum = command_args.int_args[0];
|
||||
if (subnum > 9 || subnum <= 0) return -1;
|
||||
if (subnum >= nummatches) return -1;
|
||||
if (subnum > 9 || subnum <= 0) return NULL;
|
||||
if (subnum >= nummatches) return NULL;
|
||||
offstart = ovector[subnum * 2];
|
||||
offend = ovector[subnum * 2 + 1];
|
||||
assert(offstart >= 0 && offstart < subjectlen);
|
||||
@@ -638,12 +757,10 @@ static int substvar(char *tmplvar, char **tmplvarend, char *newstr,
|
||||
// This filter only includes printable characters. It is particularly
|
||||
// useful for collapsing unicode text that looks like
|
||||
// "W\0O\0R\0K\0G\0R\0O\0U\0P\0"
|
||||
for(i=offstart; i < offend; i++)
|
||||
if (isprint((int) subject[i])) {
|
||||
if (byteswritten >= newstrlen - 1)
|
||||
return -1;
|
||||
newstr[byteswritten++] = subject[i];
|
||||
}
|
||||
for(i=offstart; i < offend; i++) {
|
||||
if (isprint((int) subject[i]))
|
||||
strbuf_append(&result, &n, &len, (const char *) subject + i, 1);
|
||||
}
|
||||
} else if (strcmp(substcommand, "SUBST") == 0) {
|
||||
char *findstr, *replstr;
|
||||
int findstrlen, replstrlen;
|
||||
@@ -651,11 +768,11 @@ static int substvar(char *tmplvar, char **tmplvarend, char *newstr,
|
||||
command_args.arg_types[0] != SUBSTARGS_ARGTYPE_INT ||
|
||||
command_args.arg_types[1] != SUBSTARGS_ARGTYPE_STRING ||
|
||||
command_args.arg_types[2] != SUBSTARGS_ARGTYPE_STRING) {
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
subnum = command_args.int_args[0];
|
||||
if (subnum > 9 || subnum <= 0) return -1;
|
||||
if (subnum >= nummatches) return -1;
|
||||
if (subnum > 9 || subnum <= 0) return NULL;
|
||||
if (subnum >= nummatches) return NULL;
|
||||
offstart = ovector[subnum * 2];
|
||||
offend = ovector[subnum * 2 + 1];
|
||||
assert(offstart >= 0 && offstart < subjectlen);
|
||||
@@ -665,26 +782,19 @@ static int substvar(char *tmplvar, char **tmplvarend, char *newstr,
|
||||
replstr = command_args.str_args[2];
|
||||
replstrlen = command_args.str_args_len[2];
|
||||
for(i=offstart; i < offend; ) {
|
||||
if (byteswritten >= newstrlen - 1)
|
||||
return -1;
|
||||
if (offend - i < findstrlen)
|
||||
newstr[byteswritten++] = subject[i++]; // No room for match
|
||||
else if (memcmp(subject + i, findstr, findstrlen) != 0)
|
||||
newstr[byteswritten++] = subject[i++]; // no match
|
||||
else {
|
||||
if (memcmp(subject + i, findstr, findstrlen) != 0) {
|
||||
strbuf_append(&result, &n, &len, (const char *) subject + i, 1); // no match
|
||||
i++;
|
||||
} else {
|
||||
// The find string was found, copy it to newstring
|
||||
if (newstrlen - 1 - byteswritten < replstrlen)
|
||||
return -1;
|
||||
memcpy(newstr + byteswritten, replstr, replstrlen);
|
||||
byteswritten += replstrlen;
|
||||
strbuf_append(&result, &n, &len, replstr, replstrlen);
|
||||
i += findstrlen;
|
||||
}
|
||||
}
|
||||
} else return -1; // Unknown command
|
||||
} else return NULL; // Unknown command
|
||||
|
||||
if (byteswritten >= newstrlen) return -1;
|
||||
newstr[byteswritten] = '\0';
|
||||
return byteswritten;
|
||||
strbuf_finish(&result, &n, &len);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -695,13 +805,19 @@ static int substvar(char *tmplvar, char **tmplvarend, char *newstr,
|
||||
// matches in ovector. The NUL-terminated newly composted string is
|
||||
// placed into 'newstr', as long as it doesn't exceed 'newstrlen'
|
||||
// bytes. Trailing whitespace and commas are removed. Returns zero for success
|
||||
//
|
||||
// The transform argument is a function pointer. If not NULL, the given
|
||||
// function is applied to all substitutions before they are inserted
|
||||
// into the result string.
|
||||
static int dotmplsubst(const u8 *subject, int subjectlen,
|
||||
int *ovector, int nummatches, char *tmpl, char *newstr,
|
||||
int newstrlen) {
|
||||
int newstrlen,
|
||||
char *(*transform)(const char *) = NULL) {
|
||||
int newlen;
|
||||
char *srcstart=tmpl, *srcend;
|
||||
char *dst = newstr;
|
||||
char *newstrend = newstr + newstrlen; // Right after the final char
|
||||
char *subst;
|
||||
|
||||
if (!newstr || !tmpl) return -1;
|
||||
if (newstrlen < 3) return -1; // fuck this!
|
||||
@@ -733,9 +849,24 @@ static int dotmplsubst(const u8 *subject, int subjectlen,
|
||||
dst += newlen;
|
||||
}
|
||||
srcstart = srcend;
|
||||
newlen = substvar(srcstart, &srcend, dst, newstrend - dst, subject,
|
||||
subjectlen, ovector, nummatches);
|
||||
if (newlen == -1) return -1;
|
||||
subst = substvar(srcstart, &srcend, subject, subjectlen, ovector, nummatches);
|
||||
if (subst == NULL)
|
||||
return -1;
|
||||
/* Apply transformation if requested. */
|
||||
if (transform != NULL) {
|
||||
char *tmp = subst;
|
||||
subst = transform(subst);
|
||||
free(tmp);
|
||||
if (subst == NULL)
|
||||
return -1;
|
||||
}
|
||||
newlen = strlen(subst);
|
||||
if (dst + newlen >= newstrend - 1) {
|
||||
free(subst);
|
||||
return -1;
|
||||
}
|
||||
memcpy(dst, subst, newlen);
|
||||
free(subst);
|
||||
dst += newlen;
|
||||
srcstart = srcend;
|
||||
}
|
||||
@@ -754,7 +885,7 @@ static int dotmplsubst(const u8 *subject, int subjectlen,
|
||||
}
|
||||
|
||||
|
||||
// Use the six version templates and the match data included here
|
||||
// Use the version templates and the match data included here
|
||||
// to put the version info into the given strings, (as long as the sizes
|
||||
// are sufficient). Returns zero for success. If no template is available
|
||||
// for a string, that string will have zero length after the function
|
||||
@@ -764,7 +895,10 @@ int ServiceProbeMatch::getVersionStr(const u8 *subject, int subjectlen,
|
||||
int *ovector, int nummatches, char *product, int productlen,
|
||||
char *version, int versionlen, char *info, int infolen,
|
||||
char *hostname, int hostnamelen, char *ostype, int ostypelen,
|
||||
char *devicetype, int devicetypelen) {
|
||||
char *devicetype, int devicetypelen,
|
||||
char *cpe_a, int cpe_alen,
|
||||
char *cpe_h, int cpe_hlen,
|
||||
char *cpe_o, int cpe_olen) {
|
||||
|
||||
int rc;
|
||||
assert(productlen >= 0 && versionlen >= 0 && infolen >= 0 &&
|
||||
@@ -776,6 +910,9 @@ int ServiceProbeMatch::getVersionStr(const u8 *subject, int subjectlen,
|
||||
if (hostnamelen > 0) *hostname = '\0';
|
||||
if (ostypelen > 0) *ostype = '\0';
|
||||
if (devicetypelen > 0) *devicetype = '\0';
|
||||
if (cpe_alen > 0) *cpe_a = '\0';
|
||||
if (cpe_hlen > 0) *cpe_h = '\0';
|
||||
if (cpe_olen > 0) *cpe_o = '\0';
|
||||
int retval = 0;
|
||||
|
||||
// Now lets get this started! We begin with the product name
|
||||
@@ -845,6 +982,42 @@ int ServiceProbeMatch::getVersionStr(const u8 *subject, int subjectlen,
|
||||
}
|
||||
}
|
||||
|
||||
/* There may be multiple cpe templates. We peek at the first character and
|
||||
store in cpe_a, cpe_h, or cpe_o as appropriate. */
|
||||
for (unsigned int i = 0; i < cpe_templates.size(); i++) {
|
||||
char *cpe;
|
||||
int cpelen;
|
||||
int part;
|
||||
|
||||
part = cpe_get_part(cpe_templates[i]);
|
||||
switch (part) {
|
||||
case 'a':
|
||||
cpe = cpe_a;
|
||||
cpelen = cpe_alen;
|
||||
break;
|
||||
case 'h':
|
||||
cpe = cpe_h;
|
||||
cpelen = cpe_hlen;
|
||||
break;
|
||||
case 'o':
|
||||
cpe = cpe_o;
|
||||
cpelen = cpe_olen;
|
||||
break;
|
||||
default:
|
||||
error("Warning: ignoring cpe:// template with unknown part '%c' (0x%02X)",
|
||||
isprint(part) ? part : '.', part);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
rc = dotmplsubst(subject, subjectlen, ovector, nummatches, cpe_templates[i], cpe, cpelen, transform_cpe);
|
||||
if (rc != 0) {
|
||||
error("Warning: Servicescan failed to fill cpe_%c (subjectlen: %d, devicetypelen: %d). Too long? Match string was line %d: d/%s/", part, subjectlen, devicetypelen, deflineno,
|
||||
(devicetype_template)? devicetype_template : "");
|
||||
if (devicetypelen > 0) *devicetype = '\0';
|
||||
retval = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -1368,6 +1541,7 @@ ServiceNFO::ServiceNFO(AllProbes *newAP) {
|
||||
currentresplen = 0;
|
||||
product_matched[0] = version_matched[0] = extrainfo_matched[0] = '\0';
|
||||
hostname_matched[0] = ostype_matched[0] = devicetype_matched[0] = '\0';
|
||||
cpe_a_matched[0] = cpe_h_matched[0] = cpe_o_matched[0] = '\0';
|
||||
tunnel = SERVICE_TUNNEL_NONE;
|
||||
ssl_session = NULL;
|
||||
softMatchFound = false;
|
||||
@@ -1891,6 +2065,7 @@ static int scanThroughTunnel(nsock_pool nsp, nsock_iod nsi, ServiceGroup *SG,
|
||||
svc->probe_matched = NULL;
|
||||
svc->product_matched[0] = svc->version_matched[0] = svc->extrainfo_matched[0] = '\0';
|
||||
svc->hostname_matched[0] = svc->ostype_matched[0] = svc->devicetype_matched[0] = '\0';
|
||||
svc->cpe_a_matched[0] = svc->cpe_h_matched[0] = svc->cpe_o_matched[0] = '\0';
|
||||
svc->softMatchFound = false;
|
||||
svc->resetProbes(true);
|
||||
startNextProbe(nsp, nsi, SG, svc, true);
|
||||
@@ -2221,6 +2396,12 @@ static void servicescan_read_handler(nsock_pool nsp, nsock_event nse, void *myda
|
||||
Strncpy(svc->ostype_matched, MD->ostype, sizeof(svc->ostype_matched));
|
||||
if (MD->devicetype)
|
||||
Strncpy(svc->devicetype_matched, MD->devicetype, sizeof(svc->devicetype_matched));
|
||||
if (MD->cpe_a)
|
||||
Strncpy(svc->cpe_a_matched, MD->cpe_a, sizeof(svc->cpe_a_matched));
|
||||
if (MD->cpe_h)
|
||||
Strncpy(svc->cpe_h_matched, MD->cpe_h, sizeof(svc->cpe_h_matched));
|
||||
if (MD->cpe_o)
|
||||
Strncpy(svc->cpe_o_matched, MD->cpe_o, sizeof(svc->cpe_o_matched));
|
||||
svc->softMatchFound = MD->isSoft;
|
||||
if (!svc->softMatchFound) {
|
||||
// We might be able to continue scan through a tunnel protocol
|
||||
@@ -2365,11 +2546,15 @@ list<ServiceNFO *>::iterator svc;
|
||||
*(*svc)->hostname_matched? (*svc)->hostname_matched : NULL,
|
||||
*(*svc)->ostype_matched? (*svc)->ostype_matched : NULL,
|
||||
*(*svc)->devicetype_matched? (*svc)->devicetype_matched : NULL,
|
||||
*(*svc)->cpe_a_matched? (*svc)->cpe_a_matched : NULL,
|
||||
*(*svc)->cpe_h_matched? (*svc)->cpe_h_matched : NULL,
|
||||
*(*svc)->cpe_o_matched? (*svc)->cpe_o_matched : NULL,
|
||||
shouldWePrintFingerprint(*svc) ? (*svc)->getServiceFingerprint(NULL) : NULL);
|
||||
} else {
|
||||
(*svc)->target->ports.setServiceProbeResults((*svc)->portno, (*svc)->proto,
|
||||
(*svc)->probe_state, NULL,
|
||||
(*svc)->tunnel, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL,
|
||||
(*svc)->getServiceFingerprint(NULL));
|
||||
}
|
||||
}
|
||||
@@ -2415,7 +2600,8 @@ static void remove_excluded_ports(AllProbes *AP, ServiceGroup *SG) {
|
||||
PROBESTATE_EXCLUDED, NULL,
|
||||
SERVICE_TUNNEL_NONE,
|
||||
"Excluded from version scan", NULL,
|
||||
NULL, NULL, NULL, NULL, NULL);
|
||||
NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL);
|
||||
|
||||
SG->services_remaining.erase(i);
|
||||
SG->services_finished.push_back(svc);
|
||||
|
||||
@@ -137,6 +137,11 @@ struct MatchDetails {
|
||||
const char *hostname;
|
||||
const char *ostype;
|
||||
const char *devicetype;
|
||||
|
||||
// CPE identifiers for application, OS, and hardware type.
|
||||
const char *cpe_a;
|
||||
const char *cpe_o;
|
||||
const char *cpe_h;
|
||||
};
|
||||
|
||||
/********************** CLASSES ***********************************/
|
||||
@@ -190,6 +195,7 @@ class ServiceProbeMatch {
|
||||
char *hostname_template;
|
||||
char *ostype_template;
|
||||
char *devicetype_template;
|
||||
std::vector<char *> cpe_templates;
|
||||
// The anchor is for SERVICESCAN_STATIC matches. If the anchor is not -1, the match must
|
||||
// start at that zero-indexed position in the response str.
|
||||
int matchops_anchor;
|
||||
@@ -205,7 +211,10 @@ class ServiceProbeMatch {
|
||||
int nummatches, char *product, int productlen,
|
||||
char *version, int versionlen, char *info, int infolen,
|
||||
char *hostname, int hostnamelen, char *ostype, int ostypelen,
|
||||
char *devicetype, int devicetypelen);
|
||||
char *devicetype, int devicetypelen,
|
||||
char *cpe_a, int cpe_alen,
|
||||
char *cpe_h, int cpe_hlen,
|
||||
char *cpe_o, int cpe_olen);
|
||||
};
|
||||
|
||||
|
||||
|
||||
17
utils.cc
17
utils.cc
@@ -449,6 +449,23 @@ void bintohexstr(char *buf, int buflen, char *src, int srclen){
|
||||
bp += Snprintf(buf+bp, buflen-bp,"\n");
|
||||
}
|
||||
|
||||
/* Get the CPE part (first component of the URL, should be "a", "h", or "o") as
|
||||
a character: 'a', 'h', or 'o'. Returns -1 on error. */
|
||||
int cpe_get_part(const char *cpe) {
|
||||
const char *PREFIX = "cpe:/";
|
||||
char part;
|
||||
|
||||
if (strncmp(cpe, PREFIX, strlen(PREFIX) != 0))
|
||||
return -1;
|
||||
/* This could be more robust, by decoding character escapes and checking ':'
|
||||
boundaries. */
|
||||
part = cpe[strlen(PREFIX)];
|
||||
|
||||
if (part == 'a' || part == 'h' || part == 'o')
|
||||
return part;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* mmap() an entire file into the address space. Returns a pointer
|
||||
|
||||
4
utils.h
4
utils.h
@@ -199,6 +199,10 @@ void bintohexstr(char *buf, int buflen, char *src, int srclen);
|
||||
char *strerror(int errnum);
|
||||
#endif
|
||||
|
||||
/* Get the CPE part (first component of the URL, should be "a", "h", or "o") as
|
||||
a character: 'a', 'h', or 'o'. Returns -1 on error. */
|
||||
int cpe_get_part(const char *cpe);
|
||||
|
||||
/* mmap() an entire file into the address space. Returns a pointer
|
||||
to the beginning of the file. The mmap'ed length is returned
|
||||
inside the length parameter. If there is a problem, NULL is
|
||||
|
||||
Reference in New Issue
Block a user