Rules
Every service has a vulnboxd-rules.yml in its directory, created the first time vulnboxd sees the service. It lists the service's public ports and the rules that apply to their traffic. Save it and the new rules are live within a second, on open connections too.
rules: # for every port of this service
- name: reverse shells
match: { regex: '/bin/(ba)?sh' }
ports:
"8080":
protocol: http
rules:
- name: path traversal
match: { path: '\.\./' }
set_status: 404
- name: watch logins
action: log
match: { method: POST, path: '^/login' }
"53/udp":
protocol: dns
rules:
- match: { qtype: TXT }Every key, with where it may appear, is in the rules file reference.
Ports
Ports are keyed by their published port number, as a string: "8080" for tcp, "53/udp" for udp. The generated file already has one entry per public port, with a comment naming the compose service and container port behind it.
Each port has a protocol, which decides what its rules can look at:
| protocol | transport | rules see |
|---|---|---|
tcp | tcp | each direction as one continuous stream |
http | tcp | requests and responses, including h2c and WebSocket messages |
ftp | tcp | each line of the control connection |
udp | udp | each datagram |
dns | udp | each datagram, plus the query name and type |
Ports start out as tcp or udp. Whether a port speaks http, ftp or dns is for you to say, since guessing wrong would break it. See Protocols for the details of each.
Settings
These can be set for the whole service at the top of the file, and for a single port under it. A port that sets one itself wins.
| setting | default | |
|---|---|---|
end_of_tick | allow | what happens to new connections and datagrams during the end-of-tick window: allow, drop or stall |
intercept | true | false publishes the port as the compose file says, with no proxy and no rules; applied at the next deploy |
pool_upstream | true | reuse connections to the service between clients (http only) |
One more setting only goes at the top of the file: bypass, headers that let your team's http requests skip every rule, for testing your own exploits.
Anatomy of a rule
- name: traversal # shown in the logs and metrics
dir: in # in (to the service, the default), out (to the client) or both
match: # all of these must hold; leave it out to match everything
path: '\.\./|%2e%2e'
not: # the rule is skipped when all of these hold
path: '^/static/'
action: block # block, replace or log
set_status: 404Rules under rules: at the top apply to every port; rules under a port apply to that port only. A service-wide rule that uses conditions of one protocol, such as path, only applies to the ports of that protocol. Under a port, such a rule is an error if the port speaks something else.
Rules are evaluated in order. The first rule that blocks ends the evaluation. A later rule sees what an earlier rule rewrote.
- Matching covers everything that can go under
matchandnot. - Actions covers what a rule does once it matches.
Log first, then block
A rule that matches more than you meant blocks the checker and costs you the service. Start every new rule as action: log, watch what it catches in vulnboxctl top, and only then let it block. Changing rules during a game has the steps.
When the file is wrong
If the file does not parse, or a rule does not compile, the error is logged and the previous rules stay active. Nothing is dropped and nothing is let through that was blocked before.
Unknown keys are errors rather than being ignored, so a typo such as macth: is caught instead of silently turning a rule into one that matches everything.