Xml External Entities in Gin with Hmac Signatures
Xml External Entities in Gin with Hmac Signatures — how this specific combination creates or exposes the vulnerability
XML External Entity (XXE) injection occurs when an XML parser processes external entity references within untrusted XML data. In Go, the standard encoding/xml parser can be configured to resolve external entities, which may lead to local file reads, SSRF, or denial-of-service via billion laughs attacks. Gin is a popular HTTP framework that does not enable XML parsing by default; developers add XML binding when they accept or return XML payloads, for example via gin.Context.BindXML. When you combine Gin handlers that parse XML with the use of Hmac Signatures for request authentication, a common pattern is to compute an Hmac over selected headers and the request body and then verify the signature before processing. If the body is untrusted XML and the Hmac verification occurs after or independently of safe parsing configuration, an attacker can submit a malicious XML payload that triggers XXE during binding. The Hmac Signature can inadvertently validate the attacker’s request because the signature is computed over the raw body, which includes the malicious entity references, and the server verifies the Hmac before enforcing strict parser settings. This combination exposes an XXE vector: the signature check gives a false sense of integrity, while the XML parser processes external entities, potentially reading files on the server or causing resource exhaustion. For example, an attacker sends an XML body with a DOCTYPE that references file:///etc/passwd, the Hmac matches (because both client and server compute the same signature using shared secret), and Gin’s BindXML parses the body without disabling external entity resolution, leading to unintended data exposure. The risk is amplified when the endpoint trusts the Hmac but does not enforce secure XML parser options such as disabling external entity processing before binding.
Hmac Signatures-Specific Remediation in Gin — concrete code fixes
To mitigate XXE when using Hmac Signatures in Gin, ensure XML parsing is configured securely and that signature verification does not encourage unsafe parsing. Use a dedicated XML decoder with Decoder.Token to inspect and reject external entity references before binding, or switch to a safer XML library that disables external entities by default. When computing and verifying Hmac Signatures, always validate the signature against a canonical representation and enforce secure parser settings as part of the verification flow. Below are concrete, working examples for Gin handlers that combine Hmac verification with safe XML handling.
Example 1: Reject external entities by using a strict XML decoder
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/xml"
"io"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// verifyHmac checks the X-Signature header against the Hmac of the body using a shared secret.
// It returns true if valid, false otherwise.
func verifyHmac(body []byte, receivedSig string, secret []byte) bool {
mac := hmac.New(sha256.New, secret)
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(receivedSig))
}
// safeDecodeXML decodes XML without processing external entities.
// It uses xml.NewDecoder and disables external entity resolution by not setting DTD or EntityResolver.
// For stricter control, iterate tokens and reject ExternalElement tokens.
func safeDecodeXML(data []byte, v interface{}) error {
const entityReplacementLimit = 1024
dec := xml.NewDecoder(strings.NewReader(string(data)))
dec.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
return input, nil
}
// Optionally, wrap dec.Token to reject external entities explicitly.
// Here we simply decode with default behavior; ensure the parser does not resolve externals.
// Use decoder.Token to inspect and reject ExternalTok if needed.
return dec.Decode(v)
}
type MyPayload struct {
XMLName xml.Name `xml:"data"`
Field string `xml:"field"`
}
func hmacProtectedXML(c *gin.Context) {
secret := []byte("super-secret-key") // in practice, load from secure configuration
sig := c.GetHeader("X-Signature")
if sig == "" {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing signature"})
return
}
body, err := c.GetRawData()
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "cannot read body"})
return
}
if !verifyHmac(body, sig, secret) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid signature"})
return
}
var payload MyPayload
if err := safeDecodeXML(body, &payload); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid XML"})
return
}
// Process payload safely
c.JSON(http.StatusOK, gin.H{"received": payload.Field})
}
Example 2: Reject external entities by validating tokens before decode
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/xml"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// verifyHmacSame as above.
func verifyHmac(body []byte, receivedSig string, secret []byte) bool {
mac := hmac.New(sha256.New, secret)
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(receivedSig))
}
// tokenBasedSafeDecode decodes XML and rejects external entity references by inspecting tokens.
func tokenBasedSafeDecode(data []byte, v interface{}) error {
dec := xml.NewDecoder(strings.NewReader(string(data)))
for {
tok, err := dec.Token()
if err != nil {
break
}
switch se := tok.(type) {
case xml.StartElement, xml.EndElement:
// Optionally inspect se.Name and attributes, but by default allow.
// To harden, you can maintain a denylist of entities or DTD tokens.
// For this example, we simply continue.
}
// Note: xml.ExternalEntity is not a token type in the standard library.
// To fully prevent XXE, avoid passing an io.Reader that exposes externals,
// and do not set an EntityResolver on a custom decoder. The standard
// decoder does not resolve external entities unless you provide a custom
// EntityResolver via a custom Reader, which we omit.
}
return dec.Decode(v)
}
type MyPayload struct {
XMLName xml.Name `xml:"data"`
Field string `xml:"field"`
}
func hmacProtectedXMLStrict(c *gin.Context) {
secret := []byte("super-secret-key")
sig := c.GetHeader("X-Signature")
if sig == "" {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing signature"})
return
}
body, err := c.GetRawData()
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "cannot read body"})
return
}
if !verifyHmac(body, sig, secret) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid signature"})
return
}
var payload MyPayload
if err := tokenBasedSafeDecode(body, &payload); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid XML"})
return
}
c.JSON(http.StatusOK, gin.H{"received": payload.Field})
}
Operational guidance
- Do not rely on Hmac Signature verification alone to ensure request safety; treat the signature as authenticity, not as a sanitization step.
- Configure XML parsing to avoid external entity resolution by not providing a custom
EntityResolverand by avoiding legacy features that trigger network or file I/O. - If you must process untrusted XML, consider using JSON or a schema-limited format, or employ a hardened XML parser that disables DTD and external entities by default.
- Use the middleBrick Dashboard to track your API security scores over time and the CLI (
middlebrick scan <url>) for quick unauthenticated scans that include XML-related findings when applicable.
Frequently Asked Questions
Does using Hmac Signatures with XML increase risk if the signature is verified before parsing?
Can middleBrick detect XXE issues in Gin APIs that use Hmac Signatures?
middlebrick scan <url>), Web Dashboard, or the GitHub Action to include these findings in your CI/CD pipeline; the MCP Server also allows scanning from your AI coding assistant.