Repository files navigation

pow-shield-php

License: GPL-3.0PHP 8+Apache 2.4+

A lightweight Proof-of-Work (PoW) gateway for PHP sites that reduces abusive traffic without CAPTCHAs.
It issues a signed cookie (abp) after a browser completes a SHA-256 work check, then allows normal access.

✨ What's Included

This repository includes:

  • ✅ PoW challenge page: __ab/pow.php
  • ✅ PoW verifier + signed cookie: __ab/pow-verify.php
  • ✅ ModSecurity rate limits for PoW endpoints: modsecurity/ab_pow_ratelimit.conf
  • ✅ Apache vhost examples (sanitized to example.com) with PoW "skip" rules + clean URL option
  • ✅ Cloudflare compatibility notes (cache bypass + real client IP restore)
  • ✅ Secret rotation script + systemd service/timer examples

This repository intentionally excludes:

  • ❌ TLS certificates / private keys
  • ❌ secrets (your AB_POW_SECRET)
  • ❌ server logs / user data

🔄 How it works (request flow)

  1. A client requests a protected URL and does not have cookie abp
  2. Apache rewrites/redirects them to:
    /__ab/pow.php?next=/original/path&qs=original=query
    
  3. pow.php runs PoW in the browser:
    • compute sha256(TOKEN + "." + counter) until it has enough leading zero bits
  4. Browser submits the solution to:
    /__ab/pow-verify.php
    
  5. Server verifies:
    • token integrity (HMAC)
    • user-agent binding (light)
    • PoW difficulty (leading zero bits)
  6. Server sets cookie:
    • abp=<signed value> (Secure, HttpOnly, SameSite=Lax)
  7. Browser is redirected back to the original URL

Goal: make abusive traffic expensive while normal visitors pass quickly.


🔴 Live Production Example

A live deployment of pow-shield-php is running in production here:

https://lassiter.eu

This site uses:

  • Proof-of-Work (PoW) gateway for unauthenticated traffic
  • ModSecurity rate limiting on PoW endpoints
  • Apache connection-level protections (Slowloris / low-and-slow mitigation)
  • Cloudflare as CDN + TLS terminator (no bot challenges, no CAPTCHA)

⚠️Note: Configuration values, secrets, and thresholds used on the live site are intentionally not published in this repository.


📋 Requirements

Automatic Installation

The installer handles all dependencies automatically. Simply run:

sudo ./install.sh

Manual Requirements

If installing manually, you need:

Origin

  • PHP 8+
  • HTTPS (required for Secure cookie + WebCrypto)
  • Apache 2.4+

