DEV Community

Cover image for How I Autoban Hackers Who Touch My Secret URLs

How I Autoban Hackers Who Touch My Secret URLs

If you look at your server logs right now, I guarantee you’ll see them.

The bots. The script kiddies. The scanners. They are relentlessly hitting paths that don’t exist: /wp-admin, .env, /backup.zip, /admin-login. They are looking for a weakness, jiggling the handle of every door in your application.

Usually, the advice is defensive: "Rate limit them." "Ignore them." "Set up a static WAF rule."

I got bored of playing defense. So I built a trap.

In this tutorial, I’m going to show you how to implement Active Defense on AWS. We aren't just going to block known attacks; we are going to create a "Honeypot" URL. It’s a fake login page that no legitimate user will ever see. But if a bot touches it?

Their IP address is instantly identified, logged, and added to a hardware-level Blocklist (WAF) that bans them from my entire infrastructure.

The Architecture: Anatomy of a Trap

To make this work, we need three components: The Bait, The Enforcer, and The Jail.

The Bait (API Gateway)

We need a URL that looks tasty to a bot but is invisible to a human. Something like /admin-backup or /v1/secret. We set up an API Gateway (REST) to listen on this specific path. If a normal user visits your homepage, they are fine. If a bot scans your site and hits this path, they trigger the trap.

The Enforcer (Lambda)

When the trap is triggered, it fires a Python Lambda function. This function doesn't return a webpage. Instead, it performs a "arrest":

  1. It extracts the Source IP of the request.
  2. It calls the AWS WAFv2 API.
  3. It adds that IP address to a specific IP Set (our "Jail").

The Jail (AWS WAF)

This is the heavy lifter. We have an AWS WAF Web ACL sitting in front of our API. It has a high-priority rule: Block ANY request coming from an IP inside the 'Honeypot' IP Set.

The second the Lambda updates that list, the door slams shut. The bot can't access the fake page, the real page, or anything else. They are gone.

Why "Active Defense"?
You might ask, "Bro, isn't this overkill?"

Not really. Traditional WAF rules look for patterns (like SQL Injection strings). They are reactive. This approach is proactive. By identifying an actor who is clearly malicious (probing for hidden files), we can block them before they even launch their real attack.

Plus, it’s extremely satisfying to watch the "Banned IPs" list grow automatically while you sleep.

The Code Breakdown

I built this entire stack using Terraform so it can be deployed (and destroyed) in minutes.

Note: During the build, I discovered a quirk. AWS WAF does not fully support the newer "HTTP APIs" (v2) for this specific type of blocking logic easily, so we are using the battle-tested "REST API" (v1) standard.

The Jail: aws_wafv2_ip_set

First, we need a mutable list. In Terraform, we define an empty IP Set.

resource "aws_wafv2_ip_set" "honeypot_set" {
  name               = "honeypot-blocked-ips"
  scope              = "REGIONAL"
  ip_address_version = "IPV4"
  addresses          = [] 

  lifecycle {
    ignore_changes = [addresses]
  }
}
Enter fullscreen mode Exit fullscreen mode

Critical Dev Tip: Notice the lifecycle { ignore_changes = [addresses] } block? This is mandatory. Without it, every time you run terraform apply, Terraform will see the list has changed (because the Lambda added bad IPs) and will try to wipe it clean. This block tells Terraform: "I created the jail, but I don't control who is inside it."

The Permissions

The Lambda function isn't just running code; it needs permission to modify your firewall. This is where most people get stuck.

We grant the Lambda two specific permissions:

  1. wafv2:GetIPSet: To read the current list (we need the LockToken for concurrency control).
  2. wafv2:UpdateIPSet: To actually write the new IP to the list
{
  Action = ["wafv2:GetIPSet", "wafv2:UpdateIPSet"],
  Effect   = "Allow",
  Resource = aws_wafv2_ip_set.honeypot_set.arn
}
Enter fullscreen mode Exit fullscreen mode

Security Note: We scope the Resource specifically to the honeypot_set.arn. Even if this Lambda gets compromised, it can only ban people—it can’t delete your other WAF rules.

The Logic: Python Boto3

Inside the Python script, the logic is simple but ruthless. We are using the boto3 library to interact with the AWS API.

The most important part is handling the LockToken. AWS WAF uses optimistic locking. To update the list, you must first read the list to get the current "version" (token). If you try to update without it, AWS rejects the request to prevent race conditions.

# 1. Read the list (Get Token)
response = waf.get_ip_set(...)
lock_token = response['LockToken']

# 2. Add the IP
current_ips.append(ip_cidr)

# 3. Push the update (With Token)
waf.update_ip_set(..., LockToken=lock_token)
Enter fullscreen mode Exit fullscreen mode

The Wall: aws_wafv2_web_acl

Finally, we set the rules of engagement.

We create a Rule with Priority 1. In WAF, lower numbers run first. This ensures that before the request is checked for SQL injection or Rate Limits, we check the Ban List. If they are in the jail, we drop the connection immediately.

rule {
  name     = "BlockBannedIPs"
  priority = 1
  action {
    block {} # <--- The Hammer
  }
  statement {
    ip_set_reference_statement {
      arn = aws_wafv2_ip_set.honeypot_set.arn
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

🚀 Deployment

Want to try this yourself? Here is the full code.

Prerequisites

  • Terraform installed
  • AWS CLI configured (aws configure) with Admin permissions

Step 1: Create the Python Logic (honeypot.py)
Save this file in a folder. This is the logic that detects the intruder and updates the firewall.

Step 2: The Infrastructure (main.tf)
Save this in the same folder. This builds the WAF, API Gateway, and Lambda.

Step 3: Deployment
Open your terminal in the folder.

Run terraform init.
Run terraform apply -auto-approve.

Step 4: How to Test (Be Careful!)
After Terraform finishes, it will output a TRAP_URL (ending in /admin-backup).

Check your status: Open the SAFE_URL (just the domain). You should see a "Not Found" (which is fine, we didn't make a root page), but it loads.

Trigger the Trap:
Open the TRAP_URL in your browser OR use curl [TRAP_URL].
You will see: Restricted Access. Your IP x.x.x.x has been flagged.

Verify the Ban:
Try to refresh the page.
You should eventually see 403 Forbidden coming from AWS WAF (not the Lambda).

Note: It might take 10-30 seconds for WAF to propagate the update.

How Not to Break Production

Before you deploy this to your company's main website, you need to handle a few edge cases. Active Defense is powerful, but it has no sense of humor.

The "Google Problem" (robots.txt)
You do not want to ban the Google Crawler just because it found your trap URL.

The Fix: Add a robots.txt file to your site root.

User-agent: *
Disallow: /admin-backup
Enter fullscreen mode Exit fullscreen mode

Legitimate crawlers (Google, Bing) respect this and won't touch the link. Malicious scanners usually ignore robots.txt and scan anyway. That’s how you filter the good bots from the bad ones.

Whitelisting
In a production environment, you should add a "Safe List" rule to your WAF with a higher priority (Priority 0) than your Block rule. Add your office VPN and CI/CD server IPs there. This ensures that no matter what crazy testing you do, your internal team never gets locked out.

References:

Honeypots & Active Defense
CSRC.NIST.gov - Honeypot Glossary
CrowdStrike - What is a Honeypot?

AWS WAF & Automation Specs
AWS Docs - UpdateIPSet
AWS Solutions - Security Automations

Infrastructure as Code (Terraform)
Terraform Registry - aws_wafv2_ip_set

Bot Management Standards
Google Developers - Robots.txt Spec

Top comments (0)