|
| 1 | +# Abusing Cloudflare Workers as pass-through proxies (IP rotation, FireProx-style) |
| 2 | + |
| 3 | +{{#include ../../banners/hacktricks-training.md}} |
| 4 | + |
| 5 | +Cloudflare Workers can be deployed as transparent HTTP pass-through proxies where the upstream target URL is supplied by the client. Requests egress from Cloudflare's network so the target observes Cloudflare IPs instead of the client's. This mirrors the well-known FireProx technique on AWS API Gateway, but uses Cloudflare Workers. |
| 6 | + |
| 7 | +### Key capabilities |
| 8 | +- Support for all HTTP methods (GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD) |
| 9 | +- Target can be supplied via query parameter (?url=...), a header (X-Target-URL), or even encoded in the path (e.g., /https://target) |
| 10 | +- Headers and body are proxied through with hop-by-hop/header filtering as needed |
| 11 | +- Responses are relayed back, preserving status code and most headers |
| 12 | +- Optional spoofing of X-Forwarded-For (if the Worker sets it from a user-controlled header) |
| 13 | +- Extremely fast/easy rotation by deploying multiple Worker endpoints and fanning out requests |
| 14 | + |
| 15 | +### How it works (flow) |
| 16 | +1) Client sends an HTTP request to a Worker URL (`<name>.<account>.workers.dev` or a custom domain route). |
| 17 | +2) Worker extracts the target from either a query parameter (?url=...), the X-Target-URL header, or a path segment if implemented. |
| 18 | +3) Worker forwards the incoming method, headers, and body to the specified upstream URL (filtering problematic headers). |
| 19 | +4) Upstream response is streamed back to the client through Cloudflare; the origin sees Cloudflare egress IPs. |
| 20 | + |
| 21 | +### Worker implementation example |
| 22 | +- Reads target URL from query param, header, or path |
| 23 | +- Copies a safe subset of headers and forwards the original method/body |
| 24 | +- Optionally sets X-Forwarded-For using a user-controlled header (X-My-X-Forwarded-For) or a random IP |
| 25 | +- Adds permissive CORS and handles preflight |
| 26 | + |
| 27 | +<details> |
| 28 | +<summary>Example Worker (JavaScript) for pass-through proxying</summary> |
| 29 | + |
| 30 | +```javascript |
| 31 | +/** |
| 32 | + * Minimal Worker pass-through proxy |
| 33 | + * - Target URL from ?url=, X-Target-URL, or /https://... |
| 34 | + * - Proxies method/headers/body to upstream; relays response |
| 35 | + */ |
| 36 | +addEventListener('fetch', event => { |
| 37 | + event.respondWith(handleRequest(event.request)) |
| 38 | +}) |
| 39 | + |
| 40 | +async function handleRequest(request) { |
| 41 | + try { |
| 42 | + const url = new URL(request.url) |
| 43 | + const targetUrl = getTargetUrl(url, request.headers) |
| 44 | + |
| 45 | + if (!targetUrl) { |
| 46 | + return errorJSON('No target URL specified', 400, { |
| 47 | + usage: { |
| 48 | + query_param: '?url=https://example.com', |
| 49 | + header: 'X-Target-URL: https://example.com', |
| 50 | + path: '/https://example.com' |
| 51 | + } |
| 52 | + }) |
| 53 | + } |
| 54 | + |
| 55 | + let target |
| 56 | + try { target = new URL(targetUrl) } catch (e) { |
| 57 | + return errorJSON('Invalid target URL', 400, { provided: targetUrl }) |
| 58 | + } |
| 59 | + |
| 60 | + // Forward original query params except control ones |
| 61 | + const passthru = new URLSearchParams() |
| 62 | + for (const [k, v] of url.searchParams) { |
| 63 | + if (!['url', '_cb', '_t'].includes(k)) passthru.append(k, v) |
| 64 | + } |
| 65 | + if (passthru.toString()) target.search = passthru.toString() |
| 66 | + |
| 67 | + // Build proxied request |
| 68 | + const proxyReq = buildProxyRequest(request, target) |
| 69 | + const upstream = await fetch(proxyReq) |
| 70 | + |
| 71 | + return buildProxyResponse(upstream, request.method) |
| 72 | + } catch (error) { |
| 73 | + return errorJSON('Proxy request failed', 500, { |
| 74 | + message: error.message, |
| 75 | + timestamp: new Date().toISOString() |
| 76 | + }) |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +function getTargetUrl(url, headers) { |
| 81 | + let t = url.searchParams.get('url') || headers.get('X-Target-URL') |
| 82 | + if (!t && url.pathname !== '/') { |
| 83 | + const p = url.pathname.slice(1) |
| 84 | + if (p.startsWith('http')) t = p |
| 85 | + } |
| 86 | + return t |
| 87 | +} |
| 88 | + |
| 89 | +function buildProxyRequest(request, target) { |
| 90 | + const h = new Headers() |
| 91 | + const allow = [ |
| 92 | + 'accept','accept-language','accept-encoding','authorization', |
| 93 | + 'cache-control','content-type','origin','referer','user-agent' |
| 94 | + ] |
| 95 | + for (const [k, v] of request.headers) { |
| 96 | + if (allow.includes(k.toLowerCase())) h.set(k, v) |
| 97 | + } |
| 98 | + h.set('Host', target.hostname) |
| 99 | + |
| 100 | + // Optional: spoof X-Forwarded-For if provided |
| 101 | + const spoof = request.headers.get('X-My-X-Forwarded-For') |
| 102 | + h.set('X-Forwarded-For', spoof || randomIP()) |
| 103 | + |
| 104 | + return new Request(target.toString(), { |
| 105 | + method: request.method, |
| 106 | + headers: h, |
| 107 | + body: ['GET','HEAD'].includes(request.method) ? null : request.body |
| 108 | + }) |
| 109 | +} |
| 110 | + |
| 111 | +function buildProxyResponse(resp, method) { |
| 112 | + const h = new Headers() |
| 113 | + for (const [k, v] of resp.headers) { |
| 114 | + if (!['content-encoding','content-length','transfer-encoding'].includes(k.toLowerCase())) { |
| 115 | + h.set(k, v) |
| 116 | + } |
| 117 | + } |
| 118 | + // Permissive CORS for tooling convenience |
| 119 | + h.set('Access-Control-Allow-Origin', '*') |
| 120 | + h.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD') |
| 121 | + h.set('Access-Control-Allow-Headers', '*') |
| 122 | + |
| 123 | + if (method === 'OPTIONS') return new Response(null, { status: 204, headers: h }) |
| 124 | + return new Response(resp.body, { status: resp.status, statusText: resp.statusText, headers: h }) |
| 125 | +} |
| 126 | + |
| 127 | +function errorJSON(msg, status=400, extra={}) { |
| 128 | + return new Response(JSON.stringify({ error: msg, ...extra }), { |
| 129 | + status, headers: { 'Content-Type': 'application/json' } |
| 130 | + }) |
| 131 | +} |
| 132 | + |
| 133 | +function randomIP() { return [1,2,3,4].map(() => Math.floor(Math.random()*255)+1).join('.') } |
| 134 | +``` |
| 135 | + |
| 136 | +</details> |
| 137 | + |
| 138 | +### Automating deployment and rotation with FlareProx |
| 139 | + |
| 140 | +FlareProx is a Python tool that uses the Cloudflare API to deploy many Worker endpoints and rotate across them. This provides FireProx-like IP rotation from Cloudflare’s network. |
| 141 | + |
| 142 | +Setup |
| 143 | +1) Create a Cloudflare API Token using the “Edit Cloudflare Workers” template and get your Account ID from the dashboard. |
| 144 | +2) Configure FlareProx: |
| 145 | + |
| 146 | +```bash |
| 147 | +git clone https://github.com/MrTurvey/flareprox |
| 148 | +cd flareprox |
| 149 | +pip install -r requirements.txt |
| 150 | +``` |
| 151 | + |
| 152 | +**Create config file flareprox.json:** |
| 153 | + |
| 154 | +```json |
| 155 | +{ |
| 156 | + "cloudflare": { |
| 157 | + "api_token": "your_cloudflare_api_token", |
| 158 | + "account_id": "your_cloudflare_account_id" |
| 159 | + } |
| 160 | +} |
| 161 | +``` |
| 162 | + |
| 163 | +**CLI usage** |
| 164 | + |
| 165 | +- Create N Worker proxies: |
| 166 | +```bash |
| 167 | +python3 flareprox.py create --count 2 |
| 168 | +``` |
| 169 | +- List endpoints: |
| 170 | +```bash |
| 171 | +python3 flareprox.py list |
| 172 | +``` |
| 173 | +- Health-test endpoints: |
| 174 | +```bash |
| 175 | +python3 flareprox.py test |
| 176 | +``` |
| 177 | +- Delete all endpoints: |
| 178 | +```bash |
| 179 | +python3 flareprox.py cleanup |
| 180 | +``` |
| 181 | + |
| 182 | +**Routing traffic through a Worker** |
| 183 | +- Query parameter form: |
| 184 | +```bash |
| 185 | +curl "https://your-worker.account.workers.dev?url=https://httpbin.org/ip" |
| 186 | +``` |
| 187 | +- Header form: |
| 188 | +```bash |
| 189 | +curl -H "X-Target-URL: https://httpbin.org/ip" https://your-worker.account.workers.dev |
| 190 | +``` |
| 191 | +- Path form (if implemented): |
| 192 | +```bash |
| 193 | +curl https://your-worker.account.workers.dev/https://httpbin.org/ip |
| 194 | +``` |
| 195 | +- Method examples: |
| 196 | +```bash |
| 197 | +# GET |
| 198 | +curl "https://your-worker.account.workers.dev?url=https://httpbin.org/get" |
| 199 | + |
| 200 | +# POST (form) |
| 201 | +curl -X POST -d "username=admin" \ |
| 202 | + "https://your-worker.account.workers.dev?url=https://httpbin.org/post" |
| 203 | + |
| 204 | +# PUT (JSON) |
| 205 | +curl -X PUT -d '{"username":"admin"}' -H "Content-Type: application/json" \ |
| 206 | + "https://your-worker.account.workers.dev?url=https://httpbin.org/put" |
| 207 | + |
| 208 | +# DELETE |
| 209 | +curl -X DELETE \ |
| 210 | + "https://your-worker.account.workers.dev?url=https://httpbin.org/delete" |
| 211 | +``` |
| 212 | + |
| 213 | +**`X-Forwarded-For` control** |
| 214 | + |
| 215 | +If the Worker honors `X-My-X-Forwarded-For`, you can influence the upstream `X-Forwarded-For` value: |
| 216 | +```bash |
| 217 | +curl -H "X-My-X-Forwarded-For: 203.0.113.10" \ |
| 218 | + "https://your-worker.account.workers.dev?url=https://httpbin.org/headers" |
| 219 | +``` |
| 220 | + |
| 221 | +**Programmatic usage** |
| 222 | + |
| 223 | +Use the FlareProx library to create/list/test endpoints and route requests from Python. |
| 224 | + |
| 225 | +<details> |
| 226 | +<summary>Python example: Send a POST via a random Worker endpoint</summary> |
| 227 | + |
| 228 | +```python |
| 229 | +#!/usr/bin/env python3 |
| 230 | +from flareprox import FlareProx, FlareProxError |
| 231 | +import json |
| 232 | + |
| 233 | +# Initialize |
| 234 | +flareprox = FlareProx(config_file="flareprox.json") |
| 235 | +if not flareprox.is_configured: |
| 236 | + print("FlareProx not configured. Run: python3 flareprox.py config") |
| 237 | + exit(1) |
| 238 | + |
| 239 | +# Ensure endpoints exist |
| 240 | +endpoints = flareprox.sync_endpoints() |
| 241 | +if not endpoints: |
| 242 | + print("Creating proxy endpoints...") |
| 243 | + flareprox.create_proxies(count=2) |
| 244 | + |
| 245 | +# Make a POST request through a random endpoint |
| 246 | +try: |
| 247 | + post_data = json.dumps({ |
| 248 | + "username": "testuser", |
| 249 | + "message": "Hello from FlareProx!", |
| 250 | + "timestamp": "2025-01-01T12:00:00Z" |
| 251 | + }) |
| 252 | + |
| 253 | + headers = { |
| 254 | + "Content-Type": "application/json", |
| 255 | + "User-Agent": "FlareProx-Client/1.0" |
| 256 | + } |
| 257 | + |
| 258 | + response = flareprox.redirect_request( |
| 259 | + target_url="https://httpbin.org/post", |
| 260 | + method="POST", |
| 261 | + headers=headers, |
| 262 | + data=post_data |
| 263 | + ) |
| 264 | + |
| 265 | + if response.status_code == 200: |
| 266 | + result = response.json() |
| 267 | + print("✓ POST successful via FlareProx") |
| 268 | + print(f"Origin IP: {result.get('origin', 'unknown')}") |
| 269 | + print(f"Posted data: {result.get('json', {})}") |
| 270 | + else: |
| 271 | + print(f"Request failed with status: {response.status_code}") |
| 272 | + |
| 273 | +except FlareProxError as e: |
| 274 | + print(f"FlareProx error: {e}") |
| 275 | +except Exception as e: |
| 276 | + print(f"Request error: {e}") |
| 277 | +``` |
| 278 | + |
| 279 | +</details> |
| 280 | + |
| 281 | +**Burp/Scanner integration** |
| 282 | +- Point tooling (for example, Burp Suite) at the Worker URL. |
| 283 | +- Supply the real upstream using ?url= or X-Target-URL. |
| 284 | +- HTTP semantics (methods/headers/body) are preserved while masking your source IP behind Cloudflare. |
| 285 | + |
| 286 | +**Operational notes and limits** |
| 287 | +- Cloudflare Workers Free plan allows roughly 100,000 requests/day per account; use multiple endpoints to distribute traffic if needed. |
| 288 | +- Workers run on Cloudflare’s network; many targets will only see Cloudflare IPs/ASN, which can bypass naive IP allow/deny lists or geo heuristics. |
| 289 | +- Use responsibly and only with authorization. Respect ToS and robots.txt. |
| 290 | + |
| 291 | +## References |
| 292 | +- [FlareProx (Cloudflare Workers pass-through/rotation)](https://github.com/MrTurvey/flareprox) |
| 293 | +- [Cloudflare Workers fetch() API](https://developers.cloudflare.com/workers/runtime-apis/fetch/) |
| 294 | +- [Cloudflare Workers pricing and free tier](https://developers.cloudflare.com/workers/platform/pricing/) |
| 295 | +- [FireProx (AWS API Gateway)](https://github.com/ustayready/fireprox) |
| 296 | + |
| 297 | +{{#include ../../banners/hacktricks-training.md}} |
0 commit comments