Dns Rebinding in Gin
How Dns Rebinding Manifests in Gin
Dns Rebinding attacks exploit the trust relationship between web applications and internal network services. In Gin applications, this vulnerability typically manifests when user-supplied URLs are used to make outbound HTTP requests without proper validation.
The attack works by registering a malicious domain that resolves to the attacker's IP initially. After the victim's browser connects, the DNS record changes to point to an internal IP address (like 192.168.1.1 or 10.0.0.1). Since browsers respect DNS TTL caching, the malicious site can bypass the same-origin policy and access internal services as if it were a legitimate internal request.
In Gin applications, this commonly appears in endpoint handlers that proxy requests or fetch external resources. Consider this vulnerable pattern:
func proxyHandler(c *gin.Context) {
targetURL := c.Query("url")
resp, err := http.Get(targetURL)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
c.Data(http.StatusOK, resp.Header.Get("Content-Type"), body)
}An attacker could craft a request like:
GET /proxy?url=http://malicious-domain.com/apiAfter DNS rebinding, this request could reach internal APIs like:
GET /proxy?url=http://192.168.1.100:8080/internal-apiGin's default middleware doesn't provide any protection against this, as it treats all outbound requests as legitimate. The vulnerability becomes particularly dangerous when the proxied service expects authentication from the originating client or when internal APIs lack proper authorization checks.
Another Gin-specific manifestation occurs with WebSocket upgrades. If a Gin handler accepts WebSocket connections to user-specified URLs:
func wsProxy(c *gin.Context) {
target := c.Query("target")
d := websocket.DefaultDialer
conn, _, err := d.Dial(target, c.Request.Header)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer conn.Close()
// Proxy WebSocket messages...
}This allows attackers to establish WebSocket connections to internal services that might not be publicly accessible, potentially exposing sensitive real-time data or control interfaces.
Gin-Specific Detection
Detecting Dns Rebinding in Gin applications requires both static code analysis and runtime testing. Static analysis should focus on identifying patterns where user input is used to construct outbound requests.
Using middleBrick's scanning capabilities, you can detect Dns Rebinding vulnerabilities without modifying your code. middleBrick's black-box scanning tests for SSRF patterns that include Dns Rebinding by attempting requests to special domains and observing responses. The scanner uses techniques like:
http://127.0.0.1.internal/
http://169.254.169.254/
http://metadata.google.internal/
http://instance-data/latest/meta-data/
These requests help identify if the application is making outbound connections to internal addresses. middleBrick also tests with time-delayed DNS responses to detect rebinding vulnerabilities.
For manual detection in your Gin codebase, search for these patterns:
# Find handlers that use user input for HTTP requests
grep -r "http\.Get\|http\.Post\|http\.Client" . | grep -E "(Query|Param|Default)"
# Find WebSocket proxy patterns
grep -r "websocket\.DefaultDialer\|websocket\.Dial" . | grep -E "(Query|Param|Default)"
Code review should specifically examine handlers that accept URLs as parameters. Look for missing validation of the scheme, host, and port. A secure implementation should reject non-HTTP/HTTPS schemes and validate the hostname against an allowlist.
middleBrick's OpenAPI analysis can also detect this vulnerability by examining your API specification. If your Swagger/OpenAPI spec shows endpoints accepting URL parameters that are used for outbound requests, middleBrick flags this as a potential SSRF risk. The scanner correlates spec definitions with runtime behavior, providing comprehensive coverage.
Runtime testing involves attempting requests with known internal IP addresses and observing the application's behavior. If the application responds with internal service data or error messages that reveal internal infrastructure details, it indicates a vulnerability.
Gin-Specific Remediation
Remediating Dns Rebinding in Gin requires implementing strict input validation and using safe HTTP clients. The most effective approach is to use an allowlist of permitted domains and reject all others.
Here's a secure implementation using Gin middleware:
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
type urlValidator struct {
allowedDomains map[string]bool
}
func (v *urlValidator) isValid(rawURL string) bool {
if !strings.HasPrefix(rawURL, "http://") && !strings.HasPrefix(rawURL, "https://") {
return false
}
u, err := http.Parse(rawURL)
if err != nil {
return false
}
// Reject private IP ranges
if isPrivateIP(u.Hostname()) {
return false
}
// Check against allowlist
domain := getDomain(u.Hostname())
if !v.allowedDomains[domain] {
return false
}
return true
}
func isPrivateIP(host string) bool {
// Check for private IP ranges: 10.x, 172.16-31.x, 192.168.x, 127.x
return strings.HasPrefix(host, "10.") ||
strings.HasPrefix(host, "127.") ||
strings.HasPrefix(host, "192.168.") ||
strings.HasPrefix(host, "172.")
}
func getDomain(host string) string {
parts := strings.Split(host, ".")
if len(parts) <= 2 {
return host
}
return strings.Join(parts[len(parts)-2:], ".")
}
func validateURLMiddleware(validator *urlValidator) gin.HandlerFunc {
return func(c *gin.Context) {
urlParam := c.Query("url")
if urlParam != "" && !validator.isValid(urlParam) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid or unauthorized URL"})
c.Abort()
return
}
c.Next()
}
}
func main() {
validator := &urlValidator{
allowedDomains: map[string]bool{
"example.com": true,
"api.example.org": true,
},
}
r := gin.Default()
r.Use(validateURLMiddleware(validator))
r.GET("/proxy", func(c *gin.Context) {
targetURL := c.Query("url")
client := &http.Client{
Timeout: 10 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // Prevent redirect loops
},
}
resp, err := client.Get(targetURL)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
c.Data(http.StatusOK, resp.Header.Get("Content-Type"), body)
})
r.Run(":8080")
}For WebSocket endpoints, implement similar validation:
func validateWSHandler(validator *urlValidator) gin.HandlerFunc {
return func(c *gin.Context) {
target := c.Query("target")
if target != "" && !validator.isValid(target) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid WebSocket target"})
c.Abort()
return
}
c.Next()
}
}
r.GET("/ws-proxy", validateWSHandler(validator), func(c *gin.Context) {
target := c.Query("target")
d := websocket.DefaultDialer
conn, _, err := d.Dial(target, c.Request.Header)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer conn.Close()
// WebSocket proxying logic...
})Additional protections include implementing DNS pinning by setting a minimum TTL for DNS resolutions, using HTTP clients that respect DNS caching properly, and implementing rate limiting on endpoints that accept URLs to slow down automated rebinding attempts.
middleBrick's continuous monitoring can verify these remediations by regularly scanning your API endpoints and ensuring the Dns Rebinding vulnerability remains resolved. The platform provides detailed reports showing which specific endpoints were tested and the results of each security check.