{"uuid": "f3856622-d73c-4a84-8ba7-20950a9be7d5", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-25679", "type": "seen", "source": "https://gist.github.com/akses0/75206bef66ae9889ac26a86a74520308", "content": "// Tripper - a deliberately vulnerable URL unfurling service.\n//\n// CVE-2026-25679 demo: the \"SSRF guard\" inspects u.Host, trusting that\n// url.Parse rejects malformed authorities. On vulnerable Go (1.24.13-1.25.7,\n// 1.26.0) the URL \"https://example.com[::1]/\" parses with Host == \"[::1]\",\n// so an attacker smuggles an internal hostname past the guard inside the\n// domain portion of the URL.\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n)\n\nvar client = &amp;http.Client{\n\t// Unfurlers usually limit redirects and set timeouts; this demo keeps\n\t// them short so port-scanning via timing is snappy.\n\tTimeout: 3 * time.Second,\n\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\tif len(via) &gt;= 3 {\n\t\t\treturn fmt.Errorf(\"too many redirects\")\n\t\t}\n\t\treturn nil\n\t},\n}\n\n// naive guard: a string blocklist over the parsed hostname. This mirrors\n// the very common real-world pattern (grep for \"localhost\", obvious\n// RFC1918 prefixes, cloud metadata...) - and it has no idea that\n// \"example.com[::1]\" parses to the IPv6 loopback on vulnerable Go.\nfunc isAllowed(u *url.URL) bool {\n\thost := strings.ToLower(u.Hostname())\n\tif host == \"\" {\n\t\treturn false\n\t}\n\tblocked := []string{\n\t\t\"localhost\", \"127.\", \"0.0.0.0\", \"10.\", \"192.168.\", \"172.16.\",\n\t\t\"169.254\", \"metadata\", \"internal\", \".local\",\n\t}\n\tfor _, b := range blocked {\n\t\tif strings.Contains(host, b) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc unfurl(w http.ResponseWriter, r *http.Request) {\n\traw := r.URL.Query().Get(\"url\")\n\tif raw == \"\" {\n\t\thttp.Error(w, \"missing ?url=\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tu, err := url.Parse(raw)\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"rejected by url.Parse: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif !isAllowed(u) {\n\t\thttp.Error(w, fmt.Sprintf(\"blocked: host %q is internal\", u.Hostname()), http.StatusForbidden)\n\t\treturn\n\t}\n\n\tstart := time.Now()\n\tresp, err := client.Get(u.String())\n\telapsed := time.Since(start)\n\tif err != nil {\n\t\t// Timing leak: even errors reveal whether something answered.\n\t\tfmt.Fprintf(w, \"fetch failed after %v: %v\\n\", elapsed, err)\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))\n\ttitle := \"\"\n\tif s := string(body); len(s) &gt; 0 {\n\t\tif i := strings.Index(s, \"\"); i &gt;= 0 {\n\t\t\tif j := strings.Index(s[i:], \"\"); j &gt; 0 {\n\t\t\t\ttitle = s[i+7 : i+j]\n\t\t\t}\n\t\t}\n\t}\n\n\tfmt.Fprintf(w, \"unfurled %s\\n  status : %s\\n  time   : %v\\n  title  : %s\\n\",\n\t\tu, resp.Status, elapsed, strings.TrimSpace(title))\n}\n\nfunc main() {\n\thttp.HandleFunc(\"/unfurl\", unfurl)\n\thttp.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"Tripper unfurler (vulnerable build)\\nTry: /unfurl?url=https://example.com\\n\")\n\t})\n\tlog.Println(\"Tripper listening on :8080\")\n\tlog.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n", "creation_timestamp": "2026-08-30T12:06:22.342042Z"}