What is ActionDispatch::RemoteIp::IpSpoofAttackError

I've seen ActionDispatch::RemoteIp::IpSpoofAttackError a few times and while Claude always assures me it's fine, I wanted to understand what it actually is.

In simple terms: Attackers want valuable stuff. We don't want them to get valuable stuff. A great way to get valuable stuff is to use lists of stolen username/passwords to brute force login forms (or other forms with valuable stuff). However, most websites (with a decent security posture) rate-limit sensitive forms to prevent this very type of abuse. But you rate-limit by checking IP addresses, and how you check those IP addresses is important. An IP spoofing attack is an attempt to exploit a website with bad IP address checking logic.

The ActionDispatch error above happens when the Client-IP header doesn't match the X-Forwarded-For header. The Client-IP can be set to whatever the requestor wants, whereas the X-Forwarded-For header is a chain that trusted proxies will only append to.1

Let's illustrate what this spoofed IP request might look like:

Client-IP
 210.63.163.15
    (fake)

X-Forwarded-For
 134.25.92.227, 195.178.110.72, 104.23.243.122, 10.31.184.41
  (also fake)    (real sender)   (Cloudflare)    (internal)

The attacker can put whatever they want in Client-IP, and they can put whatever they want in X-Forwarded-For. However, when the request arrives at Cloudflare (or any trusted proxy), Cloudflare will append the real IP address to the X-Forwarded-For chain, and then your infrastructure host will likely append an internal IP address as they forward the request through their VPC to your service.

Rails does the following:

  1. First, it attempts to find the Client-IP value anywhere in the X-Forwarded-For chain. If it cannot find that value anywhere in the chain, it raises the ActionDispatch::RemoteIp::IpSpoofAttackError exception.
  2. Only if it does find a match, Rails then locates the visitor's real IP address by starting at the end of the chain and moving back until it finds the first untrusted IP address.

I wrote this to build my own understanding about this error. I am also ignoring this exception in Honeybadger because it's just noise.

# honeybadger.yml
exceptions:
  ignore:
    - ActionDispatch::RemoteIp::IpSpoofAttackError

  1. Technically, the X-Forwarded-For header can also be set to whatever the requestor wants, but trusted (read: not run by malicious actors) proxies will only ever append. ↩