Skip to content

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.

yaml
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:

protocoltransportrules see
tcptcpeach direction as one continuous stream
httptcprequests and responses, including h2c and WebSocket messages
ftptcpeach line of the control connection
udpudpeach datagram
dnsudpeach 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.

settingdefault
end_of_tickallowwhat happens to new connections and datagrams during the end-of-tick window: allow, drop or stall
intercepttruefalse publishes the port as the compose file says, with no proxy and no rules; applied at the next deploy
pool_upstreamtruereuse 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 ​

yaml
- 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: 404

Rules 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 match and not.
  • 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.