Over two weeks, our login endpoint received about four million login attempts, almost all failing, spread across thousands of IP addresses. Rate limiting per IP barely helped because each address made only a few attempts. When we looked at where the traffic came from, a large share arrived from Tor exit nodes, hosting providers and commercial anonymising proxy networks. Real customers almost never logged in from those.

We added IP reputation controls in AWS WAF in front of our CloudFront distribution. This post covers the decision, which is not as obvious as it looks, and the rollout.

Should you block Tor at all?

Tor protects people who need privacy: journalists, activists, people in countries with censorship, and ordinary users who prefer not to be tracked. Blocking all Tor traffic to an entire website excludes them. At the same time, Tor and anonymising proxies are heavily used for automated abuse because they hide the source.

Questions we used:

  • Who are your users? A B2B tool used by known companies from office networks is different from a public news site.
  • Which paths are abused? Blocking Tor on the login and signup endpoints is very different from blocking it on the marketing site.
  • What is the alternative for legitimate Tor users? A challenge instead of a block, or an extra verification step, can keep access open.
  • What does the abuse cost? Credential stuffing that leads to account takeovers justifies stronger measures than scraping public pages.

Our decision: no change for public pages; challenge requests from anonymising sources on login, signup and password reset; block them on admin and partner API paths, which have no legitimate anonymous users.

AWS WAF building blocks

AWS WAF evaluates rules in a web ACL attached to CloudFront, an Application Load Balancer, API Gateway and other resources. Relevant managed rule groups:

Rule group What it matches
Anonymous IP list (AWSManagedRulesAnonymousIpList) Tor exit nodes, anonymising VPN and proxy services, and hosting providers
Amazon IP reputation list (AWSManagedRulesAmazonIpReputationList) IPs associated with bots and threats observed by Amazon
Account takeover prevention (ATP) Login endpoint protection, credential stuffing detection (paid add-on)
Bot Control Bot classification and challenges (paid add-on)

The anonymous IP list has separate rules, for example one for anonymising services and one for hosting providers. Hosting providers include many legitimate sources (monitoring services, partner integrations, corporate egress through cloud), so treat that rule more carefully.

Rollout: count mode first

A new block rule on a busy site can lock out real users. We always start in count mode, which records matches without acting:

{
  "Name": "anonymous-ip-sensitive-paths",
  "Priority": 10,
  "Statement": {
    "ManagedRuleGroupStatement": {
      "VendorName": "AWS",
      "Name": "AWSManagedRulesAnonymousIpList",
      "ScopeDownStatement": {
        "RegexMatchStatement": {
          "RegexString": "^/(login|signup|password/reset|admin|partner-api)",
          "FieldToMatch": { "UriPath": {} },
          "TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }]
        }
      }
    }
  },
  "OverrideAction": { "Count": {} },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "anonymous-ip-sensitive-paths"
  }
}

The scope-down statement limits the rule to the paths we care about, which also reduces WAF cost, since some managed rule groups are priced per request evaluated.

We ran count mode for a week and analysed WAF logs in Athena:

SELECT terminatingruleid, httprequest.clientip, httprequest.uri,
       count(*) AS hits
FROM waf_logs
WHERE from_unixtime(timestamp / 1000) > now() - interval '7' day
  AND any_match(nonterminatingmatchingrules, r -> r.ruleid LIKE '%AnonymousIp%')
GROUP BY 1, 2, 3
ORDER BY hits DESC
LIMIT 50;

We joined matches against successful logins from our application logs to see how many real customers would have been affected.

What the count phase showed

  • About 70% of login attempts matched the anonymising services rule; fewer than 0.1% of successful logins did.
  • The hosting provider rule matched our own uptime monitoring and two partner integrations. We excluded that rule for partner API paths and allow-listed the monitoring IPs.
  • A handful of real customers logged in through privacy-focused VPNs. A challenge, not a block, kept them able to log in.

Enforcement

After the count phase:

  • Login, signup, password reset: the anonymising services rule action changed to CAPTCHA (or Challenge for a silent browser check), so humans can pass and scripts mostly cannot.
  • Admin and partner API paths: Block, with exceptions for allow-listed partner IP sets.
  • Amazon IP reputation list: Block on sensitive paths, Count elsewhere.

Rule action overrides are set per rule inside the managed group:

"RuleActionOverrides": [
  { "Name": "AnonymousIPList", "ActionToUse": { "Captcha": {} } },
  { "Name": "HostingProviderIPList", "ActionToUse": { "Count": {} } }
]

Telling users what happened

A block page with no explanation generates support tickets and frustration. Our custom response for blocked requests on admin paths explains that access from anonymising networks is not allowed for that area and links to the support page. The CAPTCHA page for login is the standard WAF challenge, with a short note on our help page explaining why some VPN users see it.

Results

Login attempts reaching the application dropped by about 75% within a day. Account takeover reports, which had been a few per week, stopped. Support received two tickets in the first month from users behind VPNs who found the CAPTCHA annoying; both could log in.

Operating the rules

  • Alarms on the CloudWatch metrics of each rule: a sudden increase in matches means a new attack wave; a sudden drop to zero may mean the rule was disabled or the scope-down broke.
  • Monthly review of sampled requests and of support tickets mentioning CAPTCHA or blocked access.
  • Infrastructure as code for the web ACL, so every change is reviewed and can be rolled back quickly.
  • Separate web ACLs for the public site and the API, so a rule tuned for browsers does not break API clients that cannot solve challenges.

WAF is one layer

IP reputation is a blunt tool. Attackers move to residential proxy networks that look like ordinary home connections. The real defences against credential stuffing are:

  • multi-factor authentication, ideally phishing-resistant;
  • checking passwords against known breached password lists at signup and change;
  • rate limiting by account as well as by IP;
  • monitoring for unusual login success patterns.

WAF rules reduce volume and cost; they do not replace those controls.

Common mistakes

Mistake Effect Do this instead
Blocking Tor site-wide by default Excludes privacy-conscious users Scope to abused, sensitive paths
Enforcing new rules immediately Real users locked out Count mode, analyse, then enforce
Blocking all hosting providers Breaks monitoring and integrations Count, allow-list known sources
Relying on WAF alone Residential proxies bypass it MFA, breached password checks, account limits
No logging Cannot evaluate false positives WAF logs to S3, query with Athena

Key takeaways

  • Blocking anonymising networks is a trade-off between abuse and access; decide per path.
  • AWS managed rule groups for anonymous IPs and IP reputation cover the common sources.
  • Roll out in count mode, analyse logs and real user impact, then enforce.
  • Prefer challenges over blocks where legitimate users may be affected.
  • Keep MFA, breached password checks and per-account limits as the core defences.

References