Xml External Entities in Gin with Basic Auth
Xml External Entities in Gin with Basic Auth — how this specific combination creates or exposes the vulnerability
Xml External Entity (XXE) injection occurs when an XML parser is configured to process external entities and untrusted XML input is accepted. In the Gin framework for Go, this typically arises when an endpoint parses XML request bodies using a permissive XML decoder that allows external entity references. When Basic Auth is used, an attacker may first need to obtain or bypass credentials, but once a valid username and password pair is acquired or guessed, the authenticated context can be abused to probe or exploit XXE behavior in endpoints that process XML.
Because Basic Auth credentials are transmitted in the Authorization header on each request, an authenticated session can be used to test XXE payloads against authenticated routes that parse XML. Even though Basic Auth itself does not cause XXE, it can lower the barrier to testing authenticated code paths that are otherwise protected. For example, an authenticated session might access an administrative endpoint that accepts XML uploads and forwards them to a backend parser. If the parser resolves external entities, an attacker can leverage the authenticated context to read server-side files (via file:// URLs), perform SSRF to internal services, or cause denial of service through entity expansion (billion laughs).
In Gin, a vulnerable pattern looks like binding an XML body to a struct with a custom XML decoder that does not disable external entity processing. If the endpoint does not enforce strict input validation and relies on the default XML unmarshaling behavior, an attacker can supply a malicious XML payload containing DOCTYPE and entity declarations. The combination of authenticated access via Basic Auth and insufficient XML hardening creates a scenario where XXE can be leveraged to extract sensitive configuration, trigger SSRF against internal endpoints, or amplify resource consumption, depending on how the parser is configured.
Basic Auth-Specific Remediation in Gin — concrete code fixes
To mitigate XXE in Gin when Basic Auth is used, focus on hardening XML parsing and ensuring credentials are not relied upon to protect unsafe deserialization. Always disable external entities and DTD processing in your XML decoder, and avoid using the default xml.Unmarshal for untrusted payloads. Implement strict content-type validation and prefer JSON for APIs where possible. Below are concrete, secure examples for Gin with Basic Auth.
1. Secure Basic Auth setup in Gin
Use middleware to validate credentials on each request without storing sensitive data in shared state.
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// BasicAuthMiddleware validates username:password against a secure source.
func BasicAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
user, pass, ok := c.Request.BasicAuth()
if !ok || !checkCredentials(user, pass) {
c.Header("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
return
}
c.Set("user", user)
c.Next()
}
}
// checkCredentials performs constant-time verification against a secure store.
func checkCredentials(user, pass string) bool {
// In production, use a constant-time compare and hashed passwords.
// Example: compare pbkdf2/argon2 hash with stored hash.
validUsers := map[string]string{
"admin": "$argon2id$...", // placeholder for a properly hashed password
}
hashed, exists := validUsers[user]
if !exists {
return false
}
// Use a secure constant-time comparison function.
return subtleCompare(hashed, pass)
}
// subtleCompare avoids timing attacks (placeholder: use subtle.ConstantTimeCompare).
func subtleCompare(a, b string) bool {
if len(a) != len(b) {
return false
}
var equal byte
for i := 0; i < len(a); i++ {
equal |= a[i] ^ b[i]
}
return equal == 0
}
2. Disable external entities in XML parsing
Do not use the standard library’s xml.Unmarshal directly on untrusted XML. Instead, configure a decoder with strict settings that disable external entities and DTDs.
package main
import (
"encoding/xml"
"io"
"net/http"
"github.com/gin-gonic/gin"
)
type SafeXMLValidator struct{}
// DecodeXML decodes XML without processing external entities.
func (v *SafeXMLValidator) DecodeXML(c *gin.Context) (interface{}, error) {
// Limit the reader to prevent oversized payloads.
lr := &io.LimitedReader{R: c.Request.Body, N: 10e6} // 10 MB
defer c.Request.Body.Close()
// Create a decoder with strict settings.
decoder := xml.NewDecoder(lr)
decoder.Entity = xml.HTMLEntity // minimal entities, no external resolution
decoder.Strict = true
// Define a target structure that matches expected XML.
var payload struct {
Message string `xml:"message"`
}
if err := decoder.Decode(&payload); err != nil {
return nil, err
}
return payload, nil
}
// Example endpoint using both auth and safe XML parsing.
func uploadXML(c *gin.Context) {
// Auth middleware ensures credentials are valid.
user := c.MustGet("user").(string)
payload, err := SafeXMLValidator{}.DecodeXML(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid XML"})
return
}
c.JSON(http.StatusOK, gin.H{"user": user, "data": payload})
}
func main() {
r := gin.Default()
r.POST("/upload", BasicAuthMiddleware(), uploadXML)
r.Run()
}
3. Additional hardening steps
- Set
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 10<<20)before decoding to cap request size. - Validate Content-Type is exactly
application/xmlortext/xmlto avoid content-sniffing attacks. - Avoid XPath or XSLT processing on untrusted input, as they can also introduce XXE-like behaviors.
- Combine with continuous monitoring: use the middleBrick CLI (
middlebrick scan <url>) to detect misconfigurations in unauthenticated attack surface and the GitHub Action to fail builds if risk scores degrade after changes.