Writing Your First Plugin
This tutorial will try to avoid assuming the reader is already familiar with any particular programming language, although some degree of coding knowledge is assumed.
-
Install Rust
Before writing a Rust-based plugin, we’ll need the Rust toolchain available. You can skip this step if it’s already available in your environment. Full installation instructions cover additional details and environments, but for most *nix based systems this is all you need:
If you prefer to avoid shell pipe installs, there are signed standalone installers.
-
Install Bulwark
Bulwark has its own build command, which we’ll need to build our plugin. Once the Rust toolchain is installed, we can obtain the Bulwark CLI via Cargo, which is included with the toolchain. Again, you can skip this step if the CLI is already installed.
-
Blank Slate Template
We’re going to start our plugin from a “blank slate” example. The full example can be obtained from the Bulwark repository by cloning it. You will need git installed if it isn’t already. We’re going to be writing a simple plugin that treats excessively long
Content-Type
headers as suspicious. A number of historical exploit payloads have used this header and typically require much longer values than a legitimate client would send. A length check thus becomes a very simple form of anomaly detection.This is the starting point for our plugin. All of the handlers we might use already have boilerplate we can start from. In this case, we’re going to delete each handler we don’t intend to use. In the code below,
HttpHandlers
is the Rust trait (essentially an interface) that is required for each Bulwark plugin. We use a special macro,#[bulwark_plugin]
, that makes it so that we don’t need to implement every member of the trait and we can just focus on the main functionality of our plugin.We’re only going to use the
handle_request_decision
function in this plugin, so we’ll open the file in our preferred editor, and delete the rest. -
Update Plugin Name
We can also update the plugin name and the tag name we’re going to use.
-
Read Header Value
Next we need to get the value of the
Content-Type
header, if any. If there’s noContent-Type
header, our plugin will keep the default verdict, which is uncertainty. -
Check Header Length
Now we’re going to check the length of the header and render our decision based on it. There isn’t a strict limit on length given in the specification for the
Content-Type
header. However, clients tend to be fairly consistent in what they send, favoring shorter values. We’re going to capitalize on this for our simple detection.We’ll start by counting how many extra characters we’ve found in the header value above the arbitrary limit we’ve set. The longest legitimate
Content-Type
values in common usage are around 100 characters, typically some flavor ofmultipart/form-data
. The shortest viable exploit payloads we have in mind are right around 150 characters, so we’re going to pick a number in between. -
Write Unit Test
Let’s also introduce our first unit test. We want to check fairly normal cases, legitimate edge cases, and malicious scenarios on opposite ends of the range we’ve defined.
-
Score For Decision
The last step is to take the excess we calculated in the previous step and turn it into a decision value. Bulwark uses decision values to render its verdict on whether a request should be blocked or not. Crucially, it encodes uncertainty into these values. In our example, there’s nothing that forbids a client from sending a long
Content-Type
. It’s not a guarantee of malicious behavior and to control false positives, we don’t want to treat it as malicious until the evidence is very strong.We’re going to generate a score value in the range 0.0 to 0.75 to represent our confidence that the request is malicious. We stop at 0.75 because 1.0 would indicate certainty and we want to retain some of our uncertainty based on our detection method. We stay below 0.8 because that’s the default threshold to block a request and we want multiple detections to contribute to a verdict before we block outright. We want to scale linearly and hit the maximum value when a request goes over by 150 characters. We know that exploits will go far past this secondary limit, as seen in the final test case from the previous change we made. That will make this detection harder to bypass. We can calculate our scaling factor by dividing 0.75 by 150 to get 0.005. Add in a simple clamp for the maximum score and that gives us our scoring function.
-
Adding A Dependency
For testing, we’re going to introduce the
approx
crate as adev-dependency
to simplify working with floating point score values. -
Wrapping Up
Our final detection is difficult to bypass while retaining a broadly operational exploit, addresses false positive risks, gives us additional traffic visibility by tagging offending requests in our logs, and has an embedded test suite that gives us confidence that we’ve implemented our logic correctly.
The finished version of this plugin is available in the community ruleset.