The portmapper Analyzer
Keywords: doc-html

Next: The analy Analyzer Up: Analyzers and Events Previous: The login Analyzer Contents Index
Subsections

- `portmapper` variables
- `portmapper` functions
- `portmapper` event handlers

#
The `portmapper` Analyzer

The `portmapper` analyzer monitors one particularly important form of remote procedure call (RPC) [RFC1831,RFC1832] traffic: the portmapper service, used to map between RPC program (and version) numbers and the TCP or UDP port on which the service runs for a particular host. For example, rstatd is an RPC service that provides ``remote host status monitoring'' so that a set of hosts can be informed when any of them reboots. rstatd has been assigned a standard RPC program number of 100002. To find out the corresponding TCP or UDP port on a given host, a remote host would usually first contact the portmapper RPC service running on the host and request the port corresponding to program 100002.

Table 7.5: Types of calls to the RPC portmapper service.
Call | Meaning
NULL | A do-nothing call typically provided by all RPC services.
GETPORT | Look up the port associated with a given RPC program.
SET | Add a new port mapping (or replace an existing mapping) for an RPC program.
UNSET | Remove a port mapping.
DUMP | Retrieve all of the RPC program mappings.
CALLIT | Both look up a program and then directly call it.

All in all, clients can make six different types of calls to the portmapper, as summarized in Table 7.20. Attackers often use GETPORT and DUMP to see whether a host may be running an RPC service vulnerable to a known exploit.

The analyzer uses a capture filter of ```port 111`'' (§ ), equivalent to ```tcp port 111 or udp port 111`'' (since the portmapper service ordinarily accepts calls using either TCP or UDP, both on port 111). It checks the different types of portmapper calls against policies expressed using a number of different variables.

Note: An important point not to overlook is that an attacker does not have to first call the portmapper service in order to call an RPC program. They might instead happen to know the port on which the service runs a priori, since for example it may generally run on the same port for a particular operating system; or they might scan the host's different TCP or UDP ports directly looking for a reply from the service. Thus, while portmapper monitoring proves very useful in detecting attacks, it does not provide comprehensive monitoring of attempts to exploit RPC services.

##
`portmapper` variables

The standard script provides the following redefinable variables:

  [``rpc_programs : table[count] of string``] Maps RPC program numbers to a string used to name the service. For example, the `[100002]` entry is mapped to `"rstatd"`.

  Default: a large list of RPC services.

  [``NFS_services : set of string``] Lists the names of those RPC services that correspond to Network File System (NFS) [RFC1094,RFC1813] services. This variable is provided because it is convenient to express policies specific to accessing NFS file systems.

  Default: the services mountd, nfs, pcnfsd, nlockmgr, rquotad, status.

  Deficiency: Bro's notion of NFS is currently confined to just knowledge of the existence of these services. It does not analyze the particulars of different NFS operations.

  [``RPC_okay : set[addr, addr, string]``] Indexed by the host providing a given service and then by the host accessing the service. If an entry is present, it means that the given access is allowed. For example, an entry of:

          [1.2.3.4, 5.6.7.8, "rstatd"]

  means that host `5.6.7.8` is allowed to access the rstatd service on host `1.2.3.4`.

  Default: empty.

  [``RPC_okay_nets : set[net]``] A set of networks allowed to make GETPORT requests without complaint. The notion behind providing this variable is that the listed networks are trusted. However, the trust doesn't extend beyond GETPORT to other portmapper requests, because GETPORT is the only portmapper operation used routinely by a set of hosts trusted by another set of hosts (but that don't belong to the same group, and hence are not issuing SET and UNSET calls).

  Default: empty.

  [``RPC_okay_services : set[string]``] A set of services for which GETPORT requests should not generate complaints. These might be services that are widely invoked and believed exploit-free, such as walld, though care should be taken with blithely assuming that a given service is indeed exploit-free.

  Note that, like for `RPC_okay_nets`, the trust does not extend beyond GETPORT, because it should be the only portmapper operation routinely invoked.

  Default: empty.

  [``NFS_world_servers : set[addr]``] A set of hosts that provide public access to an NFS file system, and thus should not have any of their NFS traffic flagged as possibly sensitive. (The presumption here is that such public servers have been carefully secured against any remote NFS operations.) An example of such a server might be one providing read-only access to a public database.

  Default: empty.

  [``RPC_dump_okay : set[addr, addr]``] Indexed first by the host requesting a portmapper dump, and second by the host from which it's requesting the dump. If an entry is present, then the dump operation is not flagged.

  Default: empty.

  [``any_RPC_okay : set[addr, string]``] Pairs of hosts and services for which any GETPORT access to the given service is allowed.

  `sun-rpc.mcast.net` Default:

          [NFS_world_servers, NFS_services],
          [sun-rpc.mcast.net, "ypserv"]

  The first of these allows access to any NFS service of any of the `NFS_world_servers`, using Bro's cross-product initialization feature (§ ). The second allows ypserv requests to the multicast address reserved for RPC multicasts.7.2

  [``suppress_pm_log : table[addr, string] of bool``] Do not generate real-time alerts for access by the given address for the given service. Note that unlike most Bro policy variables, this one is not `const` but is modified at run-time to add to it any host that invokes the walld RPC service, so that such access is only reported once for each host.

  Default: empty, but dynamic as discussed above.

##
`portmapper` functions

The standard script provides the following externally accessible functions:

  [``rpc_prog`(p: count): string``` ] Returns the name of the RPC program with the given number, if it's present in `rpc_programs`; otherwise returns the text `"unknown-`<`p`>`"`.

  [``pm_check_getport`(r: connection, prog: string): bool``` ] Checks a GETPORT request for the given program against the policy expressed by `RPC_okay_services`, `any_RPC_okay`, `RPC_okay`, and `RPC_okay_nets`, returning true if the request violates policy, false if it's allowed.

  [``pm_activity`(r: connection, log_it: bool)``` ] A bookkeeping function invoked when there's been portmapper activity on the given connection.

  The function records the connection via `record_connection`, unless it is a TCP connection (which will instead be recorded by `connection_finished`). If `log_it` is true then the function generates a real-time alert of the form:

  > rpc: `<`connection-id`>` `<`RPC-service`>` `<`r$addl`>`

  For example:

          972616255.679799 rpc: 65.174.102.21/832 > 182.7.9.47/portmapper
      	pm_getport: nfs -> 2049/udp

  However, it does not generate the alert if either the client host and service are present in `suppress_pm_log`, or if it already generated an alert in the past for the same client, server and service (to prevent alert cascades).

  [``pm_request`(r: connection, proc: string, addl: string, log_it: bool)``` ] Invoked when the given connection has made a portmapper request of some sort for the given RPC procedure `proc`. `addl` gives an annotation to add to the connection's `addl` field. If `log_it` is true, then connection should be logged; it will also be logged if the function determines that it is hot.

  The function first invokes `check_scan` and `check_hot` (with a mode of `CONN_ESTABLISHED`), unless `r` is a TCP connection, in which case these checks have already been made by `connection_established`. The function then adds `addl` to the connection's `addl` field, though if the field's length already exceeds 80 bytes, then it just tacks on `"..."` (unless already present). This last is necessary because Bro will sometimes see zillions of successive portmapper requests that all use the same connection ID, and these will each add to `addl` until it becomes unwieldy in size. Deficiency: Clearly, the byte limit of 80 should be adjustable.

  Finally, the function invokes `check_hot` with a mode of `CONN_FINISHED`, and `pm_activity` to finish up bookkeeping for the connection.

  No return value.

  [``pm_attempt`(r: connection, proc: string, status: count, addl: string, log_it: bool)``` ] Invoked when the given connection attempted to make a portmapper request of some sort, but the request failed or went unanswered. The arguments are the same as for `pm_request`, with the addition of `status`, which gives the RPC status code corresponding to why the attempt failed (see below).

  The function first invokes `check_scan` and `check_hot` (with a mode of `CONN_ATTEMPTED`), unless `r` is a TCP connection, in which case these checks have already been made by `connection_attempt`.

  Table 7.6: Types of RPC status codes.
  Status description | Meaning
  `"ok"` | The call succeeded.
  `"prog unavail"` | The call was for an RPC program that has not registered with the portmapper.
  `"mismatch"` | The call was for a version of the RPC program that has not registered with the portmapper.
  `"garbage args"` | The parameters in the call did not decode correctly.
  `"system err"` | A system error (such as out-of-memory) occurred when processing the call.
  `"timeout"` | No reply was received within 24 seconds of the request.
  `"auth error"` | The caller failed to authenticate to the server, or was not authorized to make the call.
  `"unknown"` | An unknown error occurred.

  The function then adds `addl` to the connection's `addl` field, along with a text description of the RPC status code, as given in Table 7.20.2.

  No return value.

##
`portmapper` event handlers

