Skip to content

XML External Entity (XXE) Processing

What does this mean ?

An XML External Entity attack is a sort of attack against an application that parses XML input. This attack happens when an XML parser with a poorly set configuration processes XML input containing a reference to an external object. Some apps utilize the XML format to send data between the browser and the server. Applications that accomplish this almost always utilize a standard library or platform API to parse the XML data on the server. XXE vulnerabilities emerge because the XML specification provides a number of potentially harmful features, and standard parsers support these features even if they are not commonly utilized by the application.

What can happen ?

This attack may result in the leaking of private data, denial of service, server-side request forgery, port scanning from the perspective of the machine where the parser is situated, and other system consequences. Using file: schemes or relative paths in the system identifier, attackers can reveal local files that may contain sensitive data such as passwords or secret user data. Because the attack happens relative to the application processing the XML document, an attacker may utilize this trusted application to pivot to other internal systems, potentially releasing further internal content via http(s) requests or initiating a CSRF attack against any vulnerable internal services.

Recommendation

  • Almost all XXE vulnerabilities originate as a result of the application's XML parsing library supporting potentially harmful XML features that the program does not require or intends to utilize. Disabling such functionalities is the simplest and most efficient technique to avoid XXE attacks.
  • In general, disabling external entity resolution and XInclude support is sufficient. This is normally accomplished using configuration settings or by altering default behavior programmatically. For further information on how to deactivate superfluous features, consult the documentation for your XML parsing library or API.

Sample Code

Vulnerable :

XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = false;
XmlReader reader = XmlReader.Create(inputXml, settings);
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;
XmlReader reader = XmlReader.Create(inputXml, settings);

Non Vulnerable :

//Avoid overriding the secure settings
XmlReader reader = XmlReader.Create(inputXml);
XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = true;
XmlReader reader = XmlReader.Create(inputXml, settings);
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Ignore;
XmlReader reader = XmlReader.Create(inputXml, settings);

Vulnerable :

DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = db.parse(input);

Non Vulnerable :

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(input);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(input);

Vulnerable :

$xml = file_get_contents("xxe.xml");
$doc = simplexml_load_string($xml, "SimpleXMLElement", LIBXML_NOENT); // Noncompliant (LIBXML_NOENT enable external entities substitution)
$doc = new DOMDocument();
$doc->load("xxe.xml", LIBXML_NOENT); // Noncompliant (LIBXML_NOENT enable external entities substitution)
$reader = new XMLReader();
$reader->open("xxe.xml");
$reader->setParserProperty(XMLReader::SUBST_ENTITIES, true); // Noncompliant (SUBST_ENTITIES enable external entities substitution)

Non Vulnerable :

$xml = file_get_contents("xxe.xml");
$doc = simplexml_load_string($xml, "SimpleXMLElement"); // Compliant (external entities substitution are disabled by default)
$doc = new DOMDocument();
$doc->load("xxe.xml"); // Compliant (external entities substitution are disabled by default)
$reader = new XMLReader();
$reader->open("xxe.xml");
$reader->setParserProperty(XMLReader::SUBST_ENTITIES, false); // Compliant (SUBST_ENTITIES set to false)

Vulnerable :

const app = require("express")(),
const libxml = require("libxmljs");

app.post("/parser", (req, res) => {
    let xmlPayload = req.body,
    doc = libxml.parseXml(xmlPayload, { noent: true });
});

Non Vulnerable :

const app = require("express")(),
const libxml = require("libxmljs");

app.post("/parser", (req, res) => {
    let xmlPayload = req.body,
    doc = libxml.parseXml(xmlPayload);
});

Vulnerable :

const libxmljs = require("libxmljs");
const fs = require('fs');

const xml = fs.readFileSync('sample.xml', 'utf8');

const xmlDoc = libxmljs.parseXmlString(xml, { noblanks: true, noent: true, nocdata: true }); // vulnerable: noent set to true

Non Vulnerable :

const libxmljs = require("libxmljs");
const fs = require('fs');

const xml = fs.readFileSync('sample.xml', 'utf8');

const xmlDoc = libxmljs.parseXmlString(xml); // Non-vulnerable: noent set to false by default

Vulnerable :

file, err := c.FormFile("xml")
xml, err := file.Open()
defer xml.Close()

p := parser.New(parser.XMLParseNoEnt)
doc, err := p.ParseReader(xml)
defer doc.Free()

Non Vulnerable :

func identReaderParser(encoding string, input io.Reader) (io.Reader, error) {
    return input, nil
}
func main() {

    xmlFile, err := os.Open("sample.xml") 
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println("Successfully Opened sample.xml")
    defer xmlFile.Close()

    decoder := xml.NewDecoder(xmlFile)
    decoder.Strict = false
    decoder.CharsetReader = identReaderParser
    for {
        token, err := decoder.Token()
        if err != nil && err != io.EOF {
            fmt.Printf("Error! Decoding XML failed: %v\n", err)
            break
        }
        if token == nil {
            break
        }
        switch element := token.(type) {
        case xml.StartElement:
            fmt.Printf("<%s>", element.Name.Local)
        case xml.EndElement:
            fmt.Printf("</%s>", element.Name.Local)
        case xml.CharData:
            fmt.Printf("%s", element)
        }
    }
}

Vulnerable :

require 'sinatra'
require 'nokogiri'

post '/parse-xml' do
    xml_payload = params[:xml_payload]
    parsed_xml = Nokogiri::XML(xml_payload) do |config|
        config.options = Nokogiri::XML::ParseOptions::NOENT
    end
    return "#{parsed_xml}"
end

Non Vulnerable :

require 'xml'
require 'libxml'

ActiveSupport::XmlMini.backend = 'LibXML'

LibXML::XML.class_eval do
    def self.default_substitute_entities
        XML.default_substitute_entities = false
    end
end

References