Can I protect my SOAP / XML services; is there protection against XXE attacks?
Short answer
Yes. Requests whose Content-Type is of the …/xml or …+xml type (application/xml, text/xml, application/soap+xml) are parsed as XML; element and attribute values are scanned with signatures. In the Xml tab of the page type, the allowed element paths (Allowed XML Keys; in SOAP with a prefix such as soap:Envelope.soap:Body, attributes with _attr), nesting depth, key count, and value length and pattern are limited. XML that cannot be parsed is blocked with Block Invalid XML Contents; this setting is on by default in the Any Paths rule. On endpoints that should not accept XML, Block All XML Contents is used. For XXE, the signature library has the XML External Entity category (69 rules); bodies whose DOCTYPE declaration carries an internal definition section in square brackets (e.g. an <!ENTITY …> definition) cannot be parsed by TR7's XML parser, receive a Block Invalid XML Contents violation and are scanned as a raw body with these signatures. TR7's parser does not resolve external entities. Since in a document that can be parsed the DOCTYPE declaration is not evaluated as XML content, on services that must reject all requests carrying DOCTYPE, the HTTP Request Body condition in Load Balancer is bound to the Block action. The permanent measure against XXE is turning off DTD and external entity processing in the XML parser on the Backend Service (OWASP XXE Prevention).
Problem: Services with XML Bodies Carry an Additional Attack Surface
SOAP and XML APIs carry structured data in the body; unexpected elements, excessively deep documents and injections embedded in values can reach the application. In an XXE (XML External Entity) attack, on the other hand, the client defines an external entity in the DOCTYPE declaration at the beginning of the document; if the parser on the backend resolves this entity, a file on the server can be read or a request to the internal network can be triggered (OWASP XXE). The result of the attack depends on the setting of the backend parser; the WAF and Load Balancer layers work as additional layers in front of this setting.
The path of an XML request in TR7:
| Body | What happens |
|---|---|
XML that can be parsed (without DOCTYPE or with a DOCTYPE that has no internal definition section, e.g. <!DOCTYPE r SYSTEM "…">) | Elements and attributes go through the checks of the Xml tab and the XML-scoped signatures. The DOCTYPE declaration does not enter these checks. |
A body whose DOCTYPE carries an internal definition section in square brackets (<!DOCTYPE r [<!ENTITY …>]>; other definitions such as <!ELEMENT …> give the same result) | TR7's parser cannot parse it; if Block Invalid XML Contents is on, a 24-point violation is written and the body goes through signature scanning in its raw form (e.g. XXE Entity SYSTEM URI Schemes, XXE External DTD Declaration). |
| Malformed XML | In the same way, Block Invalid XML Contents and raw body scanning. |
The text searched for in the HTTP Request Body condition (e.g. <!DOCTYPE) | It is searched for in the first bytes of the body independently of parsing; the Block action bound to the condition rejects the request. |
Solution: 3 Methods
The methods are used together: Method 1 defines the expected structure of the SOAP or XML endpoint, and Method 2 rejects requests that carry DOCTYPE independently of the layer, and Method 3 writes both from WEB-CLI. For the Xml rule, WAF must be on in the vService (How do I enable WAF protection on a vService?); the HTTP Request Body condition and the Block action do not require WAF.
| Your situation | Suitable method |
|---|---|
| I want to define the element paths and limits that the SOAP or XML endpoint accepts | Method 1: Xml tab |
| My service does not use DOCTYPE; I want to reject every request that carries DOCTYPE | Method 2: HTTP Request Body condition + Block |
| I want to configure it from the command line | Method 3: WEB-CLI |
Method 1: Xml Tab Recommended
- How it works The Xml common rule of the endpoint compares every element and attribute path in the body with the allowlist; it applies the depth, key count and value length limits. Every path that is not in the list is a 24-point violation.
- Where it is opened Left Monitor Panel → WAF-Protected vServices → vService → WAF Management → N page types → rule → the Xml tab in the detail panel
- Requirement Structure Checks and Argument Checks on in Checks; the host group in Monitor or Blocking mode; modify permission in the WAF area
- vService type HTTP
Step-by-Step Configuration
On the WAF Management tab, the pencil icon of the Checks card is clicked; Structure Checks and Argument Checks must be checked. In SOAP envelopes, the number of paths easily exceeds the Max. Allowed Arguments value (default 25); the value is made larger than the number of paths in the list and Edit is clicked.
On the WAF Management tab, the N page types row under the mode card of the host group is clicked.
If there is no rule for the path of the service (e.g. /ws/OrderService), Add in the toolbar is clicked, Path is entered, POST is selected with Allowed Methods = Multiple Methods, and Add in the form is clicked (How do I add a new page type manually?).
The rule is clicked in the list; in the detail panel on the right, the Xml tab is selected and Add Common Rule for Xml is clicked. The form opens filled with the values of the Any Paths rule.
The Allowed XML Keys restriction is turned on and the paths are added. Element names are written together with the namespace prefix, joined with dots; attributes are located under _attr (How are element and attribute paths defined in XML?). The paths required for the example SOAP body:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body><GetOrder id="7"><No>12</No></GetOrder></soap:Body>
</soap:Envelope>soap:Envelope, soap:Envelope._attr, soap:Envelope._attr.xmlns:soap, soap:Envelope.soap:Body, soap:Envelope.soap:Body.GetOrder, soap:Envelope.soap:Body.GetOrder._attr, soap:Envelope.soap:Body.GetOrder._attr.id, soap:Envelope.soap:Body.GetOrder.No.
XML Depth, XML Key Count and XML Value Length are narrowed according to the expected size of the envelope; Block Invalid XML Contents is left checked.
Add in the form is clicked; there is no separate deployment step.
In the rule of the endpoints that work with JSON, a common rule is opened in the same tab, Block All XML Contents is checked and Add is clicked (Can I forbid JSON or XML bodies entirely on a page (Block All JSON Contents / Block All XML Contents)?). In a cluster, the changes are sent to the peer device with the default auto-sync; if Manual Sync Mode is on, they are sent with Synchronize.
Verification
An element that is not in the list is added to the envelope, the request is sent, and the Attacks tab of the Event Logs record is checked.
Allowed XML keys · path of the added element (24)A body that defines an ENTITY inside DOCTYPE is sent.
Command: curl -s -o /dev/null -w "%{http_code}\n" -H "Content-Type: application/xml" -d '<!DOCTYPE r [<!ENTITY a SYSTEM "file:///etc/passwd">]><r>&a;</r>' https://www.example.com/ws/OrderService
Block invalid XML contents and XXE signature recordsMethod 2: HTTP Request Body Condition + Block
- How it works The HTTP Request Body condition searches for the
<!DOCTYPEtext in raw form in the first Body Size to Parse(Bytes) bytes of the body; the Block action bound to the condition rejects the request before the WAF blocking page. It does not depend on parsing. - Where it is openedLoad Balancer → Conditions · Load Balancer → Actions · vService → Details → Actions
- Requirement Modify permission in the Load Balancer area; the service not using DOCTYPE; WAF is not required
- vService type HTTP
Step-by-Step Configuration
On the Load Balancer → Conditions screen, Add is clicked (how to add).
Condition Name = xml-doctype is entered, Criteria = HTTP Request Body is selected; in the matching list, Includes is selected, <!DOCTYPE is written in the Query field and Case Insensitive is checked. The default of Body Size to Parse(Bytes) is 1024; since DOCTYPE is located at the beginning of the document, this value is sufficient.
Add is clicked.
On the Load Balancer → Actions screen, Add is clicked.
Action Name is entered, Action Type = Block and Execution Level = On Request are selected; the default of Status Code is 403. In the Conditions field, the condition from step 3 is selected. If the condition is to be applied only to XML services, it is combined with a path condition with and (Can I look for and block specific content in the request body (JSON / XML / form)?).
Add is clicked.
On the Load Balancer → Load Balancer vService List screen, the vService is selected and Edit is clicked.
The action is selected in the Actions field under Details.
Save is clicked; there is no separate deployment step. In a cluster, the change is sent to the peer device with the default auto-sync; if Manual Sync Mode is on, it is sent with Synchronize.
Verification
A body that refers to an external DTD but can be parsed is sent.
Command: curl -s -o /dev/null -w "%{http_code}\n" -H "Content-Type: application/xml" -d '<!DOCTYPE r SYSTEM "http://198.51.100.5/x.dtd"><r>x</r>' https://www.example.com/ws/OrderService
403Method 3: WEB-CLI
- How it works The Xml common rule is written with
waf rule-parts add … loc xml, and the DOCTYPE condition and the Block action with thelb condition,lb actionandlb servicecommands. - Where it is opened WEB-CLI →
waf rule-parts·lb condition - Requirement WEB-CLI access and modify permission in the WAF area; the vService, host group and rule IDs (
waf host-groups list,waf fw-rules list) - vService type HTTP
Step-by-Step Configuration
In WEB-CLI, the Block All XML Contents (--xmlBlockAll) and Block Invalid XML Contents values of the XML part come as true by default; for an endpoint that accepts XML, --xmlBlockAll false is written. The other fields that are not written are also saved with the schema defaults: key length 1–64, key count at most 1024, depth at most 32, length of each text value 0–1024:
waf rule-parts add poolId <vService> hostGroupId <group> ruleId <rule> loc xml --xmlBlockAll false --xmlAllowedArgs.0 soap:Envelope --xmlAllowedArgs.1 soap:Envelope.soap:BodyThe other paths in the list are added to the same command with --xmlAllowedArgs.2, --xmlAllowedArgs.3 …. The command gives no output when it succeeds.
The searched text is written as !DOCTYPE; every body that carries <!DOCTYPE also contains this text. When --bodySize is not written, the first 4096 bytes of the body are inspected. The command gives no output when it succeeds:
lb condition add criteria req_body query '!DOCTYPE' --content_placement sub --caseInsensitive true --name xml-doctypeThe --cond field takes the ID of the condition, not its name; the ID is read from the id column of the list:
lb condition listlb action add type deny name xml-doctype-block --cond <condition-id>First, the existing rules.N lines are seen with lb service show <vService>; since --rules.N replaces the action at that position, the first empty position is used (0 if there are no actions):
lb service update id <vService> --rules.0 xml-doctype-blockVerification
The vService record is displayed.
Command: lb service show <vService>
xml-doctype-block in the rules linesCommon Mistakes
Leaving DTD processing on at the backend
The WAF and Load Balancer layers try to catch XXE on the request; however, the effect of the attack depends on the XML parser on the Backend Service resolving external entities. DTD and external entity processing are turned off in the parser (OWASP XXE Prevention).
- Writing SOAP paths without the prefix. Element names match the way they are written in the document: if
Envelopeis written instead ofsoap:Envelope, the path is not found in the list and a violation occurs. Containers and_attrpaths also go into the list. - Not increasing the Max. Allowed Arguments value. When the path list of SOAP envelopes exceeds this limit, the list is disabled for that rule without giving a warning.
- Assuming that XML comes only with application/xml.
text/xml,application/soap+xmland the other…+xmltypes are also parsed as XML; if the Content-Type is not XML, the body is scanned as raw. - Turning off Block Invalid XML Contents. Bodies with a DOCTYPE that has an internal definition section (e.g. one that defines an entity) are marked with this violation; if it is turned off, these bodies are evaluated only with raw body signature scanning.
- Applying the DOCTYPE condition to a service that uses DOCTYPE. On services that need a DTD, the condition also blocks legitimate requests; on these services, the condition is narrowed with a path or is not used.
- Not writing
--xmlBlockAll falsein WEB-CLI. Since the default istrue, all XML bodies that come to the endpoint become violations.
How it is done in the documentation
