How We Audited a High-Traffic C Enterprise Stack on AWS and Mitigated XML External Entity (XXE) injection in old SOAP integrations
The Challenge: Legacy SOAP and the XXE Threat
Our enterprise-level C application, a critical component of our financial services platform, relied heavily on older SOAP integrations for inter-service communication. While robust for its time, this architecture presented a significant security vulnerability: XML External Entity (XXE) injection. The application processed a high volume of inbound SOAP requests, many originating from less trusted external partners. A successful XXE attack could allow an attacker to read sensitive files from the server’s filesystem, perform Server-Side Request Forgery (SSRF) attacks, or even trigger denial-of-service conditions.
The primary concern was the application’s XML parser, which, by default, was configured to resolve external entities. This is a common default in many XML processing libraries, especially older versions. Given the sheer volume of traffic and the sensitive nature of the data being processed, a proactive, deep-dive audit was imperative. We needed to not only identify the vulnerable endpoints but also implement robust mitigation strategies without disrupting critical business operations.
Phase 1: Discovery and Reconnaissance
Our initial phase focused on identifying all SOAP endpoints and understanding their request/response structures. We leveraged a combination of static analysis and dynamic testing.
Static Analysis of SOAP Endpoints
We began by examining the WSDL (Web Services Description Language) files exposed by our SOAP services. These files provide a machine-readable description of the web service, including the operations it supports and the message formats. We wrote a Python script to parse these WSDLs and identify all defined operations and their associated XML schemas.
import xml.etree.ElementTree as ET
def find_soap_operations(wsdl_content):
operations = []
try:
root = ET.fromstring(wsdl_content)
# Namespace handling is crucial for WSDLs
namespaces = {
'wsdl': 'http://schemas.xmlsoap.org/wsdl/',
'soap11': 'http://schemas.xmlsoap.org/wsdl/soap/',
'soap12': 'http://schemas.xmlsoap.org/wsdl/soap12/',
'xsd': 'http://www.w3.org/2001/XMLSchema'
}
# Find all operations
for portType in root.findall('.//wsdl:portType', namespaces):
for operation in portType.findall('.//wsdl:operation', namespaces):
operation_name = operation.get('name')
input_message_name = None
output_message_name = None
input_element = operation.find('.//wsdl:input', namespaces)
if input_element is not None:
input_message_name = input_element.get('message').split(':')[-1]
output_element = operation.find('.//wsdl:output', namespaces)
if output_element is not None:
output_message_name = output_element.get('message').split(':')[-1]
operations.append({
'name': operation_name,
'input_message': input_message_name,
'output_message': output_message_name
})
return operations
except ET.ParseError as e:
print(f"Error parsing WSDL: {e}")
return []
# Example usage (assuming wsdl_content is loaded from a file or URL)
# with open('service.wsdl', 'r') as f:
# wsdl_content = f.read()
# ops = find_soap_operations(wsdl_content)
# print(ops)
This script helped us catalog every exposed SOAP operation. For each operation, we then focused on its input message definition, looking for any elements that might be susceptible to external entity expansion. This often involved examining the XSD (XML Schema Definition) referenced by the WSDL.
Dynamic Testing with XXE Payloads
To confirm our findings and uncover any parser configurations we missed in static analysis, we employed dynamic testing. We used tools like Burp Suite and custom scripts to craft malicious SOAP requests containing XXE payloads. The goal was to trigger the parser to attempt to fetch external resources or read local files.
A common XXE payload targets the `DOCTYPE` declaration. We experimented with variations to read sensitive files like `/etc/passwd` or configuration files specific to our AWS environment.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soapenv:Header/>
<soapenv:Body>
<your_operation_name xmlns="your_namespace">
<your_parameter>&xxe;</your_parameter>
</your_operation_name>
</soapenv:Body>
</soapenv:Envelope>
We also tested for SSRF by attempting to access internal AWS metadata endpoints (e.g., `http://169.254.169.254/latest/meta-data/`). This requires the XML parser to make an HTTP request to an external (or internal) URL specified in the entity.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME">
]>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soapenv:Header/>
<soapenv:Body>
<your_operation_name xmlns="your_namespace">
<your_parameter>&xxe;</your_parameter>
</your_operation_name>
</soapenv:Body>
</soapenv:Envelope>
Monitoring application logs and network traffic (using AWS VPC Flow Logs and potentially Intrusion Detection Systems) was crucial during this phase to detect any successful external entity resolution attempts.
Phase 2: Mitigation Strategies
Once we had a clear picture of the vulnerable endpoints and the specific XML parsing libraries in use (primarily libxml2 in our C application), we implemented a multi-layered mitigation strategy.
1. Disabling External Entity Resolution at the Parser Level
This is the most direct and effective defense. For libxml2, this involves setting specific parser options. We modified the C code responsible for parsing incoming SOAP requests.
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlerror.h>
// ... inside your SOAP request handling function ...
xmlDocPtr doc = NULL;
xmlParserCtxtPtr ctxt = NULL;
// Create a parser context
ctxt = xmlNewParserCtxt();
if (ctxt == NULL) {
// Handle error: could not create parser context
return ERROR;
}
// Set parser options to disable external entities
// LIBXML_PARSE_NOENT: Disable entity substitution (e.g., &xxe;)
// LIBXML_PARSE_NONET: Disable network access (prevent fetching external DTDs or entities)
// LIBXML_XINCLUDE: Disable XInclude processing, which can also fetch external resources
xmlParserCtxtUseOptions(ctxt, LIBXML_PARSE_NOENT | LIBXML_PARSE_NONET | LIBXML_XINCLUDE);
// Parse the XML document from a buffer (e.g., incoming request body)
// Assuming 'xml_buffer' is a char* containing the XML and 'buffer_size' is its length
doc = xmlCtxtReadFile(ctxt, "noname.xml", NULL, buffer_size, xml_buffer, NULL);
if (doc == NULL) {
// Handle parsing error. xmlCtxtGetLastError() can provide details.
xmlErrorPtr error = xmlCtxtGetLastError(ctxt);
if (error) {
fprintf(stderr, "XML Parsing Error: %s (line %d, col %d)\n", error->message, error->line, error->intSub);
}
xmlFreeParserCtxt(ctxt);
return ERROR;
}
// ... process the parsed XML document ...
// Clean up
xmlFreeDoc(doc);
xmlFreeParserCtxt(ctxt);
// xmlCleanupParser(); // Call this once at application exit if needed
The key options here are `LIBXML_PARSE_NOENT` and `LIBXML_PARSE_NONET`. `LIBXML_PARSE_NOENT` prevents the substitution of general entities, which is the core of many XXE attacks. `LIBXML_PARSE_NONET` is crucial for preventing the parser from making network requests to fetch external DTDs or entities, mitigating SSRF vectors.
2. Input Validation and Sanitization
While disabling external entities is paramount, robust input validation acts as a secondary defense. We implemented checks to ensure that incoming XML payloads do not contain `DOCTYPE` declarations or entity declarations that are not explicitly allowed. This is more complex and can be prone to bypasses if not done meticulously, but it adds another layer of defense.
For example, we could use regular expressions to pre-scan the incoming request body for suspicious patterns before even attempting to parse it. However, this is fragile. A more robust approach involves validating the XML structure against a known-good schema *after* parsing, ensuring no unexpected elements or entity references are present.
// Example in PHP (conceptual, as the core C app is the target)
// In a PHP gateway or proxy layer, you might do this:
function is_safe_xml($xml_string) {
// Basic check for DOCTYPE - not foolproof but a first pass
if (preg_match('/<!DOCTYPE/i', $xml_string)) {
// More sophisticated checks needed here, e.g., disallowing SYSTEM or PUBLIC keywords
// or specific entity declarations.
// A better approach is to use a DOM parser with security options.
return false;
}
// Using DOMDocument with security options (PHP 8+)
$dom = new DOMDocument();
// Disable external entity loading
$dom->loadXML($xml_string, LIBXML_NOENT | LIBXML_NONET);
// Check for libxml errors after loading
$errors = libxml_get_errors();
if (!empty($errors)) {
foreach ($errors as $error) {
// Log or handle specific errors indicating XXE attempts
if ($error->level == LIBXML_ERR_FATAL || $error->level == LIBXML_ERR_ERROR) {
// Potentially an XXE attempt or malformed XML
error_log("XML Error: " . $error->message);
return false;
}
}
}
// Further validation against expected schema could go here
// ...
return true;
}
// $soap_request_body = file_get_contents('php://input');
// if (is_safe_xml($soap_request_body)) {
// // Proceed with processing
// } else {
// // Reject request
// }
3. Network-Level Controls and WAF
While not a primary defense against XXE itself (as the attack is within the XML payload), Web Application Firewalls (WAFs) can be configured to detect and block common XXE patterns in the request body. This provides an additional layer of defense, especially against known attack vectors.
We deployed AWS WAF rules to inspect the request body for suspicious XML constructs, such as `DOCTYPE` declarations with `SYSTEM` keywords or common external entity references. These rules were carefully tuned to minimize false positives, as our SOAP traffic is extensive and complex.
// Example AWS WAF Rule (Conceptual - JSON format)
{
"Name": "XXE_Detection_Rule",
"Priority": 1,
"Action": {
"Block": {}
},
"Statement": {
"ByteMatchStatement": {
"SearchString": "<!DOCTYPE", // Basic check, needs refinement
"FieldToMatch": {
"Body": {
"OversizeHandling": "CONTINUE"
}
},
"PositionalConstraint": "CONTAINS",
"TextTransformation": {
"Priority": 0,
"Type": "LOWERCASE"
}
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "XXE_Detection_Metric"
}
}
// More advanced rules would use RegexMatchStatement for more precise pattern matching
// and target specific parts of the XML body.
4. Dependency Management and Patching
We reviewed the versions of the XML parsing libraries used by our C application and its dependencies. Older versions of libraries are more likely to have known vulnerabilities or less secure default configurations. We initiated a process to update libxml2 and any other XML-related libraries to their latest stable versions, ensuring we benefit from security patches and improved default settings.
Phase 3: Verification and Monitoring
After implementing the mitigations, a rigorous verification phase was essential. We re-ran our dynamic XXE testing suite against the production environment (in a controlled manner, potentially during a maintenance window or against a staging environment mirroring production). The goal was to confirm that all previously identified XXE vulnerabilities were no longer exploitable.
Re-testing with XXE Payloads
We used the same payloads developed in Phase 1. This time, instead of observing successful file reads or SSRF attempts, we expected to see the application either reject the request gracefully (e.g., with a malformed XML error) or process the payload literally without resolving external entities. The key was that no sensitive data should be exfiltrated, and no internal systems should be accessed via SSRF.
Continuous Monitoring
Security is an ongoing process. We enhanced our logging and monitoring to specifically track XML parsing errors and any suspicious patterns in incoming SOAP requests. AWS CloudWatch alarms were configured to alert the security team if a high volume of XML parsing errors or WAF blocks related to XXE patterns occurred.
We also integrated security scanning tools into our CI/CD pipeline. While static analysis tools might struggle with complex C code and its runtime configurations, they can still flag potential issues with XML handling. Regular penetration testing, including specific XXE test cases, is now a scheduled part of our security assurance program.
Conclusion
Auditing and securing legacy SOAP integrations against XXE injection in a high-traffic enterprise C application on AWS required a systematic approach. By combining static analysis of WSDLs, dynamic testing with crafted payloads, and implementing layered defenses—primarily by disabling external entity resolution at the parser level in our C code, supplemented by input validation and WAF rules—we successfully mitigated this critical vulnerability. Continuous monitoring and regular re-testing are now integral to maintaining the security posture of our financial platform.