Skip to content

How to configure out-of-band testing

Some vulnerabilities never tell you they worked. Blind SSRF, XXE, out-of-band SQL injection and blind XSS all produce the same response as a target that is not vulnerable at all. The only way to observe them is to make the target call a server you control, and then watch that server.

That server is the out-of-band (OAST) backend.

The out-of-band hostname is embedded in the payload. It is therefore your target (not the scanner) that makes the callback.

The template scanner’s own default is a set of public servers operated by its vendor. Left alone, that means a machine inside your network resolves and connects to a third-party host, disclosing that the machine exists, that it is reachable, and that it processes attacker-supplied input. A packet capture of the scanner would show none of it, because the scanner is not the thing making the call.

BattleTest never uses that default in any mode. The image overrides it. This page is about which backend you get instead.

Set OAST_MODE in your deployment’s environment.

Mode Agent blind-vuln tools Template-scanner OAST templates DNS you must own
catcher (default) Yes No Nothing, or one wildcard A record
interactsh Yes Yes A whole domain, delegated
off No: reported as unavailable No Nothing

The deployment ships a small, self-hosted callback catcher (oob-catcher). It is receive-only, has no egress of its own, and speaks a simple register-and-poll contract that BattleTest’s own agents use directly.

What it detects. Everything the agents probe for: blind SSRF, XXE, out-of-band SQL injection, blind XSS, DNS exfiltration (anywhere an agent constructs the payload itself).

What it does not. The template scanner (nuclei) does not speak this protocol and cannot be pointed at it. Its OAST template set stays excluded. Use self-hosted interactsh to enable nuclei OAST templates.

What it costs you. At most one DNS record:

  • A single A record plus OOB_PUBLIC_URL gives path-token correlation (https://oob.example.com/c/<id>). Enough for payloads that take a URL.
  • A wildcard A record (*.oast.example.com → the catcher) additionally catches payloads that take a bare hostname rather than a URL, which is the common shape for XXE and for DNS-only exfiltration. Set OOB_DOMAIN to the wildcard’s base.

The address must be reachable by your targets. Check it from a host outside your network. An internal-only catcher observes nothing, and the run still looks clean.

A self-hosted interactsh server, run by you. The template scanner is pointed at it with -iserver and -itoken, which restores its entire OAST template set.

Everything catcher does, plus the template scanner’s blind-vulnerability templates. It also captures SMTP and LDAP interactions, which the catcher does not.

The template scanner runs with OAST disabled, and the agent’s out-of-band tools report themselves unavailable rather than returning an empty result that reads like a negative finding. The run emits a degradation notice naming the classes that went untested, and that notice is part of the signed export.

Choose this deliberately. Use it when your deployment cannot expose any callback surface.

Concretely, four things:

1. A domain you can repoint. Not a subdomain: a registered domain whose nameserver records you can change at the registrar. In practice this is a domain bought for the purpose. Whatever else that domain was serving stops being served the moment you delegate it.

2. A host with a stable public IP, reachable from your targets on ports 53 (TCP and UDP), 80, 443, 25, 587 and 389.

3. Those ports free on that host. Port 80 and 443 rule out sharing with a reverse proxy; port 25 collides with BattleTest’s own test-mailbox catcher if you run one. In practice interactsh wants a machine of its own.

4. Ports that stay as they are. The port is implied by the payload the target receives (http://<id>.<domain> is port 80), so remapping on the host side breaks correlation silently, with no error to show for it.

Create host (glue) records at your registrar

Add two hostnames on the domain, ns1 and ns2, both pointing at your server’s public IP address. Registrars call this “host records”, “glue records”, or “personal nameservers”; the wording varies, the operation does not.

Both may point at the same IP. Two records exist because most registrars require two nameservers.

Repoint the domain's nameservers

In the domain’s DNS settings, choose to use your own nameservers and enter ns1.YOUR-DOMAIN and ns2.YOUR-DOMAIN.

Delegation makes your interactsh server authoritative for the domain. DNS propagation can take up to 48 hours.

Configure the deployment

Terminal window
OAST_MODE=interactsh
OAST_INTERACTSH_DOMAIN=your-domain.example
OAST_INTERACTSH_IP=203.0.113.10 # the server's PUBLIC IP
OAST_INTERACTSH_TOKEN=<a long random string>
OAST_INTERACTSH_URL=https://your-domain.example

OAST_INTERACTSH_IP is not optional. Left unset, interactsh tries to discover its own address by fetching a public IP-echo service and by opening a socket toward one of its vendor’s hosts: two undeclared outbound calls at startup, and a hard exit when both fail. Worse, inside a container the address it would settle on is the container’s, so every DNS answer would point your targets at a private address and no callback would ever arrive.

OAST_INTERACTSH_TOKEN is not optional either. A server started without one accepts registration from anyone who finds it, which makes it an open relay running on your infrastructure under your domain. The service refuses to start without a token rather than defaulting to open.

Start it

Terminal window
docker compose -f docker-compose.prod.yml --profile interactsh up -d interactsh

The service sits behind a compose profile so a deployment that has not done the delegation never starts a server that cannot work.

Verify a callback actually lands

From a host outside your network, not the deployment host:

Terminal window
dig +short test123.your-domain.example @ns1.your-domain.example
curl -sv http://test123.your-domain.example/ 2>&1 | head -20

The DNS query should resolve to your server’s public IP, and the HTTP request should reach it. If either fails, the delegation has not propagated or a firewall is in the way, and every blind-vulnerability result the deployment produces from here will be a false negative.

interactsh obtains a TLS certificate through ACME by default, which reaches Let’s Encrypt. On a network that cannot, supply your own:

Terminal window
OAST_INTERACTSH_ARGS=-skip-acme -cert /tls/tls.crt -privkey /tls/tls.key
OAST_INTERACTSH_CERT_PATH=/etc/ssl/internal/oast-wildcard.crt
OAST_INTERACTSH_KEY_PATH=/etc/ssl/internal/oast-wildcard.key

The certificate must be a wildcard for the domain. Correlation hostnames are randomly generated subdomains, so a single-name certificate makes every HTTPS and SMTPS interaction fail its handshake, which looks identical to a target that never called back.

-skip-acme on its own, with no certificate supplied, disables TLS entirely. HTTP, DNS and SMTP interactions still work; HTTPS ones do not.

Storage is on disk (-disk) so a redeploy does not discard interactions a run is still polling for.

Confirm these requirements before delegating the domain:

  • The domain is single-purpose. Buy one for this. It serves no mail, no web presence, and nothing else depends on its DNS.
  • Callback destination. Self-hosting keeps callback traffic on infrastructure you control.
  • The server is receive-only. It answers DNS and accepts connections. It initiates nothing outbound once OAST_INTERACTSH_IP is set.
  • Without delegation. The bundled catcher supports agent-driven tests. Nuclei OAST templates require interactsh. The report records which backend was used.

If out-of-band testing is unavailable, the run emits a coverage notice before testing. The notice lists the vulnerability classes that will not be tested.

That notice is recorded inside the signed manifest content, so the caveat cannot be separated from the report it qualifies.