How to deal with host hacking?

My site is https://kentcdodds.com and someone is proxying my site on the domain https://www.butigim.net

I tried adding this to my express middleware to avoid this issue:

const primaryHost = 'kentcdodds.com'

const getHost = (req: {get: (key: string) => string | undefined}) =>
  req.get('X-Forwarded-Host') ?? req.get('host') ?? ''

if (process.env.FLY) {
  app.use((req, res, next) => {
    const host = getHost(req)
    const allowedHosts = [primaryHost, 'kcd.fly.dev', 'kcd-staging.fly.dev']
    // TODO: figure out if we can determine the IP address that fly uses for the healthcheck
    const isIPAddress = /\d+\.\d+\.\d+\.\d+/.test(host)
    if (allowedHosts.some(h => host.endsWith(h)) || isIPAddress) {
      return next()
    } else {
      console.log(`👺 disallowed host redirected: ${host}${req.originalUrl}`)
      return res.redirect(`https://${primaryHost}${req.originalUrl}`)
    }
  })
}

However, it appears whoever’s proxying has tricked us into getting kentcdodds.com in the host header. I’m not sure where to go from here. Any ideas?

I’m probably going to add a last-ditch client-side JavaScript thing to redirect users to the proper domain in the browser. But I’d love to be able to do all this server side.

EDIT: I realized that whoever’s proxying my site is actually processing responses to find/replace kentcdodds.com with www.butigim.net so any client-side JavaScript thing I do to redirect user’s won’t work. They also can change headers so access-control-allow-origin won’t work either. I need this to work server-side so I don’t send requests from their proxy anything useful. Anyone have ideas?

Based on some digging, it looks like they’re using SiteCloner on Cloudflare: Cloudflare Apps

After looking at request headers, I noticed that the proxying site has a fly-client-ip that is not included in the x-forwarded-for header. So I decided that if that’s detected, we’ll just send a nice message:

export const proxyRedirectMiddleware: RequestHandler = (req, res, next) => {
  const host = getHost(req)
  // TODO: figure out if we can determine the IP address that fly uses for the healthcheck
  const isIPAddress = /\d+\.\d+\.\d+\.\d+/.test(host)
  if (!allowedHosts.some(h => host.endsWith(h)) && !isIPAddress) {
    console.log(`👺 disallowed host redirected: ${host}${req.originalUrl}`)
    return res.redirect(`https://${primaryHost}${req.originalUrl}`)
  }

  const flyClientIp = req.get('Fly-Client-IP')
  const xForwardedFor = req.get('X-Forwarded-For')
  if (!flyClientIp || !xForwardedFor) {
    // this should never happen, but just in case...
    return next()
  }

  if (xForwardedFor.includes(flyClientIp)) {
    return next()
  } else {
    // https://fly.io/docs/reference/runtime-environment/#fly-client-ip
    // the fly-client-ip header is the IP address of the client that initiated the request
    // and if it's not found in the x-forwarded-for header, then we know something fishy is going on 🐟
    console.log(`👺 disallowed ip address replied to:`, {
      xForwardedFor,
      flyClientIp,
    })
    return res.send(
      'Please go to https://kcd.dev instead! Ping Kent if you think you should not be seeing this...',
    )
  }
}

This seems to work, but I’m a bit concerned. Am I correct is expecting that the x-forwarded-for should always include the fly-client-ip and if it doesn’t then there’s something fishy going on? Any other ideas on better ways to avoid this?

2 Likes

I believe, using Cloudflare to proxy traffic to other web properties is against their ToS. Surprised this happens, but if you know anyone at Cloudflare, it might be worth a shot at getting the offender shut down.

1 Like

I reported it to them so we’ll see. Thanks!

1 Like

Btw: How is the mirror sending traffic straight to your web host (presumably running on Fly) without getting through Fly Proxy first? Strange.

Do you happen to have allowed_public_ports set in your fly.toml? Doesn’t seem like it (github)…

I have no idea how this is happening at all :man_shrugging: I barely understand how these headers work.

Correct. However, anybody can add that header. It can’t really be absent since all your traffic goes through us.

Unless as @ignoramous said, you have allowed_public_ports, but you don’t.

I think with Content Security Policy headers, you could prevent other “origins” from requesting your content. That’s enforced by browsers.

It used to be a bit of pain to setup, but it might have gotten easier over the years.

But I believe that they could just change that header on their server before sending the response right?

True!

Maybe blocking Cloudflare egress IPs? https://www.cloudflare.com/ips/

Not a bad plan. Sadly they could just choose a different hosting platform. I guess this is just a game a whack-a-mole. :man_shrugging:

1 Like