Basic INSPECT Syntax

This section, by presenting simple examples, provides a rudimentary description of how to read and understand INSPECT code.

INSPECT is a fairly simple script-like language that contains a series of statements designed to evaluate packets and make decisions about what to do with the packet. Although this is not a complete BNF[1] for the language, it should give you a rough idea of what to expect.

[1] BNF is an acronym for Backus Normal Form or Backus Naur Form. It is a standard format used to describe programming languages, first introduced in the Algol-60 report by John Backus and Peter Naur. Computer science luminary Donald Knuth recommended that Backus Normal Form should be renamed Backus Naur Form to honor the two authors of the Algol-60 report. Whatever it's called, I always thought it was a way to torture unsuspecting computer science students.

inspect script  ::= { <statement> }

statement       ::= <scope> <condition> { "," <condition> |
                    "and" <condition> | "xor" <condition> |
                    "or" <condition> }  ";"

scope           ::= <direction> "@" <host>

direction       ::= "<" | ">" | "<>" | "" | all | <interface name>

condition       ::= <short circuit> | <table command> |
                    <identifier> { <operand> <identifier> }  |
                    <function>

operand         ::= + | - | / | * | % | "|" | ^ | "==" | "!=" | ">" |
                    "<" | "<=" | ">="

short circuit   ::= accept | reject | drop | vanish

table command   ::= get <table entry> from <table name> |
                    delete <table entry> from <table name> |
                    modify <table entry> in <table name> |
                    record <table entry> in <table name> |
                    get <table entry> to <register name> |
                    <table entry> in <table name>

Basically, statements are a series of conditions separated by ANDs (signified by a comma or the keyword and) and ORs (signified by the keyword or). There is very limited ability for indirection with functions. There are no loops or recursion in INSPECT. What you see is pretty much what you get with INSPECT.

Each statement is processed in order until the operation either reaches the end of the script (which means INSPECT will drop the packet) or hits a short circuit as defined earlier (accept, reject, drop, and vanish). Descriptions of these short circuits appear in Table 14.1.

Table 14.1. Short circuits in INSPECT

Action

Description

accept

Accepts the packet (allows it to pass)

reject

Drops the packet loudly (i.e., sends a TCP RST or ICMP Unreachable message)

drop

Drops the packet silently

vanish

Drops the packet silently and leaves no trace

The difference between drop and vanish involves how FireWall-1 handles packets received for established TCP packets (i.e., have the ACK flag set but not the SYN flag) for which FireWall-1 holds no state information. The drop action doesn't really "drop" the packet but mangles the packet and passes it through the firewall. All the data in the packet is removed, leaving only the IP and TCP headers. This effectively makes the packet harmless. The TCP sequence numbers of the packet are also changed. When the destination host receives the mangled packet, it should immediately respond due to the mangled sequence number. If the TCP connection is still valid, this response will be matched by the security policy and the connection will be re-recorded in the connections table. If the connection has become invalid, the host will send back a TCP RST packet. FireWall-1 notices that the host drops a reply to a mangled TCP packet and therefore does not mangle it again but rather drops it for good.

Prior to FireWall-1 4.1 SP2, you could send unsolicited ACK packets through FireWall-1 and cause a denial-of-service attack against hosts behind the firewall (the ACK DoS attack). This was because FireWall-1 used drop when processing unestablished TCP packets. In FireWall-1 4.1 SP2 and later, vanish is used on these packets. This does not do the packet mangling that drop performs; it simply causes the packet to disappear without a trace. For earlier versions of FireWall-1, there is an ACK DoS fix you can apply that resolves this issue.

Consider the following statement (note that a and b are generic conditions):

a, b, accept;

This line tells FireWall-1 to accept the packet if conditions a and b are both true. You can also write the statement as:

accept a, b;

This means the same thing (accept if a and b are both true). Note that drop, reject, and vanish can also be used in this manner.

In both statements, if a is found to be false, b is never evaluated. This is a very important concept to understand, which also applies to or statements (e.g., in the statement a or b, if a proves to be true, b is never evaluated). In a long, complex statement, you can essentially "skip the rest" if part of the statement is false. For example, if a is false in the following statement, none of the other statements are evaluated:

a, b, c, d, e, f, accept;

You can also combine ANDs and ORs. The following statement:

a, b or c, d or e, f, accept;

is evaluated as if it were (logically, in pseudocode in C):

if ( a AND ( b OR c ) AND ( d OR e ) AND f ) { accept ; }

Note that you can force operator precedence with parentheses. You can also do something like this:

a, b or reject, accept;

This gives FireWall-1 the following instructions: If a and b are true, accept the packet (reject is skipped in this case). If a is false, simply go to the next statement. If a is true and b is false, reject the packet. Note that this is different than:

(a, b) or reject, accept;

which would reject the packet if either a or b were false.

Conditions

In the previous example, a single letter (e.g., a) was used to represent a generic condition. An actual condition looks more like this:

x = y
x != y
x > y
x < y

This script evaluates true if x equals y, if x does not equal y, if x is greater than y, or if x is less than y. You can also see if something is in a table (see the Manipulating Table Entries subsection):

<ip_src, ip_dst, sport> in http_table

Note that you can negate a particular statement by using not and parentheses. For example:

not (<ip_src, ip_dst, sport> in http_table)

You can also AND (&), OR (|), or XOR (^) two values together in a bitwise fashion to determine their value or use arithmetic functions (+, -, *, and /). In addition, you can bitshift values left and right with << or >>.

You can see some of the parts of a packet by looking at its predefined parts in $FWDIR/lib/tcpip.def, which includes information from IP, TCP, and UDP headers. INSPECT also defines several identifiers, which you can use as well. Table 14.2 documents some of these identifiers.

Table 14.2. Predefined identifiers

Identifier

Description

src

Source IP address

sport

Source TCP/UDP port

dst

Destination IP address

dport

Destination TCP/UDP port

ip_p

IP protocol number

tcp

True if current packet is TCP (same as ip_p = 6)

udp

True if current packet is UDP (same as ip_p = 17)

icmp

True if current packet is ICMP (same as ip_p = 1)

tod

Time of day (in 24-hour hh:mm:ss format)

date

Current system date (in dd mmm format)

day

Day of the week (using standard three-letter abbreviations)

You can also specify which parts of the packet you would like to look at in chunks of up to 4 bytes at a time. For example, if you would like to see if the 50th through 53rd bytes of the packet contain the command GET with a space character after it, you would do the comparison like this:

[50, b] = 0x47455420 // ASCII for "GET "

The ", b" portion of the line means to read the data in big-endian format and is generally needed if you want to look at TCP/IP packets more than 1 byte at a time. By default, 4 bytes are captured. If you want to look at only the 50th byte of the packet, you would use this:

[50:1, b] = 0x47 // ASCII for "G"

Constants

FireWall-1 interprets numbers in octal, hexadecimal, and decimal formats. A number beginning with a 0 is always evaluated as an octal value. Hexadecimal numbers begin with 0x. All other numbers are treated as decimals.

Time periods can be represented in hh:mm:ss format. For example, the format for 11:59 P.M. and 30 seconds is 23:59:30. Dates are represented in dd mmm format. For example, June 20 is represented as 20 Jun. Each of the standard three-letter abbreviations for days of the week (i.e., sun, mon, tue, wed, thu, fri, sat) is a constant that refers to the specific day of the week.

INSPECT also handles IP addresses entered in their dotted-quad notation (e.g., 192.168.42.69) as well as their physical interface name (e.g., eth-s1p1c0, le0, el59x1) and domain names (e.g., .phoneboy.com, .awl.com). Note the leading dot (.) on domain names.

Registers

Registers are the closest elements INSPECT has to variables. Check Point actually uses registers to modify certain internal functions, but you can use them for variables in your own code as well. They are of the format srN, where N is a number between 0 and 15. You set the value of a register as follows:

set sr1 15;

You can use the following code to accept a particular packet if the 60th byte is equal to 15:

accept sr1 = [60:1];

You can also use a register to specify an offset to a specific place in a packet. For example, you could do something like this:

sr1.[4:2, b]

which would give you bytes 19 and 20 in big-endian format (15 + 4 = 19).

Manipulating Table Entries

Tables allow FireWall-1 to remember various details about connections. Each entry in a table has the following format:

< KEY ; VALUE @ TIMEOUT >

where KEY and VALUE are one or more comma-separated items. TIMEOUT is an integer value that determines how long (in seconds) a particular entry will last in the table before expiring. For tables defined with the refresh tag (more on this in the next subsection), any read or write to a particular table entry resets this timeout. Note that only a KEY is required; the other parameters are optional. The TIMEOUT, if not specified, is taken from the default specified by the table. Consider this example:

<42,69;5@50>

The KEYs in this table are 42 and 69, the value is 5, and the entry has a timeout value of 50 seconds. If the entry is not accessed again during that time (if the table is defined with the refresh tag), the entry will expire and be removed from the table. If you want to add this entry to a table called foobar, you would use the following command:

record <42,69;5@50> in foobar;

If you want to verify whether <42,69> is in the table foobar, you would use this command:

<42,69> in foobar

which returns true if the entry is in the table, false if not. If you want to read the value stored in this table entry, you would use this command:

foobar [42,69]

You can also read the values into registers directly, which you must do if the value is a tuple. In this case, use the command get. For the following table entry in foobar:

<5,42,69;7,9,73>

use this command:

get <5,42,69> from foobar to sr1

This puts the values in successive registers (i.e., sr1 = 7, sr2 = 9, and sr3 = 73). Note that if this entry does not exist in foobar, the command returns false, and the registers maintain their previous values.

You can also simply verify that an entry exists in a table by checking to see if a particular key exists in the table. For instance:

<src,sport,dst,dport,ip_p> in connections

If the entry exists, the command returns true.

You can remove a particular entry from foobar by using the delete command:

delete <5,42,69> from foobar

You can also modify a particular table entry without resetting the timeout counter by using the modify command:

modify <5,42,69;5,20,71> in foobar

Creating Your Own Tables

Before you can start adding your own entries to tables, the tables must first be created. There are several predefined tables in $FWDIR/lib/table.def. Note that this technique does not define all the tables used in FireWall-1, but it does define tables that can be modified by the user's INSPECT code (such as the connections table).

There are actually two kinds of tables in FireWall-1: static and dynamic. Static tables are defined as follows:

name = static {} ;

In a static table, you can use your own name. This table, once defined, cannot be changed. A comma separates each entry in the table. For example:

authors = static { 10.0.0.1, 10.68.0.1, 10.31.0.1 } ;

If each entry in the table has more than one value, the group of entries is enclosed in angle brackets:

authors = static { <1, 10.0.0.1>, <2, 10.68.0.1>,
                   <3, 10.31.0.1> } ;

Dynamic tables are defined as follows:

name = dynamic {}  attributes;

The name variable represents the name of the table, dynamic tells FireWall-1 that this is a dynamic table, and attributes refers to one or more of the items listed in Table 14.3.

Table 14.3. Table attributes

Attribute

Description

expires N

When entries are added to the table without an explicit timeout, the entries are given a default timeout of N seconds.

refresh

This attribute resets the timeout value for an entry in the table when the entry is accessed.

hashsize N

This value should be set to a power of two roughly equal to the table size.

keep

All tables are flushed on a policy install except for those defined with a keep attribute.

kbuf N

The Nth argument in the value section is a pointer to an encryption key (used internally by FireWall-1 for encryption).

implies table_name

If an entry is removed from this table, also remove it from table_name if this attribute is used.

limit N

This attribute limits the number of entries in the table to N. The default is 25,000.

sync

When enabled, this attribute synchronizes the table with the State Synchronization mechanism.

NOTE!

graphics/brain_icon.gif

People who have dabbled in INSPECT code in FireWall-1 4.1 and earlier should note the keyword table is no longer used when defining a table. Using this keyword in NG causes a compilation error.