The standard script handles the following events:

  [``pm_request_null (r: connection)``] Invoked upon a successful portmapper request for the ``null'' procedure. The script invokes `pm_request` with `log_it=F`.

  [``pm_request_set (r: connection, m: pm_mapping, success: bool)``] Invoked upon a nominally successful portmapper request to set the portmapper binding `m`. The script invokes `pm_request` with `log_it=T`. `success` is true if the server honored the request, false otherwise; the script turns this into an annotation of `"ok"` or `"failed"`.

  The `pm_mapping` type (for `m`) has three fields, `program: count`, `version: count` and `p: port`, the port for the mapping of the given program and version. `pm_mapping`

  [``pm_request_unset (r: connection, m: pm_mapping, success: bool)``] Invoked upon a nominally successful portmapper request to remove a portmapper binding. The script invokes `pm_request` with `log_it=T`. `success` is true if the server honored the request, false otherwise; the script turns this into an annotation of `"ok"` or `"failed"`.

  [``pm_request_getport (r: connection, pr: pm_port_request, p: port)``] Invoked upon a successful portmapper request to look up a portmapper binding. `pr`, of type `pm_port_request`, has three fields: `program: count`, `version: count`, and `is_tcp: bool`, this last indicating whether the caller is request the TCP or UDP port, if the given program/version has mappings for both. The script invokes `pm_request` with `log_it` set according to the return value of `pm_check_getport` and an annotation of the mapping.

  [``pm_request_dump (r: connection, m: pm_mappings)``] Invoked upon a successful portmapper request to dump the portmapper bindings. The script invokes `pm_request` with `log_it=T` unless `RPC_dump_okay` indicates that the dump call is allowed. The script ignores `m`, which gives the mappings as a `table[count] of pm_mapping`, where the table index simply reflects the order in which the mappings were returned, starting with an index of 1. Deficiency: What the script should do, instead, is keep track of the mappings so that Bro can identify the service associated with connections for otherwise unknown ports.

  [``pm_request_callit (r: connection, pm_callit_request, p: port)``] Invoked upon a successful portmapper request to look up and call an RPC procedure. The script invokes `pm_request` with `log_it=T` unless the combination of the caller and the program are in `suppress_pm_log`. Finally, if the program called is walld, then the script adds the caller to `suppress_pm_log`.

  The `pm_callit_request` type has four fields: `pm_callit_request` `program: count`, `version: count`, `proc: count`, and `arg_size: count`. These reflect the procedure being looked up and called, and the size of the arguments being passed to it, respectively. Deficiency: Currently, the event engine does not do any analysis or refinement of the arguments passed to the procedure (such as making them available to the event handler) or the return value. `p` is the port value returned by the call.

  [``pm_attempt_null (r: connection, status: count)``] Invoked upon a failed portmapper request for the ``null'' procedure. `status` gives the reason for the failure. The script invokes `pm_attempt` with `log_it=T`.

  [``pm_attempt_set (r: connection, status: count, m: pm_mapping)``] Invoked upon a failed portmapper request to set the portmapper binding `m`. The script invokes `pm_attempt` with `log_it=T`.

  [``pm_attempt_unset (r: connection, status: count, m: pm_mapping)``] Invoked upon a failed portmapper request to remove a portmapper binding. The script invokes `pm_attempt` with `log_it=T`.

  [``pm_attempt_getport (r: connection, status: count, pr: pm_port_request)``] Invoked upon a failed portmapper request to look up a portmapper binding. `pr`, of type `pm_port_request`, has three fields: `program: count`, `version: count`, and `is_tcp: bool`, this last indicating whether the caller requested the TCP or UDP port. The script invokes `pm_attempt` with `log_it` set according to the return value of `pm_check_getport`.

  [``pm_attempt_dump (r: connection, status: count)``] Invoked upon a failed portmapper request to dump the portmapper bindings. The script invokes `pm_attempt` with `log_it=T` unless `RPC_dump_okay` indicates that the dump call is allowed.

  [``pm_attempt_callit (r: connection, status: count, pm_callit_request)``] Invoked upon a failed portmapper request to look up and call an RPC procedure. The script invokes `pm_attempt` with `log_it=T` unless the combination of the caller and the program are in `suppress_pm_log`. Finally, if the program called is walld, then the script adds the caller to `suppress_pm_log`.

  [``pm_bad_port (r: connection, bad_p: count)``] Invoked when a portmapper request or response includes an invalid port number. Since ports are represented by unsigned 4-byte integers, they can stray outside the allowed range of 0-65535 by being 65536. The script invokes `conn_weird_addl` with a weird tag of `"bad_pm_port"`.

Next: The analy Analyzer Up: Analyzers and Events Previous: The login Analyzer Contents Index