Ssrf Server Side in Echo Go
How SSRF Server-Side Manifests in Echo Go
SSRF (Server-Side Request Forgery) in Echo Go applications typically occurs when the server makes outbound HTTP requests based on user-controlled input without proper validation. In Echo Go, this vulnerability manifests through several common patterns that Echo developers should recognize.
The most frequent SSRF vector in Echo Go is the HTTP client usage pattern. When developers use Go's standard net/http library or third-party clients to make requests to URLs provided by users, they create an attack surface. For example:
func handleRequest(c echo.Context) error {
url := c.QueryParam("url")
resp, err := http.Get(url) // Vulnerable: user controls URL
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return c.JSON(http.StatusOK, string(body))
}
This pattern is dangerous because an attacker can supply internal URLs like http://localhost:8080, http://169.254.169.254 (AWS metadata), or http://internal.service:8080, allowing them to access internal services, cloud metadata APIs, or perform port scanning.
Echo Go applications often expose SSRF through proxy functionality, webhook handlers, or API aggregation services. Another common pattern involves using echo.Context.Request() to forward requests:
func proxyRequest(c echo.Context) error {
targetURL := c.QueryParam("target")
req, _ := http.NewRequest("GET", targetURL, nil)
client := &http.Client{}
resp, err := client.Do(req) // Vulnerable to SSRF
if err != nil {
return c.JSON(http.StatusBadGateway, err.Error())
}
defer resp.Body.Close()
return c.Stream(resp.StatusCode, resp.Header.Get("Content-Type"), resp.Body)
}
Echo Go's middleware ecosystem can also introduce SSRF risks. Custom middleware that makes external requests based on request headers or parameters, or services that fetch external resources for content processing, create similar vulnerabilities.
Echo Go-Specific Detection
Detecting SSRF in Echo Go applications requires both static code analysis and runtime scanning. For static detection, look for these Echo Go-specific patterns:
Code Pattern Analysis: Search your Echo Go codebase for HTTP client usage where URLs come from user input. Use grep or your IDE to find patterns like:
grep -r "http\.Get(" . | grep -E "(c\.QueryParam|c\.Param|c\.FormValue)"
grep -r "http\.Client" . | grep -E "(c\.QueryParam|c\.Param|c\.FormValue)"
Echo Context Parameter Extraction: Focus on these Echo-specific methods that extract user input:
c.QueryParam("url")
c.Param("id")
c.FormValue("redirect")
c.Request().Header.Get("X-Forwarded-For")
Runtime Testing with middleBrick: middleBrick's black-box scanning approach is particularly effective for Echo Go applications. Since Echo Go apps typically run on standard HTTP ports, you can scan your API endpoints directly:
middlebrick scan https://yourechoapp.com/api/endpoint
middleBrick tests for SSRF by attempting to access internal resources, cloud metadata endpoints, and non-routable IP addresses. It specifically checks for:
- Access to localhost and 127.0.0.1
- Private IP ranges (10.x, 172.16-31.x, 192.168.x)
- Cloud metadata services (169.254.169.254, 169.254.170.2)
- Non-routable addresses (0.0.0.0, 255.255.255.255)
Echo Go Middleware Inspection: Review your middleware chain for any components that make external requests. Echo Go's middleware structure makes this straightforward:
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.CORS())
// Check if any middleware makes external requests
Network Layer Detection: Use network monitoring tools during testing to observe outbound connections from your Echo Go application. Tools like Wireshark or tcpdump can reveal if your application is making unexpected external requests.
Echo Go-Specific Remediation
Remediating SSRF in Echo Go applications requires a defense-in-depth approach using Go's native capabilities and Echo Go's features. Here are Echo Go-specific remediation strategies:
URL Validation with net/url: Always validate and sanitize URLs before making requests. Echo Go developers should use Go's standard library for robust validation:
import (
"net/url"
"strings"
)
func validateURL(rawURL string) (string, error) {
parsedURL, err := url.Parse(rawURL)
if err != nil {
return "", err
}
// Block private IP ranges
if isPrivateIP(parsedURL.Hostname()) {
return "", errors.New("private IP addresses are not allowed")
}
// Block localhost
if strings.HasPrefix(parsedURL.Hostname(), "localhost") ||
parsedURL.Hostname() == "127.0.0.1" {
return "", errors.New("localhost addresses are not allowed")
}
// Block cloud metadata endpoints
if parsedURL.Hostname() == "169.254.169.254" ||
parsedURL.Hostname() == "169.254.170.2" {
return "", errors.New("cloud metadata access is not allowed")
}
return parsedURL.String(), nil
}
func isPrivateIP(host string) bool {
ip := net.ParseIP(host)
if ip == nil {
return false
}
return ip.IsPrivate() || ip.IsLoopback() || ip.IsUnspecified()
}
Echo Go Context Validation Middleware: Create reusable middleware for Echo Go that validates all incoming request parameters:
func SSRFValidationMiddleware() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Check query parameters
for _, param := range c.QueryParams() {
if isPotentiallyDangerousURL(param) {
return echo.NewHTTPError(http.StatusBadRequest, "invalid URL parameter")
}
}
// Check path parameters
for _, param := range c.ParamValues() {
if isPotentiallyDangerousURL(param) {
return echo.NewHTTPError(http.StatusBadRequest, "invalid path parameter")
}
}
return next(c)
}
}
}
func isPotentiallyDangerousURL(input string) bool {
// Simple check for URL-like patterns
lower := strings.ToLower(input)
return strings.Contains(lower, "http://") ||
strings.Contains(lower, "https://") ||
strings.Contains(lower, "localhost") ||
strings.Contains(lower, "127.0.0.1")
}
Safe HTTP Client Configuration: When making external requests in Echo Go, configure safe defaults:
import (
"net"
"net/http"
"time"
)
func createSafeHTTPClient() *http.Client {
return &http.Client{
Timeout: 10 * time.Second, // Prevent slowloris attacks
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// Custom dialer to block private networks
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
if isPrivateIP(host) {
return nil, errors.New("connection to private network denied")
}
return net.Dial(network, addr)
},
// Additional security configurations
DisableKeepAlives: true,
MaxIdleConns: 10,
},
}
}
Echo Go-Specific Allowlist Approach: For applications that need to make external requests, implement an allowlist of approved domains:
var allowedDomains = map[string]bool{
"api.example.com": true,
"webhook.example.com": true,
}
func makeSafeRequest(c echo.Context) error {
target := c.QueryParam("target")
parsedURL, err := url.Parse(target)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid URL format")
}
if !allowedDomains[parsedURL.Hostname()] {
return echo.NewHTTPError(http.StatusForbidden, "domain not allowed")
}
client := createSafeHTTPClient()
resp, err := client.Get(target)
if err != nil {
return echo.NewHTTPError(http.StatusBadGateway, "external service error")
}
defer resp.Body.Close()
return c.Stream(resp.StatusCode, resp.Header.Get("Content-Type"), resp.Body)
}
Echo Go Testing Integration: Add SSRF-specific tests to your Echo Go test suite:
func TestSSRFValidation(t *testing.T) {
e := echo.New()
// Test with malicious URLs
testCases := []struct {
input string
expected int
}{
{"http://localhost/test", http.StatusBadRequest},
{"http://169.254.169.254/metadata", http.StatusBadRequest},
{"http://10.0.0.1/test", http.StatusBadRequest},
{"http://valid.example.com/api", http.StatusOK},
}
for _, tc := range testCases {
req := httptest.NewRequest(http.MethodGet, "/test?url="+tc.input, nil)
rec := httptest.NewRecorder()
// Apply SSRF validation middleware
err := SSRFValidationMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "ok")
})(echo.NewContext(req, rec))
if err != nil && err.(*echo.HTTPError).Code != tc.expected {
t.Errorf("Expected %d for %s, got %v", tc.expected, tc.input, err)
}
}
}