It seems that it’s possible to override the CF-Connecting-IP
and X-Forwarded-For
headers with arbitrary values for subrequests destined for non-Cloudflare zones via the X-Real-IP
header. Here is a minimal POC:
addEventListener("fetch", (event) => {
event.respondWith(
handleRequest(event.request).catch(
(err) => new Response(err.stack, { status: 500 })
)
);
});
async function handleRequest(request) {
const headers = new Headers(request.headers)
headers.append("X-Real-IP", "8.8.8.8")
const newRequestInit = {
method: "GET",
redirect: "follow",
headers: headers,
};
const attrs = new Request(request, newRequestInit);
const newRequest = new Request(
"https://example.com",
attrs
);
return fetch(newRequest);
}
This is the resulting HTTP request that’s made:
GET / HTTP/1.1
host: example.com
connection: Keep-Alive
accept-encoding: gzip
x-forwarded-for: 8.8.8.8
cf-ray: 6d743456d21ad45f-HAM
x-forwarded-proto: https
cf-visitor: {"scheme":"https"}
cf-ew-via: 15
cdn-loop: cloudflare; subreqs=1
accept-language: en
accept: */*
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
cf-connecting-ip: 8.8.8.8
cf-worker: <redacted>
Given that it’s not possible to achieve this by modifying the CF-Connecting-IP
or X-Forwarded-For
headers directly, I’m wondering if this is intended behaviour. Moreover, the relevant documentation says:
For Worker subrequests destined for a non-Cloudflare customer zone, the
CF-Connecting-IP
andx-real-ip
headers will both reflect the client’s IP address, with only thex-real-ip
header able to be altered.
I might be misunderstanding this sentence, but I’d expect CF-Connecting-IP
to always contain the client’s IP address, even if the X-Real-IP
header is altered.
Interestingly, this answer from 2019 implies that the X-Real-IP
header is renamed to CF-Connecting-IP
which I assume would explain this behaviour (if it’s still actual) and also imply that this is indeed not expected behaviour (given that the answer indicates it’s a bug).
This might have security implications for anyone who assumes that the CF-Connecting-IP
header can be trusted if it’s coming from Cloudflare’s IP ranges (admittedly, given that the subrequest is destined to a non-Cloudflare zone, this assumption might simply be wrong). I wasn’t able to find any other relevant documentation about that fact though.
Is it expected that CF Workers can set arbitrary CF-Connecting-IP
and X-Forwarded-For
headers in subrequests destined to non-Cloudflare zones?