Description
Resolving external entities in untrusted XML can access local files or network resources. Despite its name, XMLParseNoEnt in github.com/lestrrat-go/libxml2/parser enables entity substitution. Impact depends on the libxml2 version, loader configuration, application permissions and use of the result.
Potential impact
- Internal files readable by the parser may be exposed through results or errors.
- An enabled network loader may send requests to internal services.
- Entity expansion or large inputs may exhaust CPU or memory.
Remediation
- Do not enable unnecessary entity substitution or external DTD loading for untrusted XML.
- Verify that the installed Go binding and libxml2 configuration block external resource loading. Blocking network access alone does not block local file access.
- Limit input size while reading and apply required schema validation. Schema or Content-Type validation does not replace blocking external entities.
- Minimize file and network permissions, and use controlled tests to confirm that external resources are not read.
Examples
These HTTP examples require the Go binding and libxml2. Production authentication and TLS setup are omitted. The before-example's allocation from ContentLength and single Read also fail to handle unknown lengths and partial reads safely.
Before
go
package main
import (
"fmt"
"net/http"
"github.com/lestrrat-go/libxml2/parser"
)
// Before: parse uploaded XML directly
func parseXMLUnsafe(w http.ResponseWriter, r *http.Request) {
body := make([]byte, r.ContentLength)
if _, err := r.Body.Read(body); err != nil {
http.Error(w, "read error", http.StatusBadRequest)
return
}
// CWE-611: enable entity substitution and external entity loading
p := parser.New(parser.XMLParseNoEnt)
doc, err := p.ParseString(string(body))
if err != nil {
http.Error(w, "parse error", http.StatusBadRequest)
return
}
defer doc.Free()
fmt.Fprintln(w, "XML parsed (unsafe)")
}
func main() {
http.HandleFunc("/upload", parseXMLUnsafe)
http.ListenAndServe(":8080", nil)
}
After
go
package main
import (
"fmt"
"io"
"net/http"
"github.com/lestrrat-go/libxml2/parser"
)
// Limit input size and omit the entity-substitution option
func parseXMLSafe(w http.ResponseWriter, r *http.Request) {
const maxBody = 1 << 20
body, err := io.ReadAll(io.LimitReader(r.Body, maxBody+1))
if err != nil {
http.Error(w, "read error", http.StatusBadRequest)
return
}
if len(body) > maxBody {
http.Error(w, "body too large", http.StatusRequestEntityTooLarge)
return
}
// 1) Do not enable substitution or external DTD loading
p := parser.New() // No XMLParseNoEnt
// 2) Perform required schema validation separately
// Schema validation does not replace blocking external resources
doc, err := p.ParseString(string(body))
if err != nil {
http.Error(w, "parse error", http.StatusBadRequest)
return
}
defer doc.Free()
fmt.Fprintln(w, "XML parsed safely")
}
func main() {
http.HandleFunc("/upload", parseXMLSafe)
http.ListenAndServe(":8080", nil)
}
Explanation:
- Before:
XMLParseNoEntenables entity substitution. Whether input such as the following file reference is resolved depends on the loader and permissions. Even if the file is read, exposure in a response depends on application logic.
xml
<!DOCTYPE d [<!ENTITY x SYSTEM "file:///etc/passwd">]>
<data>&x;</data>
- After: The reader accepts at most one byte beyond 1 MiB and rejects oversized input.
parser.New()receives no substitution or external DTD loading options. Also check that other loaders or global settings do not re-enable external access; input-size limits alone do not prevent every parsing resource-exhaustion issue.