How I Tested DenyHosts Against Custom Brute Force Simulation Scripts
DenyHosts is designed to watch SSH authentication activity, identify repeated failures, and maintain a list of hosts that should be blocked. I wanted to evaluate that behavior in a controlled environment rather than rely on assumptions or a handful of manual login attempts. The test focused on how quickly DenyHosts reacted, how accurately it separated noisy clients from ordinary mistakes, and how clearly it recorded each decision.
I wrote small Python scripts to simulate several brute force patterns against a disposable Linux machine. The scripts never touched public servers or third-party networks. Every connection came from isolated local environments, with deliberately low request rates and clear stop conditions. This made it possible to repeat the same experiment without creating an operational security problem.
The exercise also reflected a broader development habit: useful security tools should be tested with observable, reproducible inputs. A log analyzer such as log analysis background can reveal what happened after the fact, but DenyHosts had to make decisions while the simulated activity was still unfolding.
Defining The Test Environment
I used a virtual machine running Linux as the SSH target and kept the host-only network separate from the internet. The machine had a test account with a deliberately invalid password, while the simulator ran from several local virtual network addresses. A snapshot let me restore the target after each run, removing blocked-host files and returning the SSH configuration to a known state.
The DenyHosts configuration was kept close to a practical default. I recorded the relevant thresholds, purge settings, blocked-host file location, and daemon status before starting. Recording configuration mattered because a result is difficult to interpret if a previous experiment has left behind an old entry or changed the number of failures required for a ban.
I also synchronized the clocks on the virtual machines and captured system time in each script. That made it easier to compare SSH logs, DenyHosts messages, and simulator output. Small timestamp differences can otherwise make a short test appear slower or faster than it really was.
Building Custom Brute Force Simulators
The simulator was intentionally simple. It opened an SSH connection, supplied a username and invalid password, closed the session, waited for a configurable interval, and repeated the sequence. The script accepted parameters for attempt count, delay, username, source address, and concurrency. It also wrote a local event record for every attempt, including whether the connection was refused, closed, or rejected by authentication.
I created separate modes instead of one complicated attack generator. A steady mode sent failures at a fixed interval, a burst mode sent several attempts in quick succession, and a rotating-user mode changed usernames while keeping the source address constant. A slow mode represented a client trying to remain below an obvious threshold. These patterns helped expose how DenyHosts responded to timing and identity changes.
The scripts included safety controls that were essential in a lab. They rejected non-private target addresses, required an explicit test flag, capped the attempt count, and stopped when the SSH service became unavailable. The goal was to exercise detection logic, not to create a denial-of-service condition or produce an uncontrolled password-guessing tool.
Measuring Detection And Blocking
For each run, I collected four signals: the simulator’s attempt timestamps, the SSH authentication log, DenyHosts log messages, and the contents of the hosts-deny file. I then compared the first failed login with the first warning, the first recorded block, and the first rejected connection. Those intervals provided a practical view of detection latency.
A run was considered successful when DenyHosts identified the simulated source and inserted the expected rule without affecting unrelated test clients. I also checked whether repeated launches created duplicate entries, whether a restart preserved the block, and whether a clean reset removed the result as expected.
The following summary shows the kinds of behaviors I compared rather than presenting a universal benchmark. Exact timing depends on the operating system, SSH configuration, Python version, disk speed, and DenyHosts settings.
| Simulation pattern | Main variable | What I checked | Typical observation |
|---|---|---|---|
| Steady failures | Fixed delay | Threshold and detection time | Predictable escalation |
| Short burst | High attempt rate | Rapid log processing | Fast recognition, denser logs |
| Rotating usernames | Account variation | Source-based tracking | Address remained identifiable |
| Slow failures | Long delay | Sensitivity to spaced attempts | Greater dependence on thresholds |
| Repeated run | Same source after reset | State cleanup and persistence | Existing records affected results |
Comparing Attack Patterns
The steady pattern was the easiest to interpret. Each failed authentication appeared in order, and DenyHosts moved from observation to blocking after the configured failure conditions were met. This provided a baseline for checking that the simulator, SSH daemon, and DenyHosts service were all operating correctly.
Burst traffic produced a different kind of evidence. The simulator completed several attempts before the monitoring process had visibly written every message, so I treated log display time carefully. A delayed console line did not necessarily mean delayed detection. Comparing file timestamps and the final blocked-host state gave a more reliable result than watching the terminal alone.
Rotating usernames demonstrated why source identity is important. Changing the account name did not make the traffic benign when the same address generated the failures. The slow pattern was more revealing because it showed the tradeoff between catching persistent guessing and avoiding bans caused by occasional typing mistakes. Lower thresholds improved reaction time but increased the need for careful operational tuning.
Reviewing False Positives And Recovery
I included ordinary-looking mistakes in a separate run: one or two invalid passwords followed by a valid login, then a long pause before another test. DenyHosts did not treat every failed login as an attack under the selected settings, which was important for shared systems where users occasionally mistype credentials. The test also showed why an administrator should inspect the threshold and purge behavior instead of copying a configuration blindly.
Recovery was tested as carefully as detection. After a source was blocked, I verified that new SSH attempts were refused according to the resulting access rule. I then stopped the service, removed the test state, restored the snapshot, and confirmed that a clean client could authenticate again. This prevented a successful detection test from becoming a persistent access problem in later experiments.
I also examined how easy it was to understand the evidence after the run. A useful security utility should leave enough information to explain why a host was blocked. Clear timestamps, source addresses, and failure counts made the DenyHosts output valuable for troubleshooting, especially when several simulations ran in sequence.
Practical Safeguards For Reproducible Testing
The most dependable results came from treating the simulation as a small test harness rather than an attack script. These practices kept the experiments controlled:
- Use isolated virtual machines or a private host-only network.
- Require explicit target validation and cap attempts and concurrency.
- Save the DenyHosts configuration and clear state between test cases.
- Correlate simulator events with SSH and DenyHosts timestamps.
- Test recovery so a blocked source can be restored safely.
Custom scripts made the behavior easier to understand because every input was under my control. Instead of asking whether DenyHosts “worked” in the abstract, I could examine thresholds, timing, source tracking, log evidence, and cleanup as separate properties.
The testing also reinforced a practical limitation: a blocker is one layer of defense, not a replacement for strong authentication, key-based access, sensible SSH configuration, updates, and monitoring. Simulation is most useful when it exposes how those layers interact and where an administrator needs better visibility.
Anyone evaluating DenyHosts can reproduce this approach with a disposable Linux target, a few parameterized Python clients, and a written record of every configuration change. Start with a harmless steady pattern, compare it with slower and burstier traffic, review the logs, and restore the environment after each run. This turns a vague security claim into evidence that can be inspected, repeated, and improved.
