A self-hosted Out-of-Band Application Security Testing server written in Rust. It combines a DNS server, HTTP server, and SMTP server into a single binary — all interactions are forwarded to you as Telegram notifications in real time.
Typical use cases: detecting blind SSRF, blind XXE, blind SQL injection, command injection with out-of-band callbacks, and any other vulnerability class where the target application reaches out to an attacker-controlled domain.
- DNS — authoritative wildcard DNS server; every query to
*.yourdomain.comis answered with your VPS IP and reported via Telegram. - HTTP — catches every HTTP request to your domain; method, path, headers, and body are forwarded to Telegram.
- SMTP — listens on port 25 and captures email interactions (EHLO/MAIL FROM/RCPT TO/DATA); reports sender, recipient, and message body.
- IP-domain encoding — special subdomain format where the label is a hex-encoded IP string (e.g.
31302e31302e39302e323036.i.yourdomain.com→10.10.90.206). Useful for SSRF bypasses that require a DNS-resolvable hostname pointing to an internal address. - Hot-reload config — the config file is watched for changes; the server reloads it live without a restart.
- Telegram notifications — all captured events are pushed to a Telegram chat with timestamps.
┌─────────────────────────────────────────────────┐
│ oast-server │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ DNS │ │ HTTP │ │ SMTP │ │
│ │ :53/UDP │ │ :80/TCP │ │ :25/TCP │ │
│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
│ └─────────────┴───────────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ TelegramNotifier │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────┘
- Rust 1.75+ (uses Tokio async runtime)
- A VPS with a public IP
- A domain you control
- A Telegram bot token and chat ID
cargo build --release
# binary: target/release/oast-serverRegister your domain and add the following records at your registrar:
| Type | Name | Value |
|---|---|---|
| A | ns1.c.yourdomain | <VPS IP> |
| A | ns2.c.yourdomain | <VPS IP> |
| NS | c.yourdomain | ns1.c.yourdomain |
| NS | c.yourdomain | ns2.c.yourdomain |
If you also want the IP-domain feature on a second subdomain (e.g. i.yourdomain), point its NS records to the same nameservers:
| Type | Name | Value |
|---|---|---|
| NS | i.yourdomain | ns1.c.yourdomain |
| NS | i.yourdomain | ns2.c.yourdomain |
Port 53 is occupied by systemd-resolved on most modern Linux distributions. Free it before starting the server:
sudo nano /etc/systemd/resolved.conf
# Set: DNSStubListener=no
sudo systemctl restart systemd-resolved
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.confCopy the example and edit it:
cp config.yaml.example config.yaml# config.yaml
# Primary wildcard OAST domain (*.c.yourdomain.com → VPS IP)
domain: "c.yourdomain.com"
# Public IP of your VPS
ip: "1.2.3.4"
dns:
port: 53
ttl: 60
# Additional domains served by this server (same wildcard behaviour)
extra_domains: []
# Hex-encoded IP domains:
# hex("10.10.90.206") = "31302e31302e39302e323036"
# → 31302e31302e39302e323036.i.yourdomain.com resolves to 10.10.90.206
ip_domains:
- "i.yourdomain.com"
# Glue / static records
records:
- name: "ns1"
ip: "1.2.3.4"
- name: "ns2"
ip: "1.2.3.4"
http:
port: 80
enabled: true
smtp:
port: 25
enabled: true
telegram:
bot_token: "123456789:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
chat_id: "your_chat_id"The config file is watched at runtime — any save triggers a live reload.
# Uses config.yaml in the current directory by default
./target/release/oast-server
# Or specify a custom config path
./target/release/oast-server /etc/oast/config.yamlThe server requires permission to bind to privileged ports (80, 25, 53). Either run as root or grant the binary the CAP_NET_BIND_SERVICE capability:
sudo setcap 'cap_net_bind_service=+ep' ./target/release/oast-server[Unit]
Description=OAST Server
After=network.target
[Service]
ExecStart=/opt/oast-server/oast-server /opt/oast-server/config.yaml
Restart=on-failure
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.targetCreate a Telegram bot via @BotFather, obtain the token, then retrieve your chat ID by messaging the bot and calling:
https://api.telegram.org/bot<TOKEN>/getUpdates
Example notifications:
DNS hit
🔔 DNS
🌍 abc123.c.yourdomain.com
🔍 Type: A
🌐 From: 203.0.113.42:54321
⏰ 2026-03-25 10:00:00 UTC
HTTP hit
🔔 HTTP
🌍 abc123.c.yourdomain.com
🔍 GET /callback?token=secret
🌐 From: 203.0.113.42:12345
⏰ 2026-03-25 10:00:01 UTC
📋 Headers: ...
📦 Body: ...
SMTP hit
🔔 SMTP
📤 From: attacker@evil.com
📥 To: test@abc123.c.yourdomain.com
🌐 From IP: 203.0.113.42:25
⏰ 2026-03-25 10:00:02 UTC
The ip_domains option enables a special DNS mode where the subdomain label is the hex encoding of an IP address string. This is useful for SSRF exploits that need a DNS name resolving to an internal address.
Encoding an IP:
"10.10.90.206".encode().hex() # → "31302e31302e39302e323036"Query:
31302e31302e39302e323036.i.yourdomain.com → A 10.10.90.206
No Telegram notification is sent for IP-domain queries (they are infrastructure queries, not out-of-band callbacks).
| Variable | Description | Default |
|---|---|---|
RUST_LOG |
Log level filter (info, debug, …) |
oast_server=info |
RUST_LOG=debug ./oast-serverMIT