Using the Network Appliance

The network security appliance provided within CGC, cb-proxy, is an application layer inspection and modification engine designed to sit between a client and Challenge Binary (CB). cb-proxy is capable of performing bi-directional content inspection and modification akin to a Network Intrusion Detection system, without the large learning curve of handling the communication underneath the application layer. Inspection and modification is performed via custom domain-specific-language similar in nature to the Snort rules language, with the ability to provide multiple rules for a CB.

Inspection Process

As a message is read, it is appended to a unique buffer for the side of the session being analyzed, then the buffer is inspected. The inspection iterates over all of the provided rules until none of the rules match the remaining buffer or the buffer is empty. When a rule successfully completes, the content inspected by the rule is removed from the buffer. A rule is successful if all of the inspection options evaluate successfully.

After the rule matching process is complete, the message is sent to the destination.

Rule Syntax

A rule is made up of a rule type, then rule options specified in within a parenthesis, within a single line. Rule options are specified as a keyword and value pair, with a unique syntax for the value depending on the keyword. A rule option is specified by the keyword, a colon, the value, and a semi-colon.

A basic rule looks like the following:

alert (name:"foo"; match:"bar";)

The supported rule types are:

  • alert
  • admit
  • block

An alert rule will log the content for analysis upon completion. An admit rule will pass the inspected content on to its destination without logging it. A block rule will cause prevent any further communication from occurring.

The supported rule options are:

  • name
  • match
  • skip
  • regex
  • side
  • state
  • flush

name

Each rule must have one and only one name option, which is used in associated logs generated by block and alert rules. The name option must come first in the set of options.

The syntax for a name option is an arbitrary string within double quotes.

An example name option is:

name:"This is a fancy rule name";

match

Each rule may have an arbitrary number of match options, which are used to perform string matching in the inspection buffer.

The syntax for a match value is a quoted string optionally followed by a comma and then a positive integer.
The quoted string must be made up of alphanumeric characters, space, or "\x" encoded 2 byte hex-encoded characters. The optional comma and positive integer indicate that the specified string must exist within the specified amount of data.

An example match option is:

match:"foo";

An example match option with the optional integer, is:

match:"bar", 3;

The first option would look for foo anywhere in the inspection buffer, and mark all content up to and including the first instance of foo as inspected upon identification of match. The second option would look for foo in the first 3 bytes of the inspection buffer, and the 3 bytes as inspected upon match.

skip

Each rule may have any number of skip options, which are used to skip over the specified amount of content in the inspection buffer. The skip option allows a rule to indicate content has been inspected without actually performing any inspection.

The syntax for a skip value is a positive integer.

An example skip option is:

skip:10;

Any content matched by the regex option is considered inspected, and removed the beginning of the inspection buffer.

replace

Each rule may have any number of replace options, which are used to modify the matched content prior to its sending to the other side. A replace option must occur immediately after a match option, and only one replace opton may occur for each match option.

The syntax for a replace option is a quoted string. The quoted string must be made up of alphanumeric characters, space, or "\x" encoded 2 byte hex-encoded characters. The quoted string, upon decoding, must be the same length as the preceeding match string.

An example replace option is:

replace:"foo";

The replace keyword is a best effort replacement. Only content that is modified by replace that is part of the message that triggered the current inspection will be modified as it is sent to the destination. Any modified content from previous messages will have already been sent to the destination, therefor the modification is unable to occur.

regex

Each rule may have any number of regex options, which are used to search for a specified string in the inspection buffer.

The syntax for a regex option is an arbitrary string within double quotes. The string must be of a valid RE2 syntax.

An example regex option is:

regex:"foo.*bar";

Any content matched by the regex option is considered inspected, and removed the beginning of the inspection buffer.

side

Each rule may have a side option, which allow a rule to be limited to inspecting content for a specific side of the session.

The syntax for a side value is either the string client or server, to indicate which side of the session the rule applies.

An example side option is:

side:server;

state

Each rule may have an arbitrary number state options, which allow for the creation of rudimentary state machines via setting, unsetting, and checking the state of an arbitrary number of named bits that are allocated per session.

The syntax for a state value is an operation, then a comma, then a string.

The operations are as follows:

  • set
  • unset
  • is
  • not

The set operation sets the specified bit to True. The unset operation sets the specified bit to False. The is operation only continues if the specified bit is True. The not operation only continues if the specified bit is False.

The string must be one or more characters that are alphanumeric or "_".

An example state option is:

state:set,foo;

Note, while states may change during the processing of a rule, the states are only saved if the rule completes successfully.
This means if a state is changed in the rule, but a later option in the rule fails, the values are not saved.

flush

Each rule may have one flush option, which empties the analysis buffer of the specified side of the communication. If a flush option is used, it must come last in the set of rule options.

The syntax for a flush option is either the string client or server, to indicate which buffer should be emptied.

An example flush option is:

flush:server;

Beyond Simple Inspection

Order Matters

As discussed previously, rules are processed iteratively until no additional content has been inspected. As such, the order the rules are defined matters.

Take the following set of rules:

alert (name:"rule 1"; match:"foo"; replace:"bar";)
block (name:"rule 2"; match:"foo";)

As is, rule 2 should never fire, as rule 1 will always replace the string "foo" with "bar".
However, if the rules were in the opposite order, the session would drop as soon as "foo" was seen.

Non-determinism in underlying communications

CBs within CGC communicate over TCP/IP, which does not guarantee data always arrives in the same size chunks as it is sent. While testing in isolation a CB may always work in the same way, but often systems tested at load may cause edge conditions to occur. One such issue, TCP segmentation, can be replicated in challenge binaries with the --max_send parameter to cb-test.

This issue impacts the efficacy of traditional packet based network security appliances. Traditional appliances perform reassembly at a packet layer, including attempting to handle out of order packets. cb-proxy, operating at the socket layer, does not have to deal with out of order packets, but it does have to deal with reading a non-deterministic amount of traffic at a time.

CB authors have no requirement to follow any known specification for messages, and as such cb-proxy must be flexible enough to handle arbitrary protocols.

cb-proxy handles these non-deterministic issues by using a per-connection and per side inspection buffer, with the ability to flush the buffers without inspection at any point. The original CADET_00001 challenge binary, the author received 128 bytes into a buffer that was only 64 bytes long.

At first glance, a rule that says if more than 64 bytes is sent could be used to stop a POV for CADET_00001 such as the following rule:

block (name:"too much data sent"; side:client; regex:".{65}";)

However, depending on TCP buffering at various locations, at the client and/or the server, could cause more than 64 bytes in a single transmit to not cause the POV to fire.

The server will always respond with upon receiving data.
Using this knowledge, we can know if the server has completed a receive early due to buffering.

As such, the only reliable way to detect a POV is to look for more than 64 bytes of data, but as soon as the server has sent data, flush the inspection buffer for the client.

block (name:"too much data sent"; side:client; regex:".{65}";)
admit (name:"whenever we see server data, flush the client stream"; side:server; regex:".*"; flush:client;)

Memory & CPU Consumption

There are some drawbacks to this buffering. There is a performance impact to the buffering. Data is always appended to the analysis buffer. The analysis buffer is always stored for the duration of the session until successful inspection of the data by a rule or a 'flush'.

If data is never inspected or flushed, then the analysis buffer could become large, consuming excessive computing resources.