DEV Community

Cover image for Stop Scanners from Hammering Your PHP App — Without a Database or External Services
Benyamin Khalife
Benyamin Khalife

Posted on

Stop Scanners from Hammering Your PHP App — Without a Database or External Services

Every day, automated bots are scanning your website. Not just yours — everyone's. They probe for exposed .env files, old WordPress admin panels, SQL injection points, and known CVEs. Some of them send thousands of requests per minute, not because they're targeting you specifically, but because scanning the entire internet is cheap and easy.

This article is about what's actually happening out there, why it matters even for small projects, and how a lightweight PHP library can help you deal with it.


What Are These Scanners, Exactly?

Web scanners are automated programs that crawl the internet looking for security vulnerabilities. Some are legitimate security tools — like Shodan or Censys — that map the internet for research purposes. But many others are operated by attackers who are looking for:

  • Exposed configuration files.env, web.config, .git/config
  • Unprotected admin panels/wp-admin, /phpmyadmin, /adminer
  • Known vulnerable endpoints — like xmlrpc.php in WordPress or setup.php left behind after an install
  • SQL injection and XSS vectors — via query strings, POST bodies, and cookies
  • Path traversal attacks — trying ../../etc/passwd variations

The tools they use are well-known: sqlmap, nikto, nmap, dirbuster, gobuster, masscan, and many more. These aren't obscure hacking tools — they're freely available, widely documented, and actively maintained.

Even if your app has no actual vulnerabilities, these scanners generate noise, consume server resources, pollute your logs, and can slow down real users during a burst of requests.


Why Server-Level Solutions Aren't Always Enough

You might wonder: "Can't Nginx or Apache handle this?"

Sort of. You can write firewall rules, block known bad IPs, or configure rate limiting in your server config. But these approaches have limitations:

  • They require system-level access (not always available on shared hosting)
  • They need manual maintenance as attack patterns evolve
  • They can't easily inspect request payloads for things like SQL injection signatures
  • They don't integrate naturally with your application logic

A PHP-level firewall runs inside your application, which means it can inspect everything: headers, query strings, POST bodies, cookies, request paths, and user agents — and react accordingly.


Introducing xZeroProtect

xZeroProtect is a lightweight, file-based PHP 8 firewall with zero external dependencies. No Redis, no MySQL, no external services — everything is stored on disk.

Introducing xZeroProtect layouts

Install it via Composer:

composer require webrium/xzeroprotect
Enter fullscreen mode Exit fullscreen mode

The simplest possible integration — two lines at the top of your index.php:

<?php
require 'vendor/autoload.php';

use Webrium\XZeroProtect\XZeroProtect;

XZeroProtect::init()->run();
Enter fullscreen mode Exit fullscreen mode

That's it. Default rules activate immediately.


What It Actually Does

1. Blocks Known Scanner User Agents

The library maintains a list of User-Agent signatures associated with scanning tools and attack frameworks: sqlmap, nikto, nessus, acunetix, masscan, dirbuster, gobuster, feroxbuster, wfuzz, ffuf, hydra, metasploit, and many others. Requests from these tools are blocked before they get anywhere near your application logic.

You can extend or trim this list:

$firewall->patterns->addAgent('custom-bad-bot');
$firewall->patterns->removeAgent('curl'); // if your API clients use curl legitimately
Enter fullscreen mode Exit fullscreen mode

2. Blocks Sensitive Path Probes

Scanners routinely probe paths that don't exist in modern PHP apps — and shouldn't. xZeroProtect blocks requests targeting:

  • CMS admin panels: /wp-admin, /wp-login, /administrator
  • Config files: .env, .git, .htpasswd, web.config
  • Database tools: /phpmyadmin, /adminer, /dbadmin
  • Dangerous files: .sql, .bak, .backup, dump.sql
  • Web shells: shell.php, c99.php, r57.php
  • Path traversal: ../, ..%2f, %2e%2e

If you're running a fully-routed PHP app with no .php files in URLs, you can add that as a rule too:

$firewall->patterns->addPath('.php');
Enter fullscreen mode Exit fullscreen mode

3. Scans Payloads for Attack Signatures

The payload scanner inspects GET parameters, POST body, raw input, and cookies using compiled regular expressions. It detects:

  • SQL injection variants: UNION SELECT, DROP TABLE, SLEEP(), BENCHMARK()
  • XSS: <script tags, inline event handlers, javascript: protocol
  • Path traversal: ../
  • PHP code injection: system(), exec(), eval(base64_decode(...))
  • LFI: /etc/passwd, /etc/shadow
  • Remote file inclusion
  • Command injection via shell metacharacters

4. Rate Limiting Without Redis

The rate limiter uses a sliding-window counter stored per-IP on disk. No Redis or Memcached required.

$firewall = XZeroProtect::init([
    'rate_limit' => [
        'max_requests' => 60,
        'per_seconds'  => 60,
    ],
]);
Enter fullscreen mode Exit fullscreen mode

When a client exceeds the limit, they accumulate violations. After enough violations, they get temporarily banned. After enough bans, permanently.

5. Doesn't Block Legitimate Crawlers

This is important: Google, Bing, and other legitimate search engines should not get caught in your firewall. xZeroProtect handles this with double-DNS verification for major crawlers:

  1. Resolve the visitor's IP to a hostname via reverse DNS
  2. Confirm the hostname ends with the expected suffix (e.g. .googlebot.com)
  3. Re-resolve that hostname back to an IP
  4. Confirm the re-resolved IP matches the original

Anyone can fake a User-Agent string. DNS cannot be faked. This is the same verification method recommended by Google and Bing in their official documentation.


Learning Mode: Test Before You Block

Before switching to production, you can run in learning mode. All threats are detected and logged, but nothing is blocked. This lets you review what would have been caught and tune your configuration accordingly.

// During tuning
XZeroProtect::init(['mode' => 'learning'])->run();

// Once satisfied
XZeroProtect::init(['mode' => 'production'])->run();
Enter fullscreen mode Exit fullscreen mode

This is genuinely useful. Running learning mode for a few days on a real server will show you the volume and variety of automated probes hitting your application — and help you avoid accidentally blocking legitimate traffic.


Apache Integration for Zero-PHP-Overhead Blocking

For permanently banned IPs, you can optionally write them into .htaccess so Apache rejects the connection before PHP even starts:

$firewall = XZeroProtect::init([
    'apache_blocking' => true,
    'htaccess_path'   => __DIR__ . '/.htaccess',
]);
Enter fullscreen mode Exit fullscreen mode

This is particularly useful for confirmed attackers who you never want to reach your application layer again.


Custom Rules

If the built-in modules don't cover a specific case, you can register your own:

use Webrium\XZeroProtect\RuleResult;

$firewall->rules->add('no-php-extension', function ($request) {
    if (str_ends_with($request->path(), '.php')) {
        return RuleResult::block('PHP extension not valid on this server');
    }
    return RuleResult::pass();
});
Enter fullscreen mode Exit fullscreen mode

Rules can block, log without blocking, or pass. They run in priority order and can be enabled, disabled, or removed at runtime.


What xZeroProtect Is Not

To be clear about expectations: this is not a commercial WAF. It won't stop a sophisticated, targeted attack from a determined adversary. It doesn't do deep packet inspection, it doesn't have a threat intelligence feed, and it doesn't update its rule set automatically.

What it does is significantly reduce the automated noise that hits most PHP applications — the opportunistic scanners, the script-kiddie tools, the mass-probing bots — with a setup that takes minutes and has no infrastructure dependencies.

For many projects, especially those on shared hosting or small VPS instances where you don't have the option to configure Nginx rate limiting or cloud WAF rules, this is a practical and useful layer of defense.


Getting Started

composer require webrium/xzeroprotect
Enter fullscreen mode Exit fullscreen mode

Full documentation and source: github.com/webrium/xzeroprotect

Start in learning mode, review your logs, tune your configuration, then flip to production.


Have feedback or found an edge case? Issues and PRs are welcome on GitHub.

Top comments (0)