Brute is a project for monitoring authentication attempts on servers using OpenSSH. It tracks and records each attempt and provides detailed information about the source of the attempt.
Currently, this project must use a specific version of OpenSSH. Unfortunately, the changes made may compromise the security of your server, so use with caution.
-
Straightforward — Simply call the endpoint
/brute/attack/add, and Brute will log, analyze, and store the credentials for you. -
Extendable Metrics — Brute allows developers to easily add or remove metrics as needed.
-
Location Information — In standalone mode, information is retrieved via the IPinfo API. In Cloudflare Workers mode, geo data is read directly from the Cloudflare
cfrequest object — no external API token required. -
WebSocket Support — Brute supports WebSocket connections for real-time streaming. In standalone mode this uses actix-web-actors. In Workers mode this uses a hibernatable WebSocket Durable Object.
-
Dual Deployment Mode — Run as a standalone Tokio/Actix server backed by PostgreSQL, or deploy to Cloudflare Workers with D1 (SQLite) and Analytics Engine.
brute/
├── brute-core/ # Shared data models, validation, and trait definitions
├── brute-http/ # Standalone server (Tokio + Actix, PostgreSQL, IPinfo)
├── brute-worker/ # Cloudflare Workers (D1 + Analytics Engine + Durable Objects)
├── brute-daemon/ # Traffic source daemon (SSH, FTP)
└── migrations/
├── postgres/ # PostgreSQL migration files (used by brute-http)
└── d1/ # SQLite schema files (used by brute-worker via wrangler)
| Crate | Purpose |
|---|---|
brute-core |
Shared models (Individual, ProcessedIndividual, all Top* structs), validation logic, and BruteDb / BruteAnalytics / GeoProvider trait definitions |
brute-http |
Standalone HTTP server. Implements BruteDb via PostgresDb (sqlx), GeoProvider via IpInfoProvider, and BruteAnalytics via PostgresAnalytics (top_* tables) |
brute-worker |
Cloudflare Worker. Implements BruteDb via D1Db (workers-rs D1 binding), GeoProvider via CfGeoProvider (cf request object), and BruteAnalytics via AnalyticsEngine |
| Feature | brute-http (standalone) | brute-worker (Cloudflare) |
|---|---|---|
| Database | PostgreSQL (sqlx) | Cloudflare D1 (SQLite) |
| Geo lookup | IPinfo.io HTTP API (token required) | Cloudflare cf object (free, built-in) |
| Analytics | Aggregated top_* tables in PostgreSQL |
Cloudflare Analytics Engine data points |
| WebSocket | actix-web-actors (actor model) | Hibernatable WebSocket Durable Object |
| Deployment | Docker / systemd / bare metal | wrangler deploy |
| TLS | Managed via rustls + cert.pem/key.pem | Managed by Cloudflare automatically |
This installs brute-http, the standalone HTTP server that collects traffic from dummy servers.
# Download rustup
curl https://sh.rustup.rs -sSf | sh
# Add Rust to PATH (restart shell or run:)
source "$HOME/.cargo/env"
# Verify the installation
rustc -VShow instructions
-
Clone the repository:
git clone https://github.com/chomnr/brute
-
Go into the
brute-httpdirectory:cd brute/brute-http -
Set the following environment variables:
DATABASE_URL=postgresql://postgres:{password}@{host}/{database} BEARER_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx IPINFO_TOKEN=xxxxxxxxxxxxxx RUST_LOG=trace RUST_LOG_STYLE=always LISTEN_ADDRESS=0.0.0.0:7000 LISTEN_ADDRESS_TLS=0.0.0.0:7443 RUNNING_IN_DOCKER=false # Optional — enables AbuseIPDB reputation scoring ABUSEIPDB_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-
Add your
cert.pemandkey.pemto the/certsfolder insidebrute-http/:Generate one from Cloudflare, Let's Encrypt, or OpenSSL. If you don't want TLS, remove serve_tls() from main.rs. -
Build and run:
cargo build --release -p brute-http # then run the executable, or: cargo run -p brute-http
Show instructions
-
Clone the repository:
git clone https://github.com/chomnr/brute
-
Open the
DockerFileand edit the environment variables:ENV DATABASE_URL=postgresql://postgres:{password}@{host}:{port}/brute ENV BEARER_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ENV IPINFO_TOKEN=xxxxxxxxxxxxxx ENV RUST_LOG=trace ENV RUST_LOG_STYLE=always ENV LISTEN_ADDRESS=0.0.0.0:7000 ENV LISTEN_ADDRESS_TLS=0.0.0.0:7443 ENV RUNNING_IN_DOCKER=true -
(Optional) Copy your
cert.pemandkey.pemintobrute-http/:Required only if you want to run with TLS. -
Build the image from the project root:
docker build --pull --rm -f "DockerFile" -t brute:latest "."
-
Run the container:
docker run --name brute -p 7000:7000 -p 7443:7443 --restart unless-stopped -d brute # sqlx will apply migrations automatically on startup.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string |
BEARER_TOKEN |
Yes | Secret token for API authentication |
IPINFO_TOKEN |
Yes | IPinfo.io API token for geo lookup |
LISTEN_ADDRESS |
Yes | HTTP bind address, e.g. 0.0.0.0:7000 |
LISTEN_ADDRESS_TLS |
Yes | HTTPS bind address, e.g. 0.0.0.0:7443 |
RUNNING_IN_DOCKER |
Yes | Set to true when running inside Docker |
ABUSEIPDB_KEY |
No | AbuseIPDB API key — enables IP reputation scoring |
RUST_LOG |
No | Log level (trace, debug, info, warn, error) |
brute-worker deploys to the Cloudflare Workers edge network. It uses D1 for storage, Analytics Engine for aggregated event data, and a Durable Object for WebSocket broadcasting.
npm install -g wrangler
# or use npx wrangler-
Create a D1 database:
wrangler d1 create worker_brute_d1
Copy the
database_idfrom the output and paste it intobrute-worker/wrangler.toml:[[d1_databases]] binding = "worker_brute_d1" database_name = "worker_brute_d1" database_id = "YOUR_D1_DATABASE_ID" # <-- paste here
-
Apply the D1 schema:
wrangler d1 execute worker_brute_d1 --file=../migrations/d1/0001_initial_schema.sql
-
Set the bearer token secret:
wrangler secret put BEARER_TOKEN
-
Deploy:
cd brute-worker npm run deploy # or: wrangler deploy
| Binding / Variable | Type | Description |
|---|---|---|
DB |
D1 binding | SQLite database via Cloudflare D1 |
ANALYTICS |
Analytics Engine binding | Writes attack event data points |
WS_BROADCASTER |
Durable Object binding | Manages WebSocket connections |
BEARER_TOKEN |
Secret (var) | API authentication token |
No IPINFO_TOKEN is needed — geo data comes from the Cloudflare cf request object for free.
cd brute-worker
wrangler devBoth brute-http and brute-worker expose the same REST API.
All POST endpoints require a Authorization: Bearer <BEARER_TOKEN> header.
| Endpoint | Description |
|---|---|
POST /brute/attack/add |
Record an authentication attempt |
POST /brute/protocol/increment |
Increment a protocol counter directly |
| Endpoint | Description |
|---|---|
GET /brute/stats/attack |
Recent processed attacks |
GET /brute/stats/username |
Top usernames |
GET /brute/stats/password |
Top passwords |
GET /brute/stats/ip |
Top IPs |
GET /brute/stats/protocol |
Top protocols |
GET /brute/stats/country |
Top countries |
GET /brute/stats/city |
Top cities |
GET /brute/stats/region |
Top regions |
GET /brute/stats/timezone |
Top timezones |
GET /brute/stats/org |
Top organizations |
GET /brute/stats/postal |
Top postal codes |
GET /brute/stats/loc |
Top lat/lon locations |
GET /brute/stats/combo |
Top username/password combinations |
GET /brute/stats/combo/protocol |
Top combos filtered by protocol |
GET /brute/stats/hourly |
Hourly attack counts |
GET /brute/stats/daily |
Daily attack counts |
GET /brute/stats/weekly |
Weekly attack counts |
GET /brute/stats/yearly |
Yearly attack counts |
GET /brute/stats/heatmap |
Attack heatmap (day × hour) |
GET /brute/stats/subnet |
Top /24 subnets |
GET /brute/stats/velocity |
Attack velocity (per minute, last hour) |
GET /brute/stats/ip/seen |
IP first/last seen times |
GET /brute/stats/ip/abuse |
AbuseIPDB scores |
GET /brute/stats/summary |
Rolling stats summary |
| Endpoint | Description |
|---|---|
GET /brute/export/blocklist |
Export top IPs as a blocklist. ?format=plain|iptables|nginx|fail2ban |
| Endpoint | Description |
|---|---|
GET /ws |
Connect to the real-time broadcast stream |
Before installing, identify where you want to source your traffic. Two supported sources:
- OpenSSH — patched version that calls Brute on each auth attempt
- Daemon — custom daemon that listens on SSH, FTP, and other ports
Supports a wide variety of protocols. You can integrate any protocol by calling /brute/attack/add and specifying the protocol in the payload. Use this on a dummy server only.
https://github.com/chomnr/brute
Show instructions
-
Clone:
git clone https://github.com/chomnr/brute cd brute/brute-daemon -
Build:
cargo build --release mv ~/brute-daemon/target/release/brute-daemon /usr/local/bin/brute-daemon -
Create a systemd service:
nano /etc/systemd/system/brute-daemon.service
[Unit] Description=Brute Daemon After=network.target [Service] ExecStart=/usr/local/bin/brute-daemon Restart=always User=root WorkingDirectory=/usr/local/bin StandardOutput=append:/var/log/brute-daemon.log StandardError=append:/var/log/brute-daemon_error.log Environment="ADD_ATTACK_ENDPOINT=https://example.com/brute/attack/add" Environment="BEARER_TOKEN=my-secret-token" [Install] WantedBy=multi-user.target
-
Enable and start:
systemctl daemon-reload systemctl enable brute-daemon systemctl start brute-daemon systemctl status brute-daemon
Show instructions
-
Install build dependencies:
sudo apt update && sudo apt upgrade sudo apt install build-essential zlib1g-dev libssl-dev libpq-dev pkg-config sudo apt install libcurl4-openssl-dev libpam0g-dev autoconf -
Clone and build the patched OpenSSH:
git clone https://github.com/chomnr/openssh-9.8-patched cd openssh-9.8-patched autoreconf ./configure --with-pam --with-privsep-path=/var/lib/sshd/ --sysconfdir=/etc/ssh make && make install
-
Replace the system SSH in
/lib/systemd/system/ssh.service:- ExecStartPre=/usr/sbin/sshd -t - ExecStart=/usr/sbin/sshd -D $SSHD_OPTS - ExecReload=/usr/sbin/sshd -t + ExecStartPre=/usr/local/sbin/sshd -t + ExecStart=/usr/local/sbin/sshd -D $SSHD_OPTS + ExecReload=/usr/local/sbin/sshd -t
-
Verify:
ssh -Vshould output(Brute) OpenSSH_9.8... -
Build and install the PAM module:
git clone https://github.com/chomnr/brute_pam cd brute_pam cmake . && make cp lib/brute_pam.so /lib/x86_64-linux-gnu/security/
-
Add to
/etc/pam.d/common-auth:- auth [success=1 default=ignore] pam_unix.so nullok + auth [success=2 default=ignore] pam_unix.so nullok + auth optional pam_brute.so
The MIT License (MIT) 2024 - Zeljko Vranjes. Please have a look at the LICENSE.md for more details.