Optional / recommended

  • ModSecurity (Apache connector + CRS optional) for rate-limiting /__ab/*
  • If behind Cloudflare: Apache mod_remoteip configured to restore the real client IP

Secret (required)

  • AB_POW_SECRET must be set in the environment
  • Minimum: 48 characters
  • Recommended: 64+ characters

📂 Repository layout

pow-shield-php/
├─ __ab/
│ ├─ pow.php
│ └─ pow-verify.php
├─ modsecurity/
│ └─ ab_pow_ratelimit.conf
├─ apache/
│ └─ sites-available/
│ ├─ example.com-redirect.conf.example
│ └─ example.com.conf.example
├─ scripts/
│ └─ rotate-pow-secret.sh.example
├─ systemd/
│ ├─ rotate-pow-secret.service.example
│ └─ rotate-pow-secret.timer.example
├─ assets/img/
│ ├─ README.md
│ └─ .gitkeep
├─ docs/
│ ├─ cloudflare-notes.md
│ ├─ installation-checklist.md
│ └─ modsecurity-global-notes.md
├─ install.sh # 🆕 Automated installer
├─ uninstall.sh # 🆕 Automated uninstaller
└─ README.md

🚀 Quick Installation

We provide automated installation scripts for easy setup:

Option A: Automated Installation (Recommended)

# Clone the repository
git clone https://github.com/AfterPacket/pow-shield-php.git
cd pow-shield-php
# Make scripts executable
chmod +x install.sh uninstall.sh
# Run interactive installer
sudo ./install.sh

📖 Full Installation Guide: See INSTALL.md for detailed instructions, troubleshooting, and advanced configuration options.

The installer will:

  • ✅ Install all required dependencies (Apache, PHP, OpenSSL)
  • ✅ Generate secure PoW secret automatically
  • ✅ Deploy PoW endpoints and assets
  • ✅ Configure Apache virtual hosts
  • ✅ Set up ModSecurity rate limiting (optional)
  • ✅ Configure Let's Encrypt SSL (optional)
  • ✅ Set up automatic secret rotation

Installation Options

Interactive Mode (Default)

sudo ./install.sh

Follow the prompts to configure your installation.

Non-Interactive with Let's Encrypt

sudo ./install.sh -d example.com -w /var/www/html -l admin@example.com -e

Non-Interactive with Existing SSL

sudo ./install.sh -d example.com -w /var/www/html \
-c /etc/ssl/certs/cert.pem -k /etc/ssl/private/key.pem -e

Skip ModSecurity

sudo ./install.sh -d example.com -w /var/www/html -s

Installation Flags

FlagDescription
-d, --domainDomain name (e.g., example.com)
-w, --webrootWeb root directory path
-c, --certSSL certificate path (optional)
-k, --keySSL key path (optional)
-l, --letsencryptUse Let's Encrypt with email
-e, --enableEnable site with a2ensite after install
-s, --skip-modsecSkip ModSecurity installation
-n, --non-interactiveRun without prompts
-h, --helpShow help message

🗑️ Uninstallation

To completely remove pow-shield-php:

# Interactive uninstaller
sudo ./uninstall.sh
# Force removal without prompts
sudo ./uninstall.sh -d example.com -w /var/www/html -f
# Keep the PoW secret file
sudo ./uninstall.sh -d example.com -w /var/www/html -k
# Also remove ModSecurity rules
sudo ./uninstall.sh -d example.com -w /var/www/html -m

The uninstaller will:

  • ✅ Backup all files before removal
  • ✅ Disable and remove virtual hosts
  • ✅ Remove PoW endpoints
  • ✅ Remove systemd rotation (optional)
  • ✅ Remove ModSecurity rules (optional)
  • ✅ Test Apache config before reload

🛠️ Manual Installation

If you prefer manual installation:

1) Deploy /__ab/ endpoints

Copy the following files into your site webroot:

  • __ab/pow.php
  • __ab/pow-verify.php

They must resolve at:

  • https://example.com/__ab/pow.php
  • https://example.com/__ab/pow-verify.php

Tip: keep /__ab/ excluded from caching and from other WAF rules that might block POST.


2) Add the image used by pow.php (optional UI)

Your pow.php references:

/assets/img/clank.jpg

To keep this path:

  • place the image at assets/img/clank.jpg in your webroot

Or update $MEME_SRC inside __ab/pow.php.


🔐 Secret management (recommended): /etc/apache2/pow.env

Instead of embedding secrets in vhost configs, load them from a root-owned include file:

  • /etc/apache2/pow.env (root-owned, mode 600)
  • included in your HTTPS vhost via:
    IncludeOptional /etc/apache2/pow.env

Create the initial env file

sudo install -d -m 0755 /etc/apache2
sudo bash -c 'umask 077; SECRET="$(openssl rand -base64 64 | tr -d "\n")"; \ printf "%s\n" "# Managed by pow-shield-php" "SetEnv AB_POW_SECRET \"$SECRET\"" > /etc/apache2/pow.env'
sudo chown root:root /etc/apache2/pow.env
sudo chmod 600 /etc/apache2/pow.env
sudo apachectl -t
sudo systemctl reload apache2

⚠️Never commit secrets to git.


🔄 Secret rotation (optional): script + systemd service + timer

Rotating the PoW secret reduces replay value if a cookie/token leaks. To avoid breaking in-flight challenges, rotate with overlap:

  • New secret stored as AB_POW_SECRET
  • Old secret preserved as AB_POW_SECRET_PREV

✅ For this to work, your pow-verify.php should accept either secret when validating.

A) Rotation script

Save as:

/usr/local/sbin/rotate-pow-secret.sh
#!/bin/bashset -euo pipefail
OUT="/etc/apache2/pow.env"
TMP="$(mktemp)"umask 077
# Pull current secret (if any) from existing file
CURRENT=""if [[ -f"$OUT" ]];then
CURRENT="$(awk -F'"''/SetEnv[[:space:]]+AB_POW_SECRET[[:space:]]+"/ {print $2; exit}'"$OUT"|| true)"fi
NEW="$(openssl rand -base64 64 | tr -d '\n')"
{
echo'# Managed by rotate-pow-secret.sh'echo"SetEnv AB_POW_SECRET \"$NEW\""if [[ -n"${CURRENT}" ]];thenecho"SetEnv AB_POW_SECRET_PREV \"$CURRENT\""fi
} >"$TMP"
chown root:root "$TMP"
chmod 600 "$TMP"
mv -f "$TMP""$OUT"# Safety: verify Apache config first
apachectl -t
# Reload, not restart (keeps connections)
systemctl reload apache2

Install + test:

sudo install -m 0755 /usr/local/sbin/rotate-pow-secret.sh /usr/local/sbin/rotate-pow-secret.sh
sudo /usr/local/sbin/rotate-pow-secret.sh

B) systemd service

Create:

/etc/systemd/system/rotate-pow-secret.service
[Unit]Description=Rotate AB_POW_SECRET for pow-shield-php and reload Apache
Wants=apache2.service
After=apache2.service
[Service]Type=oneshot
ExecStart=/usr/local/sbin/rotate-pow-secret.sh
User=root
Group=root
# Hardening (safe defaults)NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/etc/apache2/pow.env

C) systemd timer (hourly)

Create:

/etc/systemd/system/rotate-pow-secret.timer
[Unit]Description=Hourly rotation for AB_POW_SECRET (pow-shield-php)
[Timer]OnCalendar=hourly
Persistent=true
RandomizedDelaySec=120
Unit=rotate-pow-secret.service
[Install]WantedBy=timers.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable --now rotate-pow-secret.timer
sudo systemctl list-timers --all | grep rotate-pow-secret

Manual trigger:

sudo systemctl start rotate-pow-secret.service
sudo systemctl status rotate-pow-secret.service --no-pager

🌐 Install: Apache vhost (PoW gating + skip rules)

Use the sanitized examples in apache/sites-available/.

Two common patterns:

Option A — Redirect to /__ab/pow.php (visible PoW URL)

  • simplest
  • user sees /__ab/pow.php?...

Option B — Internal rewrite (clean URL)

  • keeps the original URL in the address bar
  • uses [PT] internally to serve pow.php

In both options, always skip:

  • /__ab/* (prevents loops)
  • /status/* (your private panels/JSON)
  • static assets
  • non-GET/HEAD methods

🛡️ ModSecurity: rate-limit only the PoW endpoints (recommended)

Rules are provided in:

modsecurity/ab_pow_ratelimit.conf

A) Install ModSecurity (Debian/Ubuntu)

sudo apt update
sudo apt install -y libapache2-mod-security2
sudo a2enmod security2
sudo systemctl reload apache2

Confirm:

apachectl -M | grep -i security

B) Enable engine

In /etc/modsecurity/modsecurity.conf:

SecRuleEngine On
SecRequestBodyAccess On

Reload:

sudo systemctl reload apache2

C) Include PoW rules

Copy:

sudo mkdir -p /etc/modsecurity
sudo cp modsecurity/ab_pow_ratelimit.conf /etc/modsecurity/ab_pow_ratelimit.conf

Then include it in your vhost or global security2 config:

IncludeOptional /etc/modsecurity/ab_pow_ratelimit.conf
Header always set Retry-After "30" env=AB_RL

D) Verify enforcement

foriin$(seq 1 80);do
curl -sk https://example.com/__ab/pow.php?next=/ >/dev/null -w "%{http_code}\n"done

You should see 429 once the limit triggers.


🚨 Additional DDoS Mitigation (Apache-level)

PoW is application-layer cost. It helps with:

  • Basic bot spam
  • Naive request floods
  • Large-scale scraping (makes it expensive per request)

It does not stop all L7 attacks by itself. Pair it with:

  • ModSecurity rate limiting (especially on /__ab/pow-verify.php)
  • mod_reqtimeout (Slowloris mitigation)
  • Connection limits / MPM tuning
  • Correct real-IP restoration when behind Cloudflare

📝 Note: Pattern matters more than specific values; deploy thresholds appropriate to your traffic.


☁️ Cloudflare (recommended configuration)

See docs/cloudflare-notes.md.

Important settings:

  • Bot Fight Mode / "Stop Bot Attack": OFF (can interfere with PoW)
  • 🚫 Cache bypass for:
    • /__ab/pow.php
    • /__ab/pow-verify.php
  • 🌍 Restore real client IP at the origin using mod_remoteip

🔧 Troubleshooting

Infinite "Checking your browser…" loop

Common causes:

  • Cloudflare caching PoW endpoints
  • Cloudflare bot challenges enabled
  • Cookies blocked by browser
  • WAF blocking /__ab/pow-verify.php
  • Using PoW as an ErrorDocument 403 (can recurse)

Fix:

  • Disable Bot Fight / Stop Bot Attack
  • Bypass cache for PoW endpoints
  • Confirm Set-Cookie: abp=... is issued over HTTPS
  • Don't use PoW as 403 handler; use a static error page instead

LibreWolf / hardened Firefox shows "slow-device"

  • Lower difficulty for hardened UAs (or remove the "hard fail")
  • Extend TTL for challenge tokens
  • Ensure cookies aren't blocked for the site

Getting HTTP 429 during testing

  • ModSecurity limits are working as intended
  • Wait for the window to expire (often 60 seconds)

🔒 Security notes

  • AB_POW_SECRET must be long and random (>= 48 chars; 64+ recommended)
  • Never commit secrets to git
  • Consider rotating the secret with overlap (AB_POW_SECRET_PREV) to reduce replay value
  • Keep PoW endpoints uncached and allow POST to /__ab/pow-verify.php
  • If behind Cloudflare, configure real IP restoration before using per-IP rate limits

🧩 Contributing

Contributions are welcome! To participate:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-enhancement
  3. Commit your changes: git commit -m "Add: your feature"
  4. Push to your fork: git push origin feature/your-enhancement
  5. Open a Pull Request

🐛 Issues & Support

Found a bug or have a feature request? Please open an issue with:

  • Steps to reproduce
  • Expected vs actual behavior
  • PHP and Apache versions
  • Operating system

📄 License

This project is licensed under the GNU General Public License v3.0.
See the LICENSE file for full details.


Enjoy Fighting BOTS 🤖🛡️

About

A lightweight PHP proof-of-work gateway that issues a signed cookie, with Cloudflare-friendly ModSecurity rate limiting and Apache vhost examples.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

pow-shield-php

License: GPL-3.0PHP 8+Apache 2.4+

A lightweight Proof-of-Work (PoW) gateway for PHP sites that reduces abusive traffic without CAPTCHAs.
It issues a signed cookie (abp) after a browser completes a SHA-256 work check, then allows normal access.

✨ What's Included

This repository includes:

  • ✅ PoW challenge page: __ab/pow.php
  • ✅ PoW verifier + signed cookie: __ab/pow-verify.php
  • ✅ ModSecurity rate limits for PoW endpoints: modsecurity/ab_pow_ratelimit.conf
  • ✅ Apache vhost examples (sanitized to example.com) with PoW "skip" rules + clean URL option
  • ✅ Cloudflare compatibility notes (cache bypass + real client IP restore)
  • ✅ Secret rotation script + systemd service/timer examples

This repository intentionally excludes:

  • ❌ TLS certificates / private keys
  • ❌ secrets (your AB_POW_SECRET)
  • ❌ server logs / user data

🔄 How it works (request flow)

  1. A client requests a protected URL and does not have cookie abp
  2. Apache rewrites/redirects them to:
    /__ab/pow.php?next=/original/path&qs=original=query
    
  3. pow.php runs PoW in the browser:
    • compute sha256(TOKEN + "." + counter) until it has enough leading zero bits
  4. Browser submits the solution to:
    /__ab/pow-verify.php
    
  5. Server verifies:
    • token integrity (HMAC)
    • user-agent binding (light)
    • PoW difficulty (leading zero bits)
  6. Server sets cookie:
    • abp=<signed value> (Secure, HttpOnly, SameSite=Lax)
  7. Browser is redirected back to the original URL

Goal: make abusive traffic expensive while normal visitors pass quickly.


🔴 Live Production Example

A live deployment of pow-shield-php is running in production here:

https://lassiter.eu

This site uses:

  • Proof-of-Work (PoW) gateway for unauthenticated traffic
  • ModSecurity rate limiting on PoW endpoints
  • Apache connection-level protections (Slowloris / low-and-slow mitigation)
  • Cloudflare as CDN + TLS terminator (no bot challenges, no CAPTCHA)

⚠️Note: Configuration values, secrets, and thresholds used on the live site are intentionally not published in this repository.


📋 Requirements

Automatic Installation

The installer handles all dependencies automatically. Simply run:

sudo ./install.sh

Manual Requirements

If installing manually, you need:

Origin

  • PHP 8+
  • HTTPS (required for Secure cookie + WebCrypto)
  • Apache 2.4+

Optional / recommended

  • ModSecurity (Apache connector + CRS optional) for rate-limiting /__ab/*
  • If behind Cloudflare: Apache mod_remoteip configured to restore the real client IP

Secret (required)

  • AB_POW_SECRET must be set in the environment
  • Minimum: 48 characters
  • Recommended: 64+ characters

📂 Repository layout

pow-shield-php/
├─ __ab/
│ ├─ pow.php
│ └─ pow-verify.php
├─ modsecurity/
│ └─ ab_pow_ratelimit.conf
├─ apache/
│ └─ sites-available/
│ ├─ example.com-redirect.conf.example
│ └─ example.com.conf.example
├─ scripts/
│ └─ rotate-pow-secret.sh.example
├─ systemd/
│ ├─ rotate-pow-secret.service.example
│ └─ rotate-pow-secret.timer.example
├─ assets/img/
│ ├─ README.md
│ └─ .gitkeep
├─ docs/
│ ├─ cloudflare-notes.md
│ ├─ installation-checklist.md
│ └─ modsecurity-global-notes.md
├─ install.sh # 🆕 Automated installer
├─ uninstall.sh # 🆕 Automated uninstaller
└─ README.md

🚀 Quick Installation

We provide automated installation scripts for easy setup:

Option A: Automated Installation (Recommended)

# Clone the repository
git clone https://github.com/AfterPacket/pow-shield-php.git
cd pow-shield-php
# Make scripts executable
chmod +x install.sh uninstall.sh
# Run interactive installer
sudo ./install.sh

📖 Full Installation Guide: See INSTALL.md for detailed instructions, troubleshooting, and advanced configuration options.

The installer will:

  • ✅ Install all required dependencies (Apache, PHP, OpenSSL)
  • ✅ Generate secure PoW secret automatically
  • ✅ Deploy PoW endpoints and assets
  • ✅ Configure Apache virtual hosts
  • ✅ Set up ModSecurity rate limiting (optional)
  • ✅ Configure Let's Encrypt SSL (optional)
  • ✅ Set up automatic secret rotation

Installation Options

Interactive Mode (Default)

sudo ./install.sh

Follow the prompts to configure your installation.

Non-Interactive with Let's Encrypt

sudo ./install.sh -d example.com -w /var/www/html -l admin@example.com -e

Non-Interactive with Existing SSL

sudo ./install.sh -d example.com -w /var/www/html \
-c /etc/ssl/certs/cert.pem -k /etc/ssl/private/key.pem -e

Skip ModSecurity

sudo ./install.sh -d example.com -w /var/www/html -s

Installation Flags

FlagDescription
-d, --domainDomain name (e.g., example.com)
-w, --webrootWeb root directory path
-c, --certSSL certificate path (optional)
-k, --keySSL key path (optional)
-l, --letsencryptUse Let's Encrypt with email
-e, --enableEnable site with a2ensite after install
-s, --skip-modsecSkip ModSecurity installation
-n, --non-interactiveRun without prompts
-h, --helpShow help message

🗑️ Uninstallation

To completely remove pow-shield-php:

# Interactive uninstaller
sudo ./uninstall.sh
# Force removal without prompts
sudo ./uninstall.sh -d example.com -w /var/www/html -f
# Keep the PoW secret file
sudo ./uninstall.sh -d example.com -w /var/www/html -k
# Also remove ModSecurity rules
sudo ./uninstall.sh -d example.com -w /var/www/html -m

The uninstaller will:

  • ✅ Backup all files before removal
  • ✅ Disable and remove virtual hosts
  • ✅ Remove PoW endpoints
  • ✅ Remove systemd rotation (optional)
  • ✅ Remove ModSecurity rules (optional)
  • ✅ Test Apache config before reload

🛠️ Manual Installation

If you prefer manual installation:

1) Deploy /__ab/ endpoints

Copy the following files into your site webroot:

  • __ab/pow.php
  • __ab/pow-verify.php

They must resolve at:

  • https://example.com/__ab/pow.php
  • https://example.com/__ab/pow-verify.php

Tip: keep /__ab/ excluded from caching and from other WAF rules that might block POST.


2) Add the image used by pow.php (optional UI)

Your pow.php references:

/assets/img/clank.jpg

To keep this path:

  • place the image at assets/img/clank.jpg in your webroot

Or update $MEME_SRC inside __ab/pow.php.


🔐 Secret management (recommended): /etc/apache2/pow.env

Instead of embedding secrets in vhost configs, load them from a root-owned include file:

  • /etc/apache2/pow.env (root-owned, mode 600)
  • included in your HTTPS vhost via:
    IncludeOptional /etc/apache2/pow.env

Create the initial env file

sudo install -d -m 0755 /etc/apache2
sudo bash -c 'umask 077; SECRET="$(openssl rand -base64 64 | tr -d "\n")"; \ printf "%s\n" "# Managed by pow-shield-php" "SetEnv AB_POW_SECRET \"$SECRET\"" > /etc/apache2/pow.env'
sudo chown root:root /etc/apache2/pow.env
sudo chmod 600 /etc/apache2/pow.env
sudo apachectl -t
sudo systemctl reload apache2

⚠️Never commit secrets to git.


🔄 Secret rotation (optional): script + systemd service + timer

Rotating the PoW secret reduces replay value if a cookie/token leaks. To avoid breaking in-flight challenges, rotate with overlap:

  • New secret stored as AB_POW_SECRET
  • Old secret preserved as AB_POW_SECRET_PREV

✅ For this to work, your pow-verify.php should accept either secret when validating.

A) Rotation script

Save as:

/usr/local/sbin/rotate-pow-secret.sh
#!/bin/bashset -euo pipefail
OUT="/etc/apache2/pow.env"
TMP="$(mktemp)"umask 077
# Pull current secret (if any) from existing file
CURRENT=""if [[ -f"$OUT" ]];then
CURRENT="$(awk -F'"''/SetEnv[[:space:]]+AB_POW_SECRET[[:space:]]+"/ {print $2; exit}'"$OUT"|| true)"fi
NEW="$(openssl rand -base64 64 | tr -d '\n')"
{
echo'# Managed by rotate-pow-secret.sh'echo"SetEnv AB_POW_SECRET \"$NEW\""if [[ -n"${CURRENT}" ]];thenecho"SetEnv AB_POW_SECRET_PREV \"$CURRENT\""fi
} >"$TMP"
chown root:root "$TMP"
chmod 600 "$TMP"
mv -f "$TMP""$OUT"# Safety: verify Apache config first
apachectl -t
# Reload, not restart (keeps connections)
systemctl reload apache2

Install + test:

sudo install -m 0755 /usr/local/sbin/rotate-pow-secret.sh /usr/local/sbin/rotate-pow-secret.sh
sudo /usr/local/sbin/rotate-pow-secret.sh

B) systemd service

Create:

/etc/systemd/system/rotate-pow-secret.service
[Unit]Description=Rotate AB_POW_SECRET for pow-shield-php and reload Apache
Wants=apache2.service
After=apache2.service
[Service]Type=oneshot
ExecStart=/usr/local/sbin/rotate-pow-secret.sh
User=root
Group=root
# Hardening (safe defaults)NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/etc/apache2/pow.env

C) systemd timer (hourly)

Create:

/etc/systemd/system/rotate-pow-secret.timer
[Unit]Description=Hourly rotation for AB_POW_SECRET (pow-shield-php)
[Timer]OnCalendar=hourly
Persistent=true
RandomizedDelaySec=120
Unit=rotate-pow-secret.service
[Install]WantedBy=timers.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable --now rotate-pow-secret.timer
sudo systemctl list-timers --all | grep rotate-pow-secret

Manual trigger:

sudo systemctl start rotate-pow-secret.service
sudo systemctl status rotate-pow-secret.service --no-pager

🌐 Install: Apache vhost (PoW gating + skip rules)

Use the sanitized examples in apache/sites-available/.

Two common patterns:

Option A — Redirect to /__ab/pow.php (visible PoW URL)

  • simplest
  • user sees /__ab/pow.php?...

Option B — Internal rewrite (clean URL)

  • keeps the original URL in the address bar
  • uses [PT] internally to serve pow.php

In both options, always skip:

  • /__ab/* (prevents loops)
  • /status/* (your private panels/JSON)
  • static assets
  • non-GET/HEAD methods

🛡️ ModSecurity: rate-limit only the PoW endpoints (recommended)

Rules are provided in:

modsecurity/ab_pow_ratelimit.conf

A) Install ModSecurity (Debian/Ubuntu)

sudo apt update
sudo apt install -y libapache2-mod-security2
sudo a2enmod security2
sudo systemctl reload apache2

Confirm:

apachectl -M | grep -i security

B) Enable engine

In /etc/modsecurity/modsecurity.conf:

SecRuleEngine On
SecRequestBodyAccess On

Reload:

sudo systemctl reload apache2

C) Include PoW rules

Copy:

sudo mkdir -p /etc/modsecurity
sudo cp modsecurity/ab_pow_ratelimit.conf /etc/modsecurity/ab_pow_ratelimit.conf

Then include it in your vhost or global security2 config:

IncludeOptional /etc/modsecurity/ab_pow_ratelimit.conf
Header always set Retry-After "30" env=AB_RL

D) Verify enforcement

foriin$(seq 1 80);do
curl -sk https://example.com/__ab/pow.php?next=/ >/dev/null -w "%{http_code}\n"done

You should see 429 once the limit triggers.


🚨 Additional DDoS Mitigation (Apache-level)

PoW is application-layer cost. It helps with:

  • Basic bot spam
  • Naive request floods
  • Large-scale scraping (makes it expensive per request)

It does not stop all L7 attacks by itself. Pair it with:

  • ModSecurity rate limiting (especially on /__ab/pow-verify.php)
  • mod_reqtimeout (Slowloris mitigation)
  • Connection limits / MPM tuning
  • Correct real-IP restoration when behind Cloudflare

📝 Note: Pattern matters more than specific values; deploy thresholds appropriate to your traffic.


☁️ Cloudflare (recommended configuration)

See docs/cloudflare-notes.md.

Important settings:

  • Bot Fight Mode / "Stop Bot Attack": OFF (can interfere with PoW)
  • 🚫 Cache bypass for:
    • /__ab/pow.php
    • /__ab/pow-verify.php
  • 🌍 Restore real client IP at the origin using mod_remoteip

🔧 Troubleshooting

Infinite "Checking your browser…" loop

Common causes:

  • Cloudflare caching PoW endpoints
  • Cloudflare bot challenges enabled
  • Cookies blocked by browser
  • WAF blocking /__ab/pow-verify.php
  • Using PoW as an ErrorDocument 403 (can recurse)

Fix:

  • Disable Bot Fight / Stop Bot Attack
  • Bypass cache for PoW endpoints
  • Confirm Set-Cookie: abp=... is issued over HTTPS
  • Don't use PoW as 403 handler; use a static error page instead

LibreWolf / hardened Firefox shows "slow-device"

  • Lower difficulty for hardened UAs (or remove the "hard fail")
  • Extend TTL for challenge tokens
  • Ensure cookies aren't blocked for the site

Getting HTTP 429 during testing

  • ModSecurity limits are working as intended
  • Wait for the window to expire (often 60 seconds)

🔒 Security notes

  • AB_POW_SECRET must be long and random (>= 48 chars; 64+ recommended)
  • Never commit secrets to git
  • Consider rotating the secret with overlap (AB_POW_SECRET_PREV) to reduce replay value
  • Keep PoW endpoints uncached and allow POST to /__ab/pow-verify.php
  • If behind Cloudflare, configure real IP restoration before using per-IP rate limits

🧩 Contributing

Contributions are welcome! To participate:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-enhancement
  3. Commit your changes: git commit -m "Add: your feature"
  4. Push to your fork: git push origin feature/your-enhancement
  5. Open a Pull Request

🐛 Issues & Support

Found a bug or have a feature request? Please open an issue with:

  • Steps to reproduce
  • Expected vs actual behavior
  • PHP and Apache versions
  • Operating system

📄 License

This project is licensed under the GNU General Public License v3.0.
See the LICENSE file for full details.


Enjoy Fighting BOTS 🤖🛡️

About

A lightweight PHP proof-of-work gateway that issues a signed cookie, with Cloudflare-friendly ModSecurity rate limiting and Apache vhost examples.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

pow-shield-php

License: GPL-3.0PHP 8+Apache 2.4+

A lightweight Proof-of-Work (PoW) gateway for PHP sites that reduces abusive traffic without CAPTCHAs.
It issues a signed cookie (abp) after a browser completes a SHA-256 work check, then allows normal access.

✨ What's Included

This repository includes:

  • ✅ PoW challenge page: __ab/pow.php
  • ✅ PoW verifier + signed cookie: __ab/pow-verify.php
  • ✅ ModSecurity rate limits for PoW endpoints: modsecurity/ab_pow_ratelimit.conf
  • ✅ Apache vhost examples (sanitized to example.com) with PoW "skip" rules + clean URL option
  • ✅ Cloudflare compatibility notes (cache bypass + real client IP restore)
  • ✅ Secret rotation script + systemd service/timer examples

This repository intentionally excludes:

  • ❌ TLS certificates / private keys
  • ❌ secrets (your AB_POW_SECRET)
  • ❌ server logs / user data

🔄 How it works (request flow)

  1. A client requests a protected URL and does not have cookie abp
  2. Apache rewrites/redirects them to:
    /__ab/pow.php?next=/original/path&qs=original=query
    
  3. pow.php runs PoW in the browser:
    • compute sha256(TOKEN + "." + counter) until it has enough leading zero bits
  4. Browser submits the solution to:
    /__ab/pow-verify.php
    
  5. Server verifies:
    • token integrity (HMAC)
    • user-agent binding (light)
    • PoW difficulty (leading zero bits)
  6. Server sets cookie:
    • abp=<signed value> (Secure, HttpOnly, SameSite=Lax)
  7. Browser is redirected back to the original URL

Goal: make abusive traffic expensive while normal visitors pass quickly.


🔴 Live Production Example

A live deployment of pow-shield-php is running in production here:

https://lassiter.eu

This site uses:

  • Proof-of-Work (PoW) gateway for unauthenticated traffic
  • ModSecurity rate limiting on PoW endpoints
  • Apache connection-level protections (Slowloris / low-and-slow mitigation)
  • Cloudflare as CDN + TLS terminator (no bot challenges, no CAPTCHA)

⚠️Note: Configuration values, secrets, and thresholds used on the live site are intentionally not published in this repository.


📋 Requirements

Automatic Installation

The installer handles all dependencies automatically. Simply run:

sudo ./install.sh

Manual Requirements

If installing manually, you need:

Origin

  • PHP 8+
  • HTTPS (required for Secure cookie + WebCrypto)
  • Apache 2.4+

Optional / recommended

  • ModSecurity (Apache connector + CRS optional) for rate-limiting /__ab/*
  • If behind Cloudflare: Apache mod_remoteip configured to restore the real client IP

Secret (required)

  • AB_POW_SECRET must be set in the environment
  • Minimum: 48 characters
  • Recommended: 64+ characters

📂 Repository layout

pow-shield-php/
├─ __ab/
│ ├─ pow.php
│ └─ pow-verify.php
├─ modsecurity/
│ └─ ab_pow_ratelimit.conf
├─ apache/
│ └─ sites-available/
│ ├─ example.com-redirect.conf.example
│ └─ example.com.conf.example
├─ scripts/
│ └─ rotate-pow-secret.sh.example
├─ systemd/
│ ├─ rotate-pow-secret.service.example
│ └─ rotate-pow-secret.timer.example
├─ assets/img/
│ ├─ README.md
│ └─ .gitkeep
├─ docs/
│ ├─ cloudflare-notes.md
│ ├─ installation-checklist.md
│ └─ modsecurity-global-notes.md
├─ install.sh # 🆕 Automated installer
├─ uninstall.sh # 🆕 Automated uninstaller
└─ README.md

🚀 Quick Installation

We provide automated installation scripts for easy setup:

Option A: Automated Installation (Recommended)

# Clone the repository
git clone https://github.com/AfterPacket/pow-shield-php.git
cd pow-shield-php
# Make scripts executable
chmod +x install.sh uninstall.sh
# Run interactive installer
sudo ./install.sh

📖 Full Installation Guide: See INSTALL.md for detailed instructions, troubleshooting, and advanced configuration options.

The installer will:

  • ✅ Install all required dependencies (Apache, PHP, OpenSSL)
  • ✅ Generate secure PoW secret automatically
  • ✅ Deploy PoW endpoints and assets
  • ✅ Configure Apache virtual hosts
  • ✅ Set up ModSecurity rate limiting (optional)
  • ✅ Configure Let's Encrypt SSL (optional)
  • ✅ Set up automatic secret rotation

Installation Options

Interactive Mode (Default)

sudo ./install.sh

Follow the prompts to configure your installation.

Non-Interactive with Let's Encrypt

sudo ./install.sh -d example.com -w /var/www/html -l admin@example.com -e

Non-Interactive with Existing SSL

sudo ./install.sh -d example.com -w /var/www/html \
-c /etc/ssl/certs/cert.pem -k /etc/ssl/private/key.pem -e

Skip ModSecurity

sudo ./install.sh -d example.com -w /var/www/html -s

Installation Flags

FlagDescription
-d, --domainDomain name (e.g., example.com)
-w, --webrootWeb root directory path
-c, --certSSL certificate path (optional)
-k, --keySSL key path (optional)
-l, --letsencryptUse Let's Encrypt with email
-e, --enableEnable site with a2ensite after install
-s, --skip-modsecSkip ModSecurity installation
-n, --non-interactiveRun without prompts
-h, --helpShow help message

🗑️ Uninstallation

To completely remove pow-shield-php:

# Interactive uninstaller
sudo ./uninstall.sh
# Force removal without prompts
sudo ./uninstall.sh -d example.com -w /var/www/html -f
# Keep the PoW secret file
sudo ./uninstall.sh -d example.com -w /var/www/html -k
# Also remove ModSecurity rules
sudo ./uninstall.sh -d example.com -w /var/www/html -m

The uninstaller will:

  • ✅ Backup all files before removal
  • ✅ Disable and remove virtual hosts
  • ✅ Remove PoW endpoints
  • ✅ Remove systemd rotation (optional)
  • ✅ Remove ModSecurity rules (optional)
  • ✅ Test Apache config before reload

🛠️ Manual Installation

If you prefer manual installation:

1) Deploy /__ab/ endpoints

Copy the following files into your site webroot:

  • __ab/pow.php
  • __ab/pow-verify.php

They must resolve at:

  • https://example.com/__ab/pow.php
  • https://example.com/__ab/pow-verify.php

Tip: keep /__ab/ excluded from caching and from other WAF rules that might block POST.


2) Add the image used by pow.php (optional UI)

Your pow.php references:

/assets/img/clank.jpg

To keep this path:

  • place the image at assets/img/clank.jpg in your webroot

Or update $MEME_SRC inside __ab/pow.php.


🔐 Secret management (recommended): /etc/apache2/pow.env

Instead of embedding secrets in vhost configs, load them from a root-owned include file:

  • /etc/apache2/pow.env (root-owned, mode 600)
  • included in your HTTPS vhost via:
    IncludeOptional /etc/apache2/pow.env

Create the initial env file

sudo install -d -m 0755 /etc/apache2
sudo bash -c 'umask 077; SECRET="$(openssl rand -base64 64 | tr -d "\n")"; \ printf "%s\n" "# Managed by pow-shield-php" "SetEnv AB_POW_SECRET \"$SECRET\"" > /etc/apache2/pow.env'
sudo chown root:root /etc/apache2/pow.env
sudo chmod 600 /etc/apache2/pow.env
sudo apachectl -t
sudo systemctl reload apache2

⚠️Never commit secrets to git.


🔄 Secret rotation (optional): script + systemd service + timer

Rotating the PoW secret reduces replay value if a cookie/token leaks. To avoid breaking in-flight challenges, rotate with overlap:

  • New secret stored as AB_POW_SECRET
  • Old secret preserved as AB_POW_SECRET_PREV

✅ For this to work, your pow-verify.php should accept either secret when validating.

A) Rotation script

Save as:

/usr/local/sbin/rotate-pow-secret.sh
#!/bin/bashset -euo pipefail
OUT="/etc/apache2/pow.env"
TMP="$(mktemp)"umask 077
# Pull current secret (if any) from existing file
CURRENT=""if [[ -f"$OUT" ]];then
CURRENT="$(awk -F'"''/SetEnv[[:space:]]+AB_POW_SECRET[[:space:]]+"/ {print $2; exit}'"$OUT"|| true)"fi
NEW="$(openssl rand -base64 64 | tr -d '\n')"
{
echo'# Managed by rotate-pow-secret.sh'echo"SetEnv AB_POW_SECRET \"$NEW\""if [[ -n"${CURRENT}" ]];thenecho"SetEnv AB_POW_SECRET_PREV \"$CURRENT\""fi
} >"$TMP"
chown root:root "$TMP"
chmod 600 "$TMP"
mv -f "$TMP""$OUT"# Safety: verify Apache config first
apachectl -t
# Reload, not restart (keeps connections)
systemctl reload apache2

Install + test:

sudo install -m 0755 /usr/local/sbin/rotate-pow-secret.sh /usr/local/sbin/rotate-pow-secret.sh
sudo /usr/local/sbin/rotate-pow-secret.sh

B) systemd service

Create:

/etc/systemd/system/rotate-pow-secret.service
[Unit]Description=Rotate AB_POW_SECRET for pow-shield-php and reload Apache
Wants=apache2.service
After=apache2.service
[Service]Type=oneshot
ExecStart=/usr/local/sbin/rotate-pow-secret.sh
User=root
Group=root
# Hardening (safe defaults)NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/etc/apache2/pow.env

C) systemd timer (hourly)

Create:

/etc/systemd/system/rotate-pow-secret.timer
[Unit]Description=Hourly rotation for AB_POW_SECRET (pow-shield-php)
[Timer]OnCalendar=hourly
Persistent=true
RandomizedDelaySec=120
Unit=rotate-pow-secret.service
[Install]WantedBy=timers.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable --now rotate-pow-secret.timer
sudo systemctl list-timers --all | grep rotate-pow-secret

Manual trigger:

sudo systemctl start rotate-pow-secret.service
sudo systemctl status rotate-pow-secret.service --no-pager

🌐 Install: Apache vhost (PoW gating + skip rules)

Use the sanitized examples in apache/sites-available/.

Two common patterns:

Option A — Redirect to /__ab/pow.php (visible PoW URL)

  • simplest
  • user sees /__ab/pow.php?...

Option B — Internal rewrite (clean URL)

  • keeps the original URL in the address bar
  • uses [PT] internally to serve pow.php

In both options, always skip:

  • /__ab/* (prevents loops)
  • /status/* (your private panels/JSON)
  • static assets
  • non-GET/HEAD methods

🛡️ ModSecurity: rate-limit only the PoW endpoints (recommended)

Rules are provided in:

modsecurity/ab_pow_ratelimit.conf

A) Install ModSecurity (Debian/Ubuntu)

sudo apt update
sudo apt install -y libapache2-mod-security2
sudo a2enmod security2
sudo systemctl reload apache2

Confirm:

apachectl -M | grep -i security

B) Enable engine

In /etc/modsecurity/modsecurity.conf:

SecRuleEngine On
SecRequestBodyAccess On

Reload:

sudo systemctl reload apache2

C) Include PoW rules

Copy:

sudo mkdir -p /etc/modsecurity
sudo cp modsecurity/ab_pow_ratelimit.conf /etc/modsecurity/ab_pow_ratelimit.conf

Then include it in your vhost or global security2 config:

IncludeOptional /etc/modsecurity/ab_pow_ratelimit.conf
Header always set Retry-After "30" env=AB_RL

D) Verify enforcement

foriin$(seq 1 80);do
curl -sk https://example.com/__ab/pow.php?next=/ >/dev/null -w "%{http_code}\n"done

You should see 429 once the limit triggers.


🚨 Additional DDoS Mitigation (Apache-level)

PoW is application-layer cost. It helps with:

  • Basic bot spam
  • Naive request floods
  • Large-scale scraping (makes it expensive per request)

It does not stop all L7 attacks by itself. Pair it with:

  • ModSecurity rate limiting (especially on /__ab/pow-verify.php)
  • mod_reqtimeout (Slowloris mitigation)
  • Connection limits / MPM tuning
  • Correct real-IP restoration when behind Cloudflare

📝 Note: Pattern matters more than specific values; deploy thresholds appropriate to your traffic.


☁️ Cloudflare (recommended configuration)

See docs/cloudflare-notes.md.

Important settings:

  • Bot Fight Mode / "Stop Bot Attack": OFF (can interfere with PoW)
  • 🚫 Cache bypass for:
    • /__ab/pow.php
    • /__ab/pow-verify.php
  • 🌍 Restore real client IP at the origin using mod_remoteip

🔧 Troubleshooting

Infinite "Checking your browser…" loop

Common causes:

  • Cloudflare caching PoW endpoints
  • Cloudflare bot challenges enabled
  • Cookies blocked by browser
  • WAF blocking /__ab/pow-verify.php
  • Using PoW as an ErrorDocument 403 (can recurse)

Fix:

  • Disable Bot Fight / Stop Bot Attack
  • Bypass cache for PoW endpoints
  • Confirm Set-Cookie: abp=... is issued over HTTPS
  • Don't use PoW as 403 handler; use a static error page instead

LibreWolf / hardened Firefox shows "slow-device"

  • Lower difficulty for hardened UAs (or remove the "hard fail")
  • Extend TTL for challenge tokens
  • Ensure cookies aren't blocked for the site

Getting HTTP 429 during testing

  • ModSecurity limits are working as intended
  • Wait for the window to expire (often 60 seconds)

🔒 Security notes

  • AB_POW_SECRET must be long and random (>= 48 chars; 64+ recommended)
  • Never commit secrets to git
  • Consider rotating the secret with overlap (AB_POW_SECRET_PREV) to reduce replay value
  • Keep PoW endpoints uncached and allow POST to /__ab/pow-verify.php
  • If behind Cloudflare, configure real IP restoration before using per-IP rate limits

🧩 Contributing

Contributions are welcome! To participate:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-enhancement
  3. Commit your changes: git commit -m "Add: your feature"
  4. Push to your fork: git push origin feature/your-enhancement
  5. Open a Pull Request

🐛 Issues & Support

Found a bug or have a feature request? Please open an issue with:

  • Steps to reproduce
  • Expected vs actual behavior
  • PHP and Apache versions
  • Operating system

📄 License

This project is licensed under the GNU General Public License v3.0.
See the LICENSE file for full details.


Enjoy Fighting BOTS 🤖🛡️

About

A lightweight PHP proof-of-work gateway that issues a signed cookie, with Cloudflare-friendly ModSecurity rate limiting and Apache vhost examples.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

pow-shield-php

License: GPL-3.0PHP 8+Apache 2.4+

A lightweight Proof-of-Work (PoW) gateway for PHP sites that reduces abusive traffic without CAPTCHAs.
It issues a signed cookie (abp) after a browser completes a SHA-256 work check, then allows normal access.

✨ What's Included

This repository includes:

  • ✅ PoW challenge page: __ab/pow.php
  • ✅ PoW verifier + signed cookie: __ab/pow-verify.php
  • ✅ ModSecurity rate limits for PoW endpoints: modsecurity/ab_pow_ratelimit.conf
  • ✅ Apache vhost examples (sanitized to example.com) with PoW "skip" rules + clean URL option
  • ✅ Cloudflare compatibility notes (cache bypass + real client IP restore)
  • ✅ Secret rotation script + systemd service/timer examples

This repository intentionally excludes:

  • ❌ TLS certificates / private keys
  • ❌ secrets (your AB_POW_SECRET)
  • ❌ server logs / user data

🔄 How it works (request flow)

  1. A client requests a protected URL and does not have cookie abp
  2. Apache rewrites/redirects them to:
    /__ab/pow.php?next=/original/path&qs=original=query
    
  3. pow.php runs PoW in the browser:
    • compute sha256(TOKEN + "." + counter) until it has enough leading zero bits
  4. Browser submits the solution to:
    /__ab/pow-verify.php
    
  5. Server verifies:
    • token integrity (HMAC)
    • user-agent binding (light)
    • PoW difficulty (leading zero bits)
  6. Server sets cookie:
    • abp=<signed value> (Secure, HttpOnly, SameSite=Lax)
  7. Browser is redirected back to the original URL

Goal: make abusive traffic expensive while normal visitors pass quickly.


🔴 Live Production Example

A live deployment of pow-shield-php is running in production here:

https://lassiter.eu

This site uses:

  • Proof-of-Work (PoW) gateway for unauthenticated traffic
  • ModSecurity rate limiting on PoW endpoints
  • Apache connection-level protections (Slowloris / low-and-slow mitigation)
  • Cloudflare as CDN + TLS terminator (no bot challenges, no CAPTCHA)

⚠️Note: Configuration values, secrets, and thresholds used on the live site are intentionally not published in this repository.


📋 Requirements

Automatic Installation

The installer handles all dependencies automatically. Simply run:

sudo ./install.sh

Manual Requirements

If installing manually, you need:

Origin

  • PHP 8+
  • HTTPS (required for Secure cookie + WebCrypto)
  • Apache 2.4+

Optional / recommended

  • ModSecurity (Apache connector + CRS optional) for rate-limiting /__ab/*
  • If behind Cloudflare: Apache mod_remoteip configured to restore the real client IP

Secret (required)

  • AB_POW_SECRET must be set in the environment
  • Minimum: 48 characters
  • Recommended: 64+ characters

📂 Repository layout

pow-shield-php/
├─ __ab/
│ ├─ pow.php
│ └─ pow-verify.php
├─ modsecurity/
│ └─ ab_pow_ratelimit.conf
├─ apache/
│ └─ sites-available/
│ ├─ example.com-redirect.conf.example
│ └─ example.com.conf.example
├─ scripts/
│ └─ rotate-pow-secret.sh.example
├─ systemd/
│ ├─ rotate-pow-secret.service.example
│ └─ rotate-pow-secret.timer.example
├─ assets/img/
│ ├─ README.md
│ └─ .gitkeep
├─ docs/
│ ├─ cloudflare-notes.md
│ ├─ installation-checklist.md
│ └─ modsecurity-global-notes.md
├─ install.sh # 🆕 Automated installer
├─ uninstall.sh # 🆕 Automated uninstaller
└─ README.md

🚀 Quick Installation

We provide automated installation scripts for easy setup:

Option A: Automated Installation (Recommended)

# Clone the repository
git clone https://github.com/AfterPacket/pow-shield-php.git
cd pow-shield-php
# Make scripts executable
chmod +x install.sh uninstall.sh
# Run interactive installer
sudo ./install.sh

📖 Full Installation Guide: See INSTALL.md for detailed instructions, troubleshooting, and advanced configuration options.

The installer will:

  • ✅ Install all required dependencies (Apache, PHP, OpenSSL)
  • ✅ Generate secure PoW secret automatically
  • ✅ Deploy PoW endpoints and assets
  • ✅ Configure Apache virtual hosts
  • ✅ Set up ModSecurity rate limiting (optional)
  • ✅ Configure Let's Encrypt SSL (optional)
  • ✅ Set up automatic secret rotation

Installation Options

Interactive Mode (Default)

sudo ./install.sh

Follow the prompts to configure your installation.

Non-Interactive with Let's Encrypt

sudo ./install.sh -d example.com -w /var/www/html -l admin@example.com -e

Non-Interactive with Existing SSL

sudo ./install.sh -d example.com -w /var/www/html \
-c /etc/ssl/certs/cert.pem -k /etc/ssl/private/key.pem -e

Skip ModSecurity

sudo ./install.sh -d example.com -w /var/www/html -s

Installation Flags

FlagDescription
-d, --domainDomain name (e.g., example.com)
-w, --webrootWeb root directory path
-c, --certSSL certificate path (optional)
-k, --keySSL key path (optional)
-l, --letsencryptUse Let's Encrypt with email
-e, --enableEnable site with a2ensite after install
-s, --skip-modsecSkip ModSecurity installation
-n, --non-interactiveRun without prompts
-h, --helpShow help message

🗑️ Uninstallation

To completely remove pow-shield-php:

# Interactive uninstaller
sudo ./uninstall.sh
# Force removal without prompts
sudo ./uninstall.sh -d example.com -w /var/www/html -f
# Keep the PoW secret file
sudo ./uninstall.sh -d example.com -w /var/www/html -k
# Also remove ModSecurity rules
sudo ./uninstall.sh -d example.com -w /var/www/html -m

The uninstaller will:

  • ✅ Backup all files before removal
  • ✅ Disable and remove virtual hosts
  • ✅ Remove PoW endpoints
  • ✅ Remove systemd rotation (optional)
  • ✅ Remove ModSecurity rules (optional)
  • ✅ Test Apache config before reload

🛠️ Manual Installation

If you prefer manual installation:

1) Deploy /__ab/ endpoints

Copy the following files into your site webroot:

  • __ab/pow.php
  • __ab/pow-verify.php

They must resolve at:

  • https://example.com/__ab/pow.php
  • https://example.com/__ab/pow-verify.php

Tip: keep /__ab/ excluded from caching and from other WAF rules that might block POST.


2) Add the image used by pow.php (optional UI)

Your pow.php references:

/assets/img/clank.jpg

To keep this path:

  • place the image at assets/img/clank.jpg in your webroot

Or update $MEME_SRC inside __ab/pow.php.


🔐 Secret management (recommended): /etc/apache2/pow.env

Instead of embedding secrets in vhost configs, load them from a root-owned include file:

  • /etc/apache2/pow.env (root-owned, mode 600)
  • included in your HTTPS vhost via:
    IncludeOptional /etc/apache2/pow.env

Create the initial env file

sudo install -d -m 0755 /etc/apache2
sudo bash -c 'umask 077; SECRET="$(openssl rand -base64 64 | tr -d "\n")"; \ printf "%s\n" "# Managed by pow-shield-php" "SetEnv AB_POW_SECRET \"$SECRET\"" > /etc/apache2/pow.env'
sudo chown root:root /etc/apache2/pow.env
sudo chmod 600 /etc/apache2/pow.env
sudo apachectl -t
sudo systemctl reload apache2

⚠️Never commit secrets to git.


🔄 Secret rotation (optional): script + systemd service + timer

Rotating the PoW secret reduces replay value if a cookie/token leaks. To avoid breaking in-flight challenges, rotate with overlap:

  • New secret stored as AB_POW_SECRET
  • Old secret preserved as AB_POW_SECRET_PREV

✅ For this to work, your pow-verify.php should accept either secret when validating.

A) Rotation script

Save as:

/usr/local/sbin/rotate-pow-secret.sh
#!/bin/bashset -euo pipefail
OUT="/etc/apache2/pow.env"
TMP="$(mktemp)"umask 077
# Pull current secret (if any) from existing file
CURRENT=""if [[ -f"$OUT" ]];then
CURRENT="$(awk -F'"''/SetEnv[[:space:]]+AB_POW_SECRET[[:space:]]+"/ {print $2; exit}'"$OUT"|| true)"fi
NEW="$(openssl rand -base64 64 | tr -d '\n')"
{
echo'# Managed by rotate-pow-secret.sh'echo"SetEnv AB_POW_SECRET \"$NEW\""if [[ -n"${CURRENT}" ]];thenecho"SetEnv AB_POW_SECRET_PREV \"$CURRENT\""fi
} >"$TMP"
chown root:root "$TMP"
chmod 600 "$TMP"
mv -f "$TMP""$OUT"# Safety: verify Apache config first
apachectl -t
# Reload, not restart (keeps connections)
systemctl reload apache2

Install + test:

sudo install -m 0755 /usr/local/sbin/rotate-pow-secret.sh /usr/local/sbin/rotate-pow-secret.sh
sudo /usr/local/sbin/rotate-pow-secret.sh

B) systemd service

Create:

/etc/systemd/system/rotate-pow-secret.service
[Unit]Description=Rotate AB_POW_SECRET for pow-shield-php and reload Apache
Wants=apache2.service
After=apache2.service
[Service]Type=oneshot
ExecStart=/usr/local/sbin/rotate-pow-secret.sh
User=root
Group=root
# Hardening (safe defaults)NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/etc/apache2/pow.env

C) systemd timer (hourly)

Create:

/etc/systemd/system/rotate-pow-secret.timer
[Unit]Description=Hourly rotation for AB_POW_SECRET (pow-shield-php)
[Timer]OnCalendar=hourly
Persistent=true
RandomizedDelaySec=120
Unit=rotate-pow-secret.service
[Install]WantedBy=timers.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable --now rotate-pow-secret.timer
sudo systemctl list-timers --all | grep rotate-pow-secret

Manual trigger:

sudo systemctl start rotate-pow-secret.service
sudo systemctl status rotate-pow-secret.service --no-pager

🌐 Install: Apache vhost (PoW gating + skip rules)

Use the sanitized examples in apache/sites-available/.

Two common patterns:

Option A — Redirect to /__ab/pow.php (visible PoW URL)

  • simplest
  • user sees /__ab/pow.php?...

Option B — Internal rewrite (clean URL)

  • keeps the original URL in the address bar
  • uses [PT] internally to serve pow.php

In both options, always skip:

  • /__ab/* (prevents loops)
  • /status/* (your private panels/JSON)
  • static assets
  • non-GET/HEAD methods

🛡️ ModSecurity: rate-limit only the PoW endpoints (recommended)

Rules are provided in:

modsecurity/ab_pow_ratelimit.conf

A) Install ModSecurity (Debian/Ubuntu)

sudo apt update
sudo apt install -y libapache2-mod-security2
sudo a2enmod security2
sudo systemctl reload apache2

Confirm:

apachectl -M | grep -i security

B) Enable engine

In /etc/modsecurity/modsecurity.conf:

SecRuleEngine On
SecRequestBodyAccess On

Reload:

sudo systemctl reload apache2

C) Include PoW rules

Copy:

sudo mkdir -p /etc/modsecurity
sudo cp modsecurity/ab_pow_ratelimit.conf /etc/modsecurity/ab_pow_ratelimit.conf

Then include it in your vhost or global security2 config:

IncludeOptional /etc/modsecurity/ab_pow_ratelimit.conf
Header always set Retry-After "30" env=AB_RL

D) Verify enforcement

foriin$(seq 1 80);do
curl -sk https://example.com/__ab/pow.php?next=/ >/dev/null -w "%{http_code}\n"done

You should see 429 once the limit triggers.


🚨 Additional DDoS Mitigation (Apache-level)

PoW is application-layer cost. It helps with:

  • Basic bot spam
  • Naive request floods
  • Large-scale scraping (makes it expensive per request)

It does not stop all L7 attacks by itself. Pair it with:

  • ModSecurity rate limiting (especially on /__ab/pow-verify.php)
  • mod_reqtimeout (Slowloris mitigation)
  • Connection limits / MPM tuning
  • Correct real-IP restoration when behind Cloudflare

📝 Note: Pattern matters more than specific values; deploy thresholds appropriate to your traffic.


☁️ Cloudflare (recommended configuration)

See docs/cloudflare-notes.md.

Important settings:

  • Bot Fight Mode / "Stop Bot Attack": OFF (can interfere with PoW)
  • 🚫 Cache bypass for:
    • /__ab/pow.php
    • /__ab/pow-verify.php
  • 🌍 Restore real client IP at the origin using mod_remoteip

🔧 Troubleshooting

Infinite "Checking your browser…" loop

Common causes:

  • Cloudflare caching PoW endpoints
  • Cloudflare bot challenges enabled
  • Cookies blocked by browser
  • WAF blocking /__ab/pow-verify.php
  • Using PoW as an ErrorDocument 403 (can recurse)

Fix:

  • Disable Bot Fight / Stop Bot Attack
  • Bypass cache for PoW endpoints
  • Confirm Set-Cookie: abp=... is issued over HTTPS
  • Don't use PoW as 403 handler; use a static error page instead

LibreWolf / hardened Firefox shows "slow-device"

  • Lower difficulty for hardened UAs (or remove the "hard fail")
  • Extend TTL for challenge tokens
  • Ensure cookies aren't blocked for the site

Getting HTTP 429 during testing

  • ModSecurity limits are working as intended
  • Wait for the window to expire (often 60 seconds)

🔒 Security notes

  • AB_POW_SECRET must be long and random (>= 48 chars; 64+ recommended)
  • Never commit secrets to git
  • Consider rotating the secret with overlap (AB_POW_SECRET_PREV) to reduce replay value
  • Keep PoW endpoints uncached and allow POST to /__ab/pow-verify.php
  • If behind Cloudflare, configure real IP restoration before using per-IP rate limits

🧩 Contributing

Contributions are welcome! To participate:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-enhancement
  3. Commit your changes: git commit -m "Add: your feature"
  4. Push to your fork: git push origin feature/your-enhancement
  5. Open a Pull Request

🐛 Issues & Support

Found a bug or have a feature request? Please open an issue with:

  • Steps to reproduce
  • Expected vs actual behavior
  • PHP and Apache versions
  • Operating system

📄 License

This project is licensed under the GNU General Public License v3.0.
See the LICENSE file for full details.


Enjoy Fighting BOTS 🤖🛡️

About

A lightweight PHP proof-of-work gateway that issues a signed cookie, with Cloudflare-friendly ModSecurity rate limiting and Apache vhost examples.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

pow-shield-php

License: GPL-3.0PHP 8+Apache 2.4+

A lightweight Proof-of-Work (PoW) gateway for PHP sites that reduces abusive traffic without CAPTCHAs.
It issues a signed cookie (abp) after a browser completes a SHA-256 work check, then allows normal access.

✨ What's Included

This repository includes:

  • ✅ PoW challenge page: __ab/pow.php
  • ✅ PoW verifier + signed cookie: __ab/pow-verify.php
  • ✅ ModSecurity rate limits for PoW endpoints: modsecurity/ab_pow_ratelimit.conf
  • ✅ Apache vhost examples (sanitized to example.com) with PoW "skip" rules + clean URL option
  • ✅ Cloudflare compatibility notes (cache bypass + real client IP restore)
  • ✅ Secret rotation script + systemd service/timer examples

This repository intentionally excludes:

  • ❌ TLS certificates / private keys
  • ❌ secrets (your AB_POW_SECRET)
  • ❌ server logs / user data

🔄 How it works (request flow)

  1. A client requests a protected URL and does not have cookie abp
  2. Apache rewrites/redirects them to:
    /__ab/pow.php?next=/original/path&qs=original=query
    
  3. pow.php runs PoW in the browser:
    • compute sha256(TOKEN + "." + counter) until it has enough leading zero bits
  4. Browser submits the solution to:
    /__ab/pow-verify.php
    
  5. Server verifies:
    • token integrity (HMAC)
    • user-agent binding (light)
    • PoW difficulty (leading zero bits)
  6. Server sets cookie:
    • abp=<signed value> (Secure, HttpOnly, SameSite=Lax)
  7. Browser is redirected back to the original URL

Goal: make abusive traffic expensive while normal visitors pass quickly.


🔴 Live Production Example

A live deployment of pow-shield-php is running in production here:

https://lassiter.eu

This site uses:

  • Proof-of-Work (PoW) gateway for unauthenticated traffic
  • ModSecurity rate limiting on PoW endpoints
  • Apache connection-level protections (Slowloris / low-and-slow mitigation)
  • Cloudflare as CDN + TLS terminator (no bot challenges, no CAPTCHA)

⚠️Note: Configuration values, secrets, and thresholds used on the live site are intentionally not published in this repository.


📋 Requirements

Automatic Installation

The installer handles all dependencies automatically. Simply run:

sudo ./install.sh

Manual Requirements

If installing manually, you need:

Origin

  • PHP 8+
  • HTTPS (required for Secure cookie + WebCrypto)
  • Apache 2.4+

Optional / recommended

  • ModSecurity (Apache connector + CRS optional) for rate-limiting /__ab/*
  • If behind Cloudflare: Apache mod_remoteip configured to restore the real client IP

Secret (required)

  • AB_POW_SECRET must be set in the environment
  • Minimum: 48 characters
  • Recommended: 64+ characters

📂 Repository layout

pow-shield-php/
├─ __ab/
│ ├─ pow.php
│ └─ pow-verify.php
├─ modsecurity/
│ └─ ab_pow_ratelimit.conf
├─ apache/
│ └─ sites-available/
│ ├─ example.com-redirect.conf.example
│ └─ example.com.conf.example
├─ scripts/
│ └─ rotate-pow-secret.sh.example
├─ systemd/
│ ├─ rotate-pow-secret.service.example
│ └─ rotate-pow-secret.timer.example
├─ assets/img/
│ ├─ README.md
│ └─ .gitkeep
├─ docs/
│ ├─ cloudflare-notes.md
│ ├─ installation-checklist.md
│ └─ modsecurity-global-notes.md
├─ install.sh # 🆕 Automated installer
├─ uninstall.sh # 🆕 Automated uninstaller
└─ README.md

🚀 Quick Installation

We provide automated installation scripts for easy setup:

Option A: Automated Installation (Recommended)

# Clone the repository
git clone https://github.com/AfterPacket/pow-shield-php.git
cd pow-shield-php
# Make scripts executable
chmod +x install.sh uninstall.sh
# Run interactive installer
sudo ./install.sh

📖 Full Installation Guide: See INSTALL.md for detailed instructions, troubleshooting, and advanced configuration options.

The installer will:

  • ✅ Install all required dependencies (Apache, PHP, OpenSSL)
  • ✅ Generate secure PoW secret automatically
  • ✅ Deploy PoW endpoints and assets
  • ✅ Configure Apache virtual hosts
  • ✅ Set up ModSecurity rate limiting (optional)
  • ✅ Configure Let's Encrypt SSL (optional)
  • ✅ Set up automatic secret rotation

Installation Options

Interactive Mode (Default)

sudo ./install.sh

Follow the prompts to configure your installation.

Non-Interactive with Let's Encrypt

sudo ./install.sh -d example.com -w /var/www/html -l admin@example.com -e

Non-Interactive with Existing SSL

sudo ./install.sh -d example.com -w /var/www/html \
-c /etc/ssl/certs/cert.pem -k /etc/ssl/private/key.pem -e

Skip ModSecurity

sudo ./install.sh -d example.com -w /var/www/html -s

Installation Flags

FlagDescription
-d, --domainDomain name (e.g., example.com)
-w, --webrootWeb root directory path
-c, --certSSL certificate path (optional)
-k, --keySSL key path (optional)
-l, --letsencryptUse Let's Encrypt with email
-e, --enableEnable site with a2ensite after install
-s, --skip-modsecSkip ModSecurity installation
-n, --non-interactiveRun without prompts
-h, --helpShow help message

🗑️ Uninstallation

To completely remove pow-shield-php:

# Interactive uninstaller
sudo ./uninstall.sh
# Force removal without prompts
sudo ./uninstall.sh -d example.com -w /var/www/html -f
# Keep the PoW secret file
sudo ./uninstall.sh -d example.com -w /var/www/html -k
# Also remove ModSecurity rules
sudo ./uninstall.sh -d example.com -w /var/www/html -m

The uninstaller will:

  • ✅ Backup all files before removal
  • ✅ Disable and remove virtual hosts
  • ✅ Remove PoW endpoints
  • ✅ Remove systemd rotation (optional)
  • ✅ Remove ModSecurity rules (optional)
  • ✅ Test Apache config before reload

🛠️ Manual Installation

If you prefer manual installation:

1) Deploy /__ab/ endpoints

Copy the following files into your site webroot:

  • __ab/pow.php
  • __ab/pow-verify.php

They must resolve at:

  • https://example.com/__ab/pow.php
  • https://example.com/__ab/pow-verify.php

Tip: keep /__ab/ excluded from caching and from other WAF rules that might block POST.


2) Add the image used by pow.php (optional UI)

Your pow.php references:

/assets/img/clank.jpg

To keep this path:

  • place the image at assets/img/clank.jpg in your webroot

Or update $MEME_SRC inside __ab/pow.php.


🔐 Secret management (recommended): /etc/apache2/pow.env

Instead of embedding secrets in vhost configs, load them from a root-owned include file:

  • /etc/apache2/pow.env (root-owned, mode 600)
  • included in your HTTPS vhost via:
    IncludeOptional /etc/apache2/pow.env

Create the initial env file

sudo install -d -m 0755 /etc/apache2
sudo bash -c 'umask 077; SECRET="$(openssl rand -base64 64 | tr -d "\n")"; \ printf "%s\n" "# Managed by pow-shield-php" "SetEnv AB_POW_SECRET \"$SECRET\"" > /etc/apache2/pow.env'
sudo chown root:root /etc/apache2/pow.env
sudo chmod 600 /etc/apache2/pow.env
sudo apachectl -t
sudo systemctl reload apache2

⚠️Never commit secrets to git.


🔄 Secret rotation (optional): script + systemd service + timer

Rotating the PoW secret reduces replay value if a cookie/token leaks. To avoid breaking in-flight challenges, rotate with overlap:

  • New secret stored as AB_POW_SECRET
  • Old secret preserved as AB_POW_SECRET_PREV

✅ For this to work, your pow-verify.php should accept either secret when validating.

A) Rotation script

Save as:

/usr/local/sbin/rotate-pow-secret.sh
#!/bin/bashset -euo pipefail
OUT="/etc/apache2/pow.env"
TMP="$(mktemp)"umask 077
# Pull current secret (if any) from existing file
CURRENT=""if [[ -f"$OUT" ]];then
CURRENT="$(awk -F'"''/SetEnv[[:space:]]+AB_POW_SECRET[[:space:]]+"/ {print $2; exit}'"$OUT"|| true)"fi
NEW="$(openssl rand -base64 64 | tr -d '\n')"
{
echo'# Managed by rotate-pow-secret.sh'echo"SetEnv AB_POW_SECRET \"$NEW\""if [[ -n"${CURRENT}" ]];thenecho"SetEnv AB_POW_SECRET_PREV \"$CURRENT\""fi
} >"$TMP"
chown root:root "$TMP"
chmod 600 "$TMP"
mv -f "$TMP""$OUT"# Safety: verify Apache config first
apachectl -t
# Reload, not restart (keeps connections)
systemctl reload apache2

Install + test:

sudo install -m 0755 /usr/local/sbin/rotate-pow-secret.sh /usr/local/sbin/rotate-pow-secret.sh
sudo /usr/local/sbin/rotate-pow-secret.sh

B) systemd service

Create:

/etc/systemd/system/rotate-pow-secret.service
[Unit]Description=Rotate AB_POW_SECRET for pow-shield-php and reload Apache
Wants=apache2.service
After=apache2.service
[Service]Type=oneshot
ExecStart=/usr/local/sbin/rotate-pow-secret.sh
User=root
Group=root
# Hardening (safe defaults)NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/etc/apache2/pow.env

C) systemd timer (hourly)

Create:

/etc/systemd/system/rotate-pow-secret.timer
[Unit]Description=Hourly rotation for AB_POW_SECRET (pow-shield-php)
[Timer]OnCalendar=hourly
Persistent=true
RandomizedDelaySec=120
Unit=rotate-pow-secret.service
[Install]WantedBy=timers.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable --now rotate-pow-secret.timer
sudo systemctl list-timers --all | grep rotate-pow-secret

Manual trigger:

sudo systemctl start rotate-pow-secret.service
sudo systemctl status rotate-pow-secret.service --no-pager

🌐 Install: Apache vhost (PoW gating + skip rules)

Use the sanitized examples in apache/sites-available/.

Two common patterns:

Option A — Redirect to /__ab/pow.php (visible PoW URL)

  • simplest
  • user sees /__ab/pow.php?...

Option B — Internal rewrite (clean URL)

  • keeps the original URL in the address bar
  • uses [PT] internally to serve pow.php

In both options, always skip:

  • /__ab/* (prevents loops)
  • /status/* (your private panels/JSON)
  • static assets
  • non-GET/HEAD methods

🛡️ ModSecurity: rate-limit only the PoW endpoints (recommended)

Rules are provided in:

modsecurity/ab_pow_ratelimit.conf

A) Install ModSecurity (Debian/Ubuntu)

sudo apt update
sudo apt install -y libapache2-mod-security2
sudo a2enmod security2
sudo systemctl reload apache2

Confirm:

apachectl -M | grep -i security

B) Enable engine

In /etc/modsecurity/modsecurity.conf:

SecRuleEngine On
SecRequestBodyAccess On

Reload:

sudo systemctl reload apache2

C) Include PoW rules

Copy:

sudo mkdir -p /etc/modsecurity
sudo cp modsecurity/ab_pow_ratelimit.conf /etc/modsecurity/ab_pow_ratelimit.conf

Then include it in your vhost or global security2 config:

IncludeOptional /etc/modsecurity/ab_pow_ratelimit.conf
Header always set Retry-After "30" env=AB_RL

D) Verify enforcement

foriin$(seq 1 80);do
curl -sk https://example.com/__ab/pow.php?next=/ >/dev/null -w "%{http_code}\n"done

You should see 429 once the limit triggers.


🚨 Additional DDoS Mitigation (Apache-level)

PoW is application-layer cost. It helps with:

  • Basic bot spam
  • Naive request floods
  • Large-scale scraping (makes it expensive per request)

It does not stop all L7 attacks by itself. Pair it with:

  • ModSecurity rate limiting (especially on /__ab/pow-verify.php)
  • mod_reqtimeout (Slowloris mitigation)
  • Connection limits / MPM tuning
  • Correct real-IP restoration when behind Cloudflare

📝 Note: Pattern matters more than specific values; deploy thresholds appropriate to your traffic.


☁️ Cloudflare (recommended configuration)

See docs/cloudflare-notes.md.

Important settings:

  • Bot Fight Mode / "Stop Bot Attack": OFF (can interfere with PoW)
  • 🚫 Cache bypass for:
    • /__ab/pow.php
    • /__ab/pow-verify.php
  • 🌍 Restore real client IP at the origin using mod_remoteip

🔧 Troubleshooting

Infinite "Checking your browser…" loop

Common causes:

  • Cloudflare caching PoW endpoints
  • Cloudflare bot challenges enabled
  • Cookies blocked by browser
  • WAF blocking /__ab/pow-verify.php
  • Using PoW as an ErrorDocument 403 (can recurse)

Fix:

  • Disable Bot Fight / Stop Bot Attack
  • Bypass cache for PoW endpoints
  • Confirm Set-Cookie: abp=... is issued over HTTPS
  • Don't use PoW as 403 handler; use a static error page instead

LibreWolf / hardened Firefox shows "slow-device"

  • Lower difficulty for hardened UAs (or remove the "hard fail")
  • Extend TTL for challenge tokens
  • Ensure cookies aren't blocked for the site

Getting HTTP 429 during testing

  • ModSecurity limits are working as intended
  • Wait for the window to expire (often 60 seconds)

🔒 Security notes

  • AB_POW_SECRET must be long and random (>= 48 chars; 64+ recommended)
  • Never commit secrets to git
  • Consider rotating the secret with overlap (AB_POW_SECRET_PREV) to reduce replay value
  • Keep PoW endpoints uncached and allow POST to /__ab/pow-verify.php
  • If behind Cloudflare, configure real IP restoration before using per-IP rate limits

🧩 Contributing

Contributions are welcome! To participate:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-enhancement
  3. Commit your changes: git commit -m "Add: your feature"
  4. Push to your fork: git push origin feature/your-enhancement
  5. Open a Pull Request

🐛 Issues & Support

Found a bug or have a feature request? Please open an issue with:

  • Steps to reproduce
  • Expected vs actual behavior
  • PHP and Apache versions
  • Operating system

📄 License

This project is licensed under the GNU General Public License v3.0.
See the LICENSE file for full details.


Enjoy Fighting BOTS 🤖🛡️

About

A lightweight PHP proof-of-work gateway that issues a signed cookie, with Cloudflare-friendly ModSecurity rate limiting and Apache vhost examples.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

pow-shield-php

License: GPL-3.0PHP 8+Apache 2.4+

A lightweight Proof-of-Work (PoW) gateway for PHP sites that reduces abusive traffic without CAPTCHAs.
It issues a signed cookie (abp) after a browser completes a SHA-256 work check, then allows normal access.

✨ What's Included

This repository includes:

  • ✅ PoW challenge page: __ab/pow.php
  • ✅ PoW verifier + signed cookie: __ab/pow-verify.php
  • ✅ ModSecurity rate limits for PoW endpoints: modsecurity/ab_pow_ratelimit.conf
  • ✅ Apache vhost examples (sanitized to example.com) with PoW "skip" rules + clean URL option
  • ✅ Cloudflare compatibility notes (cache bypass + real client IP restore)
  • ✅ Secret rotation script + systemd service/timer examples

This repository intentionally excludes:

  • ❌ TLS certificates / private keys
  • ❌ secrets (your AB_POW_SECRET)
  • ❌ server logs / user data

🔄 How it works (request flow)

  1. A client requests a protected URL and does not have cookie abp
  2. Apache rewrites/redirects them to:
    /__ab/pow.php?next=/original/path&qs=original=query
    
  3. pow.php runs PoW in the browser:
    • compute sha256(TOKEN + "." + counter) until it has enough leading zero bits
  4. Browser submits the solution to:
    /__ab/pow-verify.php
    
  5. Server verifies:
    • token integrity (HMAC)
    • user-agent binding (light)
    • PoW difficulty (leading zero bits)
  6. Server sets cookie:
    • abp=<signed value> (Secure, HttpOnly, SameSite=Lax)
  7. Browser is redirected back to the original URL

Goal: make abusive traffic expensive while normal visitors pass quickly.


🔴 Live Production Example

A live deployment of pow-shield-php is running in production here:

https://lassiter.eu

This site uses:

  • Proof-of-Work (PoW) gateway for unauthenticated traffic
  • ModSecurity rate limiting on PoW endpoints
  • Apache connection-level protections (Slowloris / low-and-slow mitigation)
  • Cloudflare as CDN + TLS terminator (no bot challenges, no CAPTCHA)

⚠️Note: Configuration values, secrets, and thresholds used on the live site are intentionally not published in this repository.


📋 Requirements

Automatic Installation

The installer handles all dependencies automatically. Simply run:

sudo ./install.sh

Manual Requirements

If installing manually, you need:

Origin

  • PHP 8+
  • HTTPS (required for Secure cookie + WebCrypto)
  • Apache 2.4+

Optional / recommended

  • ModSecurity (Apache connector + CRS optional) for rate-limiting /__ab/*
  • If behind Cloudflare: Apache mod_remoteip configured to restore the real client IP

Secret (required)

  • AB_POW_SECRET must be set in the environment
  • Minimum: 48 characters
  • Recommended: 64+ characters

📂 Repository layout

pow-shield-php/
├─ __ab/
│ ├─ pow.php
│ └─ pow-verify.php
├─ modsecurity/
│ └─ ab_pow_ratelimit.conf
├─ apache/
│ └─ sites-available/
│ ├─ example.com-redirect.conf.example
│ └─ example.com.conf.example
├─ scripts/
│ └─ rotate-pow-secret.sh.example
├─ systemd/
│ ├─ rotate-pow-secret.service.example
│ └─ rotate-pow-secret.timer.example
├─ assets/img/
│ ├─ README.md
│ └─ .gitkeep
├─ docs/
│ ├─ cloudflare-notes.md
│ ├─ installation-checklist.md
│ └─ modsecurity-global-notes.md
├─ install.sh # 🆕 Automated installer
├─ uninstall.sh # 🆕 Automated uninstaller
└─ README.md

🚀 Quick Installation

We provide automated installation scripts for easy setup:

Option A: Automated Installation (Recommended)

# Clone the repository
git clone https://github.com/AfterPacket/pow-shield-php.git
cd pow-shield-php
# Make scripts executable
chmod +x install.sh uninstall.sh
# Run interactive installer
sudo ./install.sh

📖 Full Installation Guide: See INSTALL.md for detailed instructions, troubleshooting, and advanced configuration options.

The installer will:

  • ✅ Install all required dependencies (Apache, PHP, OpenSSL)
  • ✅ Generate secure PoW secret automatically
  • ✅ Deploy PoW endpoints and assets
  • ✅ Configure Apache virtual hosts
  • ✅ Set up ModSecurity rate limiting (optional)
  • ✅ Configure Let's Encrypt SSL (optional)
  • ✅ Set up automatic secret rotation

Installation Options

Interactive Mode (Default)

sudo ./install.sh

Follow the prompts to configure your installation.

Non-Interactive with Let's Encrypt

sudo ./install.sh -d example.com -w /var/www/html -l admin@example.com -e

Non-Interactive with Existing SSL

sudo ./install.sh -d example.com -w /var/www/html \
-c /etc/ssl/certs/cert.pem -k /etc/ssl/private/key.pem -e

Skip ModSecurity

sudo ./install.sh -d example.com -w /var/www/html -s

Installation Flags

FlagDescription
-d, --domainDomain name (e.g., example.com)
-w, --webrootWeb root directory path
-c, --certSSL certificate path (optional)
-k, --keySSL key path (optional)
-l, --letsencryptUse Let's Encrypt with email
-e, --enableEnable site with a2ensite after install
-s, --skip-modsecSkip ModSecurity installation
-n, --non-interactiveRun without prompts
-h, --helpShow help message

🗑️ Uninstallation

To completely remove pow-shield-php:

# Interactive uninstaller
sudo ./uninstall.sh
# Force removal without prompts
sudo ./uninstall.sh -d example.com -w /var/www/html -f
# Keep the PoW secret file
sudo ./uninstall.sh -d example.com -w /var/www/html -k
# Also remove ModSecurity rules
sudo ./uninstall.sh -d example.com -w /var/www/html -m

The uninstaller will:

  • ✅ Backup all files before removal
  • ✅ Disable and remove virtual hosts
  • ✅ Remove PoW endpoints
  • ✅ Remove systemd rotation (optional)
  • ✅ Remove ModSecurity rules (optional)
  • ✅ Test Apache config before reload

🛠️ Manual Installation

If you prefer manual installation:

1) Deploy /__ab/ endpoints

Copy the following files into your site webroot:

  • __ab/pow.php
  • __ab/pow-verify.php

They must resolve at:

  • https://example.com/__ab/pow.php
  • https://example.com/__ab/pow-verify.php

Tip: keep /__ab/ excluded from caching and from other WAF rules that might block POST.


2) Add the image used by pow.php (optional UI)

Your pow.php references:

/assets/img/clank.jpg

To keep this path:

  • place the image at assets/img/clank.jpg in your webroot

Or update $MEME_SRC inside __ab/pow.php.


🔐 Secret management (recommended): /etc/apache2/pow.env

Instead of embedding secrets in vhost configs, load them from a root-owned include file:

  • /etc/apache2/pow.env (root-owned, mode 600)
  • included in your HTTPS vhost via:
    IncludeOptional /etc/apache2/pow.env

Create the initial env file

sudo install -d -m 0755 /etc/apache2
sudo bash -c 'umask 077; SECRET="$(openssl rand -base64 64 | tr -d "\n")"; \ printf "%s\n" "# Managed by pow-shield-php" "SetEnv AB_POW_SECRET \"$SECRET\"" > /etc/apache2/pow.env'
sudo chown root:root /etc/apache2/pow.env
sudo chmod 600 /etc/apache2/pow.env
sudo apachectl -t
sudo systemctl reload apache2

⚠️Never commit secrets to git.


🔄 Secret rotation (optional): script + systemd service + timer

Rotating the PoW secret reduces replay value if a cookie/token leaks. To avoid breaking in-flight challenges, rotate with overlap:

  • New secret stored as AB_POW_SECRET
  • Old secret preserved as AB_POW_SECRET_PREV

✅ For this to work, your pow-verify.php should accept either secret when validating.

A) Rotation script

Save as:

/usr/local/sbin/rotate-pow-secret.sh
#!/bin/bashset -euo pipefail
OUT="/etc/apache2/pow.env"
TMP="$(mktemp)"umask 077
# Pull current secret (if any) from existing file
CURRENT=""if [[ -f"$OUT" ]];then
CURRENT="$(awk -F'"''/SetEnv[[:space:]]+AB_POW_SECRET[[:space:]]+"/ {print $2; exit}'"$OUT"|| true)"fi
NEW="$(openssl rand -base64 64 | tr -d '\n')"
{
echo'# Managed by rotate-pow-secret.sh'echo"SetEnv AB_POW_SECRET \"$NEW\""if [[ -n"${CURRENT}" ]];thenecho"SetEnv AB_POW_SECRET_PREV \"$CURRENT\""fi
} >"$TMP"
chown root:root "$TMP"
chmod 600 "$TMP"
mv -f "$TMP""$OUT"# Safety: verify Apache config first
apachectl -t
# Reload, not restart (keeps connections)
systemctl reload apache2

Install + test:

sudo install -m 0755 /usr/local/sbin/rotate-pow-secret.sh /usr/local/sbin/rotate-pow-secret.sh
sudo /usr/local/sbin/rotate-pow-secret.sh

B) systemd service

Create:

/etc/systemd/system/rotate-pow-secret.service
[Unit]Description=Rotate AB_POW_SECRET for pow-shield-php and reload Apache
Wants=apache2.service
After=apache2.service
[Service]Type=oneshot
ExecStart=/usr/local/sbin/rotate-pow-secret.sh
User=root
Group=root
# Hardening (safe defaults)NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/etc/apache2/pow.env

C) systemd timer (hourly)

Create:

/etc/systemd/system/rotate-pow-secret.timer
[Unit]Description=Hourly rotation for AB_POW_SECRET (pow-shield-php)
[Timer]OnCalendar=hourly
Persistent=true
RandomizedDelaySec=120
Unit=rotate-pow-secret.service
[Install]WantedBy=timers.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable --now rotate-pow-secret.timer
sudo systemctl list-timers --all | grep rotate-pow-secret

Manual trigger:

sudo systemctl start rotate-pow-secret.service
sudo systemctl status rotate-pow-secret.service --no-pager

🌐 Install: Apache vhost (PoW gating + skip rules)

Use the sanitized examples in apache/sites-available/.

Two common patterns:

Option A — Redirect to /__ab/pow.php (visible PoW URL)

  • simplest
  • user sees /__ab/pow.php?...

Option B — Internal rewrite (clean URL)

  • keeps the original URL in the address bar
  • uses [PT] internally to serve pow.php

In both options, always skip:

  • /__ab/* (prevents loops)
  • /status/* (your private panels/JSON)
  • static assets
  • non-GET/HEAD methods

🛡️ ModSecurity: rate-limit only the PoW endpoints (recommended)

Rules are provided in:

modsecurity/ab_pow_ratelimit.conf

A) Install ModSecurity (Debian/Ubuntu)

sudo apt update
sudo apt install -y libapache2-mod-security2
sudo a2enmod security2
sudo systemctl reload apache2

Confirm:

apachectl -M | grep -i security

B) Enable engine

In /etc/modsecurity/modsecurity.conf:

SecRuleEngine On
SecRequestBodyAccess On

Reload:

sudo systemctl reload apache2

C) Include PoW rules

Copy:

sudo mkdir -p /etc/modsecurity
sudo cp modsecurity/ab_pow_ratelimit.conf /etc/modsecurity/ab_pow_ratelimit.conf

Then include it in your vhost or global security2 config:

IncludeOptional /etc/modsecurity/ab_pow_ratelimit.conf
Header always set Retry-After "30" env=AB_RL

D) Verify enforcement

foriin$(seq 1 80);do
curl -sk https://example.com/__ab/pow.php?next=/ >/dev/null -w "%{http_code}\n"done

You should see 429 once the limit triggers.


🚨 Additional DDoS Mitigation (Apache-level)

PoW is application-layer cost. It helps with:

  • Basic bot spam
  • Naive request floods
  • Large-scale scraping (makes it expensive per request)

It does not stop all L7 attacks by itself. Pair it with:

  • ModSecurity rate limiting (especially on /__ab/pow-verify.php)
  • mod_reqtimeout (Slowloris mitigation)
  • Connection limits / MPM tuning
  • Correct real-IP restoration when behind Cloudflare

📝 Note: Pattern matters more than specific values; deploy thresholds appropriate to your traffic.


☁️ Cloudflare (recommended configuration)

See docs/cloudflare-notes.md.

Important settings:

  • Bot Fight Mode / "Stop Bot Attack": OFF (can interfere with PoW)
  • 🚫 Cache bypass for:
    • /__ab/pow.php
    • /__ab/pow-verify.php
  • 🌍 Restore real client IP at the origin using mod_remoteip

🔧 Troubleshooting

Infinite "Checking your browser…" loop

Common causes:

  • Cloudflare caching PoW endpoints
  • Cloudflare bot challenges enabled
  • Cookies blocked by browser
  • WAF blocking /__ab/pow-verify.php
  • Using PoW as an ErrorDocument 403 (can recurse)

Fix:

  • Disable Bot Fight / Stop Bot Attack
  • Bypass cache for PoW endpoints
  • Confirm Set-Cookie: abp=... is issued over HTTPS
  • Don't use PoW as 403 handler; use a static error page instead

LibreWolf / hardened Firefox shows "slow-device"

  • Lower difficulty for hardened UAs (or remove the "hard fail")
  • Extend TTL for challenge tokens
  • Ensure cookies aren't blocked for the site

Getting HTTP 429 during testing

  • ModSecurity limits are working as intended
  • Wait for the window to expire (often 60 seconds)

🔒 Security notes

  • AB_POW_SECRET must be long and random (>= 48 chars; 64+ recommended)
  • Never commit secrets to git
  • Consider rotating the secret with overlap (AB_POW_SECRET_PREV) to reduce replay value
  • Keep PoW endpoints uncached and allow POST to /__ab/pow-verify.php
  • If behind Cloudflare, configure real IP restoration before using per-IP rate limits

🧩 Contributing

Contributions are welcome! To participate:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-enhancement
  3. Commit your changes: git commit -m "Add: your feature"
  4. Push to your fork: git push origin feature/your-enhancement
  5. Open a Pull Request

🐛 Issues & Support

Found a bug or have a feature request? Please open an issue with:

  • Steps to reproduce
  • Expected vs actual behavior
  • PHP and Apache versions
  • Operating system

📄 License

This project is licensed under the GNU General Public License v3.0.
See the LICENSE file for full details.


Enjoy Fighting BOTS 🤖🛡️

About

A lightweight PHP proof-of-work gateway that issues a signed cookie, with Cloudflare-friendly ModSecurity rate limiting and Apache vhost examples.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

pow-shield-php

License: GPL-3.0PHP 8+Apache 2.4+

A lightweight Proof-of-Work (PoW) gateway for PHP sites that reduces abusive traffic without CAPTCHAs.
It issues a signed cookie (abp) after a browser completes a SHA-256 work check, then allows normal access.

✨ What's Included

This repository includes:

  • ✅ PoW challenge page: __ab/pow.php
  • ✅ PoW verifier + signed cookie: __ab/pow-verify.php
  • ✅ ModSecurity rate limits for PoW endpoints: modsecurity/ab_pow_ratelimit.conf
  • ✅ Apache vhost examples (sanitized to example.com) with PoW "skip" rules + clean URL option
  • ✅ Cloudflare compatibility notes (cache bypass + real client IP restore)
  • ✅ Secret rotation script + systemd service/timer examples

This repository intentionally excludes:

  • ❌ TLS certificates / private keys
  • ❌ secrets (your AB_POW_SECRET)
  • ❌ server logs / user data

🔄 How it works (request flow)

  1. A client requests a protected URL and does not have cookie abp
  2. Apache rewrites/redirects them to:
    /__ab/pow.php?next=/original/path&qs=original=query
    
  3. pow.php runs PoW in the browser:
    • compute sha256(TOKEN + "." + counter) until it has enough leading zero bits
  4. Browser submits the solution to:
    /__ab/pow-verify.php
    
  5. Server verifies:
    • token integrity (HMAC)
    • user-agent binding (light)
    • PoW difficulty (leading zero bits)
  6. Server sets cookie:
    • abp=<signed value> (Secure, HttpOnly, SameSite=Lax)
  7. Browser is redirected back to the original URL

Goal: make abusive traffic expensive while normal visitors pass quickly.


🔴 Live Production Example

A live deployment of pow-shield-php is running in production here:

https://lassiter.eu

This site uses:

  • Proof-of-Work (PoW) gateway for unauthenticated traffic
  • ModSecurity rate limiting on PoW endpoints
  • Apache connection-level protections (Slowloris / low-and-slow mitigation)
  • Cloudflare as CDN + TLS terminator (no bot challenges, no CAPTCHA)

⚠️Note: Configuration values, secrets, and thresholds used on the live site are intentionally not published in this repository.


📋 Requirements

Automatic Installation

The installer handles all dependencies automatically. Simply run:

sudo ./install.sh

Manual Requirements

If installing manually, you need:

Origin

  • PHP 8+
  • HTTPS (required for Secure cookie + WebCrypto)
  • Apache 2.4+

Optional / recommended

  • ModSecurity (Apache connector + CRS optional) for rate-limiting /__ab/*
  • If behind Cloudflare: Apache mod_remoteip configured to restore the real client IP

Secret (required)

  • AB_POW_SECRET must be set in the environment
  • Minimum: 48 characters
  • Recommended: 64+ characters

📂 Repository layout

pow-shield-php/
├─ __ab/
│ ├─ pow.php
│ └─ pow-verify.php
├─ modsecurity/
│ └─ ab_pow_ratelimit.conf
├─ apache/
│ └─ sites-available/
│ ├─ example.com-redirect.conf.example
│ └─ example.com.conf.example
├─ scripts/
│ └─ rotate-pow-secret.sh.example
├─ systemd/
│ ├─ rotate-pow-secret.service.example
│ └─ rotate-pow-secret.timer.example
├─ assets/img/
│ ├─ README.md
│ └─ .gitkeep
├─ docs/
│ ├─ cloudflare-notes.md
│ ├─ installation-checklist.md
│ └─ modsecurity-global-notes.md
├─ install.sh # 🆕 Automated installer
├─ uninstall.sh # 🆕 Automated uninstaller
└─ README.md

🚀 Quick Installation

We provide automated installation scripts for easy setup:

Option A: Automated Installation (Recommended)

# Clone the repository
git clone https://github.com/AfterPacket/pow-shield-php.git
cd pow-shield-php
# Make scripts executable
chmod +x install.sh uninstall.sh
# Run interactive installer
sudo ./install.sh

📖 Full Installation Guide: See INSTALL.md for detailed instructions, troubleshooting, and advanced configuration options.

The installer will:

  • ✅ Install all required dependencies (Apache, PHP, OpenSSL)
  • ✅ Generate secure PoW secret automatically
  • ✅ Deploy PoW endpoints and assets
  • ✅ Configure Apache virtual hosts
  • ✅ Set up ModSecurity rate limiting (optional)
  • ✅ Configure Let's Encrypt SSL (optional)
  • ✅ Set up automatic secret rotation

Installation Options

Interactive Mode (Default)

sudo ./install.sh

Follow the prompts to configure your installation.

Non-Interactive with Let's Encrypt

sudo ./install.sh -d example.com -w /var/www/html -l admin@example.com -e

Non-Interactive with Existing SSL

sudo ./install.sh -d example.com -w /var/www/html \
-c /etc/ssl/certs/cert.pem -k /etc/ssl/private/key.pem -e

Skip ModSecurity

sudo ./install.sh -d example.com -w /var/www/html -s

Installation Flags

FlagDescription
-d, --domainDomain name (e.g., example.com)
-w, --webrootWeb root directory path
-c, --certSSL certificate path (optional)
-k, --keySSL key path (optional)
-l, --letsencryptUse Let's Encrypt with email
-e, --enableEnable site with a2ensite after install
-s, --skip-modsecSkip ModSecurity installation
-n, --non-interactiveRun without prompts
-h, --helpShow help message

🗑️ Uninstallation

To completely remove pow-shield-php:

# Interactive uninstaller
sudo ./uninstall.sh
# Force removal without prompts
sudo ./uninstall.sh -d example.com -w /var/www/html -f
# Keep the PoW secret file
sudo ./uninstall.sh -d example.com -w /var/www/html -k
# Also remove ModSecurity rules
sudo ./uninstall.sh -d example.com -w /var/www/html -m

The uninstaller will:

  • ✅ Backup all files before removal
  • ✅ Disable and remove virtual hosts
  • ✅ Remove PoW endpoints
  • ✅ Remove systemd rotation (optional)
  • ✅ Remove ModSecurity rules (optional)
  • ✅ Test Apache config before reload

🛠️ Manual Installation

If you prefer manual installation:

1) Deploy /__ab/ endpoints

Copy the following files into your site webroot:

  • __ab/pow.php
  • __ab/pow-verify.php

They must resolve at:

  • https://example.com/__ab/pow.php
  • https://example.com/__ab/pow-verify.php

Tip: keep /__ab/ excluded from caching and from other WAF rules that might block POST.


2) Add the image used by pow.php (optional UI)

Your pow.php references:

/assets/img/clank.jpg

To keep this path:

  • place the image at assets/img/clank.jpg in your webroot

Or update $MEME_SRC inside __ab/pow.php.


🔐 Secret management (recommended): /etc/apache2/pow.env

Instead of embedding secrets in vhost configs, load them from a root-owned include file:

  • /etc/apache2/pow.env (root-owned, mode 600)
  • included in your HTTPS vhost via:
    IncludeOptional /etc/apache2/pow.env

Create the initial env file

sudo install -d -m 0755 /etc/apache2
sudo bash -c 'umask 077; SECRET="$(openssl rand -base64 64 | tr -d "\n")"; \ printf "%s\n" "# Managed by pow-shield-php" "SetEnv AB_POW_SECRET \"$SECRET\"" > /etc/apache2/pow.env'
sudo chown root:root /etc/apache2/pow.env
sudo chmod 600 /etc/apache2/pow.env
sudo apachectl -t
sudo systemctl reload apache2

⚠️Never commit secrets to git.


🔄 Secret rotation (optional): script + systemd service + timer

Rotating the PoW secret reduces replay value if a cookie/token leaks. To avoid breaking in-flight challenges, rotate with overlap:

  • New secret stored as AB_POW_SECRET
  • Old secret preserved as AB_POW_SECRET_PREV

✅ For this to work, your pow-verify.php should accept either secret when validating.

A) Rotation script

Save as:

/usr/local/sbin/rotate-pow-secret.sh
#!/bin/bashset -euo pipefail
OUT="/etc/apache2/pow.env"
TMP="$(mktemp)"umask 077
# Pull current secret (if any) from existing file
CURRENT=""if [[ -f"$OUT" ]];then
CURRENT="$(awk -F'"''/SetEnv[[:space:]]+AB_POW_SECRET[[:space:]]+"/ {print $2; exit}'"$OUT"|| true)"fi
NEW="$(openssl rand -base64 64 | tr -d '\n')"
{
echo'# Managed by rotate-pow-secret.sh'echo"SetEnv AB_POW_SECRET \"$NEW\""if [[ -n"${CURRENT}" ]];thenecho"SetEnv AB_POW_SECRET_PREV \"$CURRENT\""fi
} >"$TMP"
chown root:root "$TMP"
chmod 600 "$TMP"
mv -f "$TMP""$OUT"# Safety: verify Apache config first
apachectl -t
# Reload, not restart (keeps connections)
systemctl reload apache2

Install + test:

sudo install -m 0755 /usr/local/sbin/rotate-pow-secret.sh /usr/local/sbin/rotate-pow-secret.sh
sudo /usr/local/sbin/rotate-pow-secret.sh

B) systemd service

Create:

/etc/systemd/system/rotate-pow-secret.service
[Unit]Description=Rotate AB_POW_SECRET for pow-shield-php and reload Apache
Wants=apache2.service
After=apache2.service
[Service]Type=oneshot
ExecStart=/usr/local/sbin/rotate-pow-secret.sh
User=root
Group=root
# Hardening (safe defaults)NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/etc/apache2/pow.env

C) systemd timer (hourly)

Create:

/etc/systemd/system/rotate-pow-secret.timer
[Unit]Description=Hourly rotation for AB_POW_SECRET (pow-shield-php)
[Timer]OnCalendar=hourly
Persistent=true
RandomizedDelaySec=120
Unit=rotate-pow-secret.service
[Install]WantedBy=timers.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable --now rotate-pow-secret.timer
sudo systemctl list-timers --all | grep rotate-pow-secret

Manual trigger:

sudo systemctl start rotate-pow-secret.service
sudo systemctl status rotate-pow-secret.service --no-pager

🌐 Install: Apache vhost (PoW gating + skip rules)

Use the sanitized examples in apache/sites-available/.

Two common patterns:

Option A — Redirect to /__ab/pow.php (visible PoW URL)

  • simplest
  • user sees /__ab/pow.php?...

Option B — Internal rewrite (clean URL)

  • keeps the original URL in the address bar
  • uses [PT] internally to serve pow.php

In both options, always skip:

  • /__ab/* (prevents loops)
  • /status/* (your private panels/JSON)
  • static assets
  • non-GET/HEAD methods

🛡️ ModSecurity: rate-limit only the PoW endpoints (recommended)

Rules are provided in:

modsecurity/ab_pow_ratelimit.conf

A) Install ModSecurity (Debian/Ubuntu)

sudo apt update
sudo apt install -y libapache2-mod-security2
sudo a2enmod security2
sudo systemctl reload apache2

Confirm:

apachectl -M | grep -i security

B) Enable engine

In /etc/modsecurity/modsecurity.conf:

SecRuleEngine On
SecRequestBodyAccess On

Reload:

sudo systemctl reload apache2

C) Include PoW rules

Copy:

sudo mkdir -p /etc/modsecurity
sudo cp modsecurity/ab_pow_ratelimit.conf /etc/modsecurity/ab_pow_ratelimit.conf

Then include it in your vhost or global security2 config:

IncludeOptional /etc/modsecurity/ab_pow_ratelimit.conf
Header always set Retry-After "30" env=AB_RL

D) Verify enforcement

foriin$(seq 1 80);do
curl -sk https://example.com/__ab/pow.php?next=/ >/dev/null -w "%{http_code}\n"done

You should see 429 once the limit triggers.


🚨 Additional DDoS Mitigation (Apache-level)

PoW is application-layer cost. It helps with:

  • Basic bot spam
  • Naive request floods
  • Large-scale scraping (makes it expensive per request)

It does not stop all L7 attacks by itself. Pair it with:

  • ModSecurity rate limiting (especially on /__ab/pow-verify.php)
  • mod_reqtimeout (Slowloris mitigation)
  • Connection limits / MPM tuning
  • Correct real-IP restoration when behind Cloudflare

📝 Note: Pattern matters more than specific values; deploy thresholds appropriate to your traffic.


☁️ Cloudflare (recommended configuration)

See docs/cloudflare-notes.md.

Important settings:

  • Bot Fight Mode / "Stop Bot Attack": OFF (can interfere with PoW)
  • 🚫 Cache bypass for:
    • /__ab/pow.php
    • /__ab/pow-verify.php
  • 🌍 Restore real client IP at the origin using mod_remoteip

🔧 Troubleshooting

Infinite "Checking your browser…" loop

Common causes:

  • Cloudflare caching PoW endpoints
  • Cloudflare bot challenges enabled
  • Cookies blocked by browser
  • WAF blocking /__ab/pow-verify.php
  • Using PoW as an ErrorDocument 403 (can recurse)

Fix:

  • Disable Bot Fight / Stop Bot Attack
  • Bypass cache for PoW endpoints
  • Confirm Set-Cookie: abp=... is issued over HTTPS
  • Don't use PoW as 403 handler; use a static error page instead

LibreWolf / hardened Firefox shows "slow-device"

  • Lower difficulty for hardened UAs (or remove the "hard fail")
  • Extend TTL for challenge tokens
  • Ensure cookies aren't blocked for the site

Getting HTTP 429 during testing

  • ModSecurity limits are working as intended
  • Wait for the window to expire (often 60 seconds)

🔒 Security notes

  • AB_POW_SECRET must be long and random (>= 48 chars; 64+ recommended)
  • Never commit secrets to git
  • Consider rotating the secret with overlap (AB_POW_SECRET_PREV) to reduce replay value
  • Keep PoW endpoints uncached and allow POST to /__ab/pow-verify.php
  • If behind Cloudflare, configure real IP restoration before using per-IP rate limits

🧩 Contributing

Contributions are welcome! To participate:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-enhancement
  3. Commit your changes: git commit -m "Add: your feature"
  4. Push to your fork: git push origin feature/your-enhancement
  5. Open a Pull Request

🐛 Issues & Support

Found a bug or have a feature request? Please open an issue with:

  • Steps to reproduce
  • Expected vs actual behavior
  • PHP and Apache versions
  • Operating system

📄 License

This project is licensed under the GNU General Public License v3.0.
See the LICENSE file for full details.


Enjoy Fighting BOTS 🤖🛡️

About

A lightweight PHP proof-of-work gateway that issues a signed cookie, with Cloudflare-friendly ModSecurity rate limiting and Apache vhost examples.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

pow-shield-php

License: GPL-3.0PHP 8+Apache 2.4+

A lightweight Proof-of-Work (PoW) gateway for PHP sites that reduces abusive traffic without CAPTCHAs.
It issues a signed cookie (abp) after a browser completes a SHA-256 work check, then allows normal access.

✨ What's Included

This repository includes:

  • ✅ PoW challenge page: __ab/pow.php
  • ✅ PoW verifier + signed cookie: __ab/pow-verify.php
  • ✅ ModSecurity rate limits for PoW endpoints: modsecurity/ab_pow_ratelimit.conf
  • ✅ Apache vhost examples (sanitized to example.com) with PoW "skip" rules + clean URL option
  • ✅ Cloudflare compatibility notes (cache bypass + real client IP restore)
  • ✅ Secret rotation script + systemd service/timer examples

This repository intentionally excludes:

  • ❌ TLS certificates / private keys
  • ❌ secrets (your AB_POW_SECRET)
  • ❌ server logs / user data

🔄 How it works (request flow)

  1. A client requests a protected URL and does not have cookie abp
  2. Apache rewrites/redirects them to:
    /__ab/pow.php?next=/original/path&qs=original=query
    
  3. pow.php runs PoW in the browser:
    • compute sha256(TOKEN + "." + counter) until it has enough leading zero bits
  4. Browser submits the solution to:
    /__ab/pow-verify.php
    
  5. Server verifies:
    • token integrity (HMAC)
    • user-agent binding (light)
    • PoW difficulty (leading zero bits)
  6. Server sets cookie:
    • abp=<signed value> (Secure, HttpOnly, SameSite=Lax)
  7. Browser is redirected back to the original URL

Goal: make abusive traffic expensive while normal visitors pass quickly.


🔴 Live Production Example

A live deployment of pow-shield-php is running in production here:

https://lassiter.eu

This site uses:

  • Proof-of-Work (PoW) gateway for unauthenticated traffic
  • ModSecurity rate limiting on PoW endpoints
  • Apache connection-level protections (Slowloris / low-and-slow mitigation)
  • Cloudflare as CDN + TLS terminator (no bot challenges, no CAPTCHA)

⚠️Note: Configuration values, secrets, and thresholds used on the live site are intentionally not published in this repository.


📋 Requirements

Automatic Installation

The installer handles all dependencies automatically. Simply run:

sudo ./install.sh

Manual Requirements

If installing manually, you need:

Origin

  • PHP 8+
  • HTTPS (required for Secure cookie + WebCrypto)
  • Apache 2.4+

Optional / recommended

  • ModSecurity (Apache connector + CRS optional) for rate-limiting /__ab/*
  • If behind Cloudflare: Apache mod_remoteip configured to restore the real client IP

Secret (required)

  • AB_POW_SECRET must be set in the environment
  • Minimum: 48 characters
  • Recommended: 64+ characters

📂 Repository layout

pow-shield-php/
├─ __ab/
│ ├─ pow.php
│ └─ pow-verify.php
├─ modsecurity/
│ └─ ab_pow_ratelimit.conf
├─ apache/
│ └─ sites-available/
│ ├─ example.com-redirect.conf.example
│ └─ example.com.conf.example
├─ scripts/
│ └─ rotate-pow-secret.sh.example
├─ systemd/
│ ├─ rotate-pow-secret.service.example
│ └─ rotate-pow-secret.timer.example
├─ assets/img/
│ ├─ README.md
│ └─ .gitkeep
├─ docs/
│ ├─ cloudflare-notes.md
│ ├─ installation-checklist.md
│ └─ modsecurity-global-notes.md
├─ install.sh # 🆕 Automated installer
├─ uninstall.sh # 🆕 Automated uninstaller
└─ README.md

🚀 Quick Installation

We provide automated installation scripts for easy setup:

Option A: Automated Installation (Recommended)

# Clone the repository
git clone https://github.com/AfterPacket/pow-shield-php.git
cd pow-shield-php
# Make scripts executable
chmod +x install.sh uninstall.sh
# Run interactive installer
sudo ./install.sh

📖 Full Installation Guide: See INSTALL.md for detailed instructions, troubleshooting, and advanced configuration options.

The installer will:

  • ✅ Install all required dependencies (Apache, PHP, OpenSSL)
  • ✅ Generate secure PoW secret automatically
  • ✅ Deploy PoW endpoints and assets
  • ✅ Configure Apache virtual hosts
  • ✅ Set up ModSecurity rate limiting (optional)
  • ✅ Configure Let's Encrypt SSL (optional)
  • ✅ Set up automatic secret rotation

Installation Options

Interactive Mode (Default)

sudo ./install.sh

Follow the prompts to configure your installation.

Non-Interactive with Let's Encrypt

sudo ./install.sh -d example.com -w /var/www/html -l admin@example.com -e

Non-Interactive with Existing SSL

sudo ./install.sh -d example.com -w /var/www/html \
-c /etc/ssl/certs/cert.pem -k /etc/ssl/private/key.pem -e

Skip ModSecurity

sudo ./install.sh -d example.com -w /var/www/html -s

Installation Flags

FlagDescription
-d, --domainDomain name (e.g., example.com)
-w, --webrootWeb root directory path
-c, --certSSL certificate path (optional)
-k, --keySSL key path (optional)
-l, --letsencryptUse Let's Encrypt with email
-e, --enableEnable site with a2ensite after install
-s, --skip-modsecSkip ModSecurity installation
-n, --non-interactiveRun without prompts
-h, --helpShow help message

🗑️ Uninstallation

To completely remove pow-shield-php:

# Interactive uninstaller
sudo ./uninstall.sh
# Force removal without prompts
sudo ./uninstall.sh -d example.com -w /var/www/html -f
# Keep the PoW secret file
sudo ./uninstall.sh -d example.com -w /var/www/html -k
# Also remove ModSecurity rules
sudo ./uninstall.sh -d example.com -w /var/www/html -m

The uninstaller will:

  • ✅ Backup all files before removal
  • ✅ Disable and remove virtual hosts
  • ✅ Remove PoW endpoints
  • ✅ Remove systemd rotation (optional)
  • ✅ Remove ModSecurity rules (optional)
  • ✅ Test Apache config before reload

🛠️ Manual Installation

If you prefer manual installation:

1) Deploy /__ab/ endpoints

Copy the following files into your site webroot:

  • __ab/pow.php
  • __ab/pow-verify.php

They must resolve at:

  • https://example.com/__ab/pow.php
  • https://example.com/__ab/pow-verify.php

Tip: keep /__ab/ excluded from caching and from other WAF rules that might block POST.


2) Add the image used by pow.php (optional UI)

Your pow.php references:

/assets/img/clank.jpg

To keep this path:

  • place the image at assets/img/clank.jpg in your webroot

Or update $MEME_SRC inside __ab/pow.php.


🔐 Secret management (recommended): /etc/apache2/pow.env

Instead of embedding secrets in vhost configs, load them from a root-owned include file:

  • /etc/apache2/pow.env (root-owned, mode 600)
  • included in your HTTPS vhost via:
    IncludeOptional /etc/apache2/pow.env

Create the initial env file

sudo install -d -m 0755 /etc/apache2
sudo bash -c 'umask 077; SECRET="$(openssl rand -base64 64 | tr -d "\n")"; \ printf "%s\n" "# Managed by pow-shield-php" "SetEnv AB_POW_SECRET \"$SECRET\"" > /etc/apache2/pow.env'
sudo chown root:root /etc/apache2/pow.env
sudo chmod 600 /etc/apache2/pow.env
sudo apachectl -t
sudo systemctl reload apache2

⚠️Never commit secrets to git.


🔄 Secret rotation (optional): script + systemd service + timer

Rotating the PoW secret reduces replay value if a cookie/token leaks. To avoid breaking in-flight challenges, rotate with overlap:

  • New secret stored as AB_POW_SECRET
  • Old secret preserved as AB_POW_SECRET_PREV

✅ For this to work, your pow-verify.php should accept either secret when validating.

A) Rotation script

Save as:

/usr/local/sbin/rotate-pow-secret.sh
#!/bin/bashset -euo pipefail
OUT="/etc/apache2/pow.env"
TMP="$(mktemp)"umask 077
# Pull current secret (if any) from existing file
CURRENT=""if [[ -f"$OUT" ]];then
CURRENT="$(awk -F'"''/SetEnv[[:space:]]+AB_POW_SECRET[[:space:]]+"/ {print $2; exit}'"$OUT"|| true)"fi
NEW="$(openssl rand -base64 64 | tr -d '\n')"
{
echo'# Managed by rotate-pow-secret.sh'echo"SetEnv AB_POW_SECRET \"$NEW\""if [[ -n"${CURRENT}" ]];thenecho"SetEnv AB_POW_SECRET_PREV \"$CURRENT\""fi
} >"$TMP"
chown root:root "$TMP"
chmod 600 "$TMP"
mv -f "$TMP""$OUT"# Safety: verify Apache config first
apachectl -t
# Reload, not restart (keeps connections)
systemctl reload apache2

Install + test:

sudo install -m 0755 /usr/local/sbin/rotate-pow-secret.sh /usr/local/sbin/rotate-pow-secret.sh
sudo /usr/local/sbin/rotate-pow-secret.sh

B) systemd service

Create:

/etc/systemd/system/rotate-pow-secret.service
[Unit]Description=Rotate AB_POW_SECRET for pow-shield-php and reload Apache
Wants=apache2.service
After=apache2.service
[Service]Type=oneshot
ExecStart=/usr/local/sbin/rotate-pow-secret.sh
User=root
Group=root
# Hardening (safe defaults)NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/etc/apache2/pow.env

C) systemd timer (hourly)

Create:

/etc/systemd/system/rotate-pow-secret.timer
[Unit]Description=Hourly rotation for AB_POW_SECRET (pow-shield-php)
[Timer]OnCalendar=hourly
Persistent=true
RandomizedDelaySec=120
Unit=rotate-pow-secret.service
[Install]WantedBy=timers.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable --now rotate-pow-secret.timer
sudo systemctl list-timers --all | grep rotate-pow-secret

Manual trigger:

sudo systemctl start rotate-pow-secret.service
sudo systemctl status rotate-pow-secret.service --no-pager

🌐 Install: Apache vhost (PoW gating + skip rules)

Use the sanitized examples in apache/sites-available/.

Two common patterns:

Option A — Redirect to /__ab/pow.php (visible PoW URL)

  • simplest
  • user sees /__ab/pow.php?...

Option B — Internal rewrite (clean URL)

  • keeps the original URL in the address bar
  • uses [PT] internally to serve pow.php

In both options, always skip:

  • /__ab/* (prevents loops)
  • /status/* (your private panels/JSON)
  • static assets
  • non-GET/HEAD methods

🛡️ ModSecurity: rate-limit only the PoW endpoints (recommended)

Rules are provided in:

modsecurity/ab_pow_ratelimit.conf

A) Install ModSecurity (Debian/Ubuntu)

sudo apt update
sudo apt install -y libapache2-mod-security2
sudo a2enmod security2
sudo systemctl reload apache2

Confirm:

apachectl -M | grep -i security

B) Enable engine

In /etc/modsecurity/modsecurity.conf:

SecRuleEngine On
SecRequestBodyAccess On

Reload:

sudo systemctl reload apache2

C) Include PoW rules

Copy:

sudo mkdir -p /etc/modsecurity
sudo cp modsecurity/ab_pow_ratelimit.conf /etc/modsecurity/ab_pow_ratelimit.conf

Then include it in your vhost or global security2 config:

IncludeOptional /etc/modsecurity/ab_pow_ratelimit.conf
Header always set Retry-After "30" env=AB_RL

D) Verify enforcement

foriin$(seq 1 80);do
curl -sk https://example.com/__ab/pow.php?next=/ >/dev/null -w "%{http_code}\n"done

You should see 429 once the limit triggers.


🚨 Additional DDoS Mitigation (Apache-level)

PoW is application-layer cost. It helps with:

  • Basic bot spam
  • Naive request floods
  • Large-scale scraping (makes it expensive per request)

It does not stop all L7 attacks by itself. Pair it with:

  • ModSecurity rate limiting (especially on /__ab/pow-verify.php)
  • mod_reqtimeout (Slowloris mitigation)
  • Connection limits / MPM tuning
  • Correct real-IP restoration when behind Cloudflare

📝 Note: Pattern matters more than specific values; deploy thresholds appropriate to your traffic.


☁️ Cloudflare (recommended configuration)

See docs/cloudflare-notes.md.

Important settings:

  • Bot Fight Mode / "Stop Bot Attack": OFF (can interfere with PoW)
  • 🚫 Cache bypass for:
    • /__ab/pow.php
    • /__ab/pow-verify.php
  • 🌍 Restore real client IP at the origin using mod_remoteip

🔧 Troubleshooting

Infinite "Checking your browser…" loop

Common causes:

  • Cloudflare caching PoW endpoints
  • Cloudflare bot challenges enabled
  • Cookies blocked by browser
  • WAF blocking /__ab/pow-verify.php
  • Using PoW as an ErrorDocument 403 (can recurse)

Fix:

  • Disable Bot Fight / Stop Bot Attack
  • Bypass cache for PoW endpoints
  • Confirm Set-Cookie: abp=... is issued over HTTPS
  • Don't use PoW as 403 handler; use a static error page instead

LibreWolf / hardened Firefox shows "slow-device"

  • Lower difficulty for hardened UAs (or remove the "hard fail")
  • Extend TTL for challenge tokens
  • Ensure cookies aren't blocked for the site

Getting HTTP 429 during testing

  • ModSecurity limits are working as intended
  • Wait for the window to expire (often 60 seconds)

🔒 Security notes

  • AB_POW_SECRET must be long and random (>= 48 chars; 64+ recommended)
  • Never commit secrets to git
  • Consider rotating the secret with overlap (AB_POW_SECRET_PREV) to reduce replay value
  • Keep PoW endpoints uncached and allow POST to /__ab/pow-verify.php
  • If behind Cloudflare, configure real IP restoration before using per-IP rate limits

🧩 Contributing

Contributions are welcome! To participate:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-enhancement
  3. Commit your changes: git commit -m "Add: your feature"
  4. Push to your fork: git push origin feature/your-enhancement
  5. Open a Pull Request

🐛 Issues & Support

Found a bug or have a feature request? Please open an issue with:

  • Steps to reproduce
  • Expected vs actual behavior
  • PHP and Apache versions
  • Operating system

📄 License

This project is licensed under the GNU General Public License v3.0.
See the LICENSE file for full details.


Enjoy Fighting BOTS 🤖🛡️

About

A lightweight PHP proof-of-work gateway that issues a signed cookie, with Cloudflare-friendly ModSecurity rate limiting and Apache vhost examples.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages