flawopen.com/ssrf/Go
How http.Get in Go allows internal network traversal, and how to build safe http.Client transports with DialContext.
कल्पना करें कि आप एक सहायक को सार्वजनिक दुकान से पार्सल लाने के लिए भेजते हैं, लेकिन उसे प्रबंधक के कार्यालय में रखी बंद तिजोरी का पता देते हैं। चूंकि सहायक के पास आंतरिक सुरक्षा बैज है, वह तिजोरी खोलकर कंपनी के रहस्य आपको सौंप देता है।
Web Application SecurityCWE-918.CWE-918CWE-918): Standard Common Weakness Enumeration classification for ssrf-go.Defense-in-Depthएप्लिकेशन इमेज फ़ेचिंग, वेबहुक या पीडीएफ़ जनरेशन के लिए उपयोगकर्ता द्वारा दिया गया URL स्वीकार करता है।
हमलावर क्लाउड मेटाडेटा सेवा (उदा. http://169.254.169.254/latest/meta-data/) या लूपबैक एड्रेस इनपुट करता है।
सर्वर बिना आईपी सत्यापन के आंतरिक वीपीसी नेटवर्क से अनुरोध भेज देता है।
आंतरिक मेटाडेटा सेवा सर्वर के अनुरोध पर विश्वास करके अस्थायी आईएएम टोकन और गुप्त कुंजियाँ लौटा देती है।
// VULNERABLE: Default http.Get allows internal network calls
package main
import (
"io"
"net/http"
)
func proxyHandler(w http.ResponseWriter, r *http.Request) {
targetURL := r.URL.Query().Get("url")
// Attacker passes: http://169.254.169.254/latest/meta-data/
resp, err := http.Get(targetURL)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()
io.Copy(w, resp.Body)
}
// HARDENED: Custom DialContext checking net.IP.IsPrivate and IsLoopback
package main
import (
"context"
"errors"
"io"
"net"
"net/http"
"net/url"
"time"
)
func isSafeIP(ip net.IP) bool {
return !ip.IsLoopback() &&
!ip.IsPrivate() &&
!ip.IsLinkLocalUnicast() &&
!ip.IsLinkLocalMulticast() &&
!ip.IsUnspecified()
}
func newSafeHTTPClient() *http.Client {
dialer := &net.Dialer{
Timeout: 3 * time.Second,
}
transport := &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
// Resolve all IP addresses
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", host)
if err != nil {
return nil, err
}
for _, ip := range ips {
if !isSafeIP(ip) {
return nil, errors.New("SSRF Blocked: connection to private/internal IP rejected")
}
}
// Connect to the first validated IP
targetAddr := net.JoinHostPort(ips[0].String(), port)
return dialer.DialContext(ctx, network, targetAddr)
},
}
return &http.Client{
Transport: transport,
Timeout: 5 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return errors.New("redirects are disabled to prevent SSRF bypass")
},
}
}
func proxyHandler(w http.ResponseWriter, r *http.Request) {
targetURL := r.URL.Query().Get("url")
parsed, err := url.Parse(targetURL)
if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") {
http.Error(w, "Invalid URL scheme", http.StatusBadRequest)
return
}
client := newSafeHTTPClient()
resp, err := client.Get(targetURL)
if err != nil {
http.Error(w, "Request failed: "+err.Error(), http.StatusForbidden)
return
}
defer resp.Body.Close()
io.Copy(w, resp.Body)
}
ip.IsPrivate() && !ip.IsLoopback() को सत्यापित और जांचें।