|
| 1 | +--- |
| 2 | +title: "XML and JSON content" |
| 3 | +weight: 2000 |
| 4 | +toc: true |
| 5 | +nd-content-type: reference |
| 6 | +nd-product: NAP-WAF |
| 7 | +--- |
| 8 | + |
| 9 | +This section explains how to configure XML and JSON content profiles in an F5 WAF for NGINX policy. The examples below use JSON for illustration, but the same concepts apply to XML profiles unless noted otherwise. |
| 10 | + |
| 11 | +### XML and JSON content profiles |
| 12 | + |
| 13 | +By default, any request that includes a `Content-Type` header specifying XML or JSON is expected to carry a corresponding XML or JSON body. The system automatically performs validation to ensure the body is well-formed and applies restrictions on its size and content. These restrictions are defined in **XML and JSON profiles**. |
| 14 | + |
| 15 | +Profiles can be linked to URLs and, optionally, to Parameters when those parameters are known to contain XML or JSON data. JSON profiles in particular offer powerful validation options, such as enforcing compliance with a defined schema; this capability will be covered in the next section. |
| 16 | + |
| 17 | +The **Base template** includes built-in XML and JSON profiles, both named `default`. These profiles are automatically applied to all (`*`) URLs based on the `Content-Type` header. You can reuse these profiles for other URLs or Parameters in your policies, or create **custom profiles** to apply more specific validation rules tailored to your content. |
| 18 | + |
| 19 | +For example, let’s assume you have a JSON registration form under the URL `/register`. It is a small form, and it makes sense to limit its size to 1000 characters and its nesting depth to 2. Here is a policy that enforces this: |
| 20 | + |
| 21 | +```json |
| 22 | +{ |
| 23 | + "policy": { |
| 24 | + "name": "json_form_policy", |
| 25 | + "template": { |
| 26 | + "name": "POLICY_TEMPLATE_NGINX_BASE" |
| 27 | + }, |
| 28 | + "json-profiles": [ |
| 29 | + { |
| 30 | + "name": "reg_form_prof", |
| 31 | + "defenseAttributes": { |
| 32 | + "maximumArrayLength": "any", |
| 33 | + "maximumStructureDepth": 2, |
| 34 | + "maximumTotalLengthOfJSONData": 1000, |
| 35 | + "maximumValueLength": "any", |
| 36 | + "tolerateJSONParsingWarnings": false |
| 37 | + } |
| 38 | + } |
| 39 | + ], |
| 40 | + "urls": [ |
| 41 | + { |
| 42 | + "name": "/register", |
| 43 | + "method": "POST", |
| 44 | + "type": "explicit", |
| 45 | + "attackSignaturesCheck": true, |
| 46 | + "clickjackingProtection": false, |
| 47 | + "disallowFileUploadOfExecutables": false, |
| 48 | + "isAllowed": true, |
| 49 | + "mandatoryBody": false, |
| 50 | + "methodsOverrideOnUrlCheck": false, |
| 51 | + "urlContentProfiles": [ |
| 52 | + { |
| 53 | + "headerName": "*", |
| 54 | + "headerValue": "*", |
| 55 | + "headerOrder": "default", |
| 56 | + "type": "json", |
| 57 | + "contentProfile": { |
| 58 | + "name": "reg_form_prof" |
| 59 | + } |
| 60 | + } |
| 61 | + ] |
| 62 | + } |
| 63 | + ] |
| 64 | + } |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +In this example, the JSON enforcement is defined in the `reg_form_prof` profile, which is attached to the `/register` URL. JSON content is always expected for this URL—it applies to all header name and value combinations, and no other content type is permitted. The URL is restricted to the `POST` method only. |
| 69 | + |
| 70 | +If a `POST` request to `/register` includes a body that is not well-formed JSON, it triggers the **VIOL_JSON_MALFORMED** violation. |
| 71 | + |
| 72 | +If the body is valid JSON but violates profile restrictions (for example, nesting depth of 3), it triggers the **VIOL_JSON_FORMAT** violation with details about the specific issue. |
| 73 | + |
| 74 | +Now, let’s assume that your JSON registration form contains a specific field that must be Base64-encoded. In that case, you can set the `handleJsonValuesAsParameters` option to `true` at the profile level and configure the parameter with `decodeValueAsBase64` set to `required`. This enforces that the parameter value must always be valid Base64. |
| 75 | + |
| 76 | +```json |
| 77 | +{ |
| 78 | + "policy": { |
| 79 | + "name": "json_parse_param_policy", |
| 80 | + "template": { |
| 81 | + "name": "POLICY_TEMPLATE_NGINX_BASE" |
| 82 | + }, |
| 83 | + "applicationLanguage": "utf-8", |
| 84 | + "caseInsensitive": false, |
| 85 | + "enforcementMode": "blocking", |
| 86 | + "blocking-settings": { |
| 87 | + "violations": [ |
| 88 | + { |
| 89 | + "name": "VIOL_PARAMETER_VALUE_BASE64", |
| 90 | + "alarm": true, |
| 91 | + "block": true |
| 92 | + } |
| 93 | + ] |
| 94 | + }, |
| 95 | + "parameters": [ |
| 96 | + { |
| 97 | + "name": "*", |
| 98 | + "type": "wildcard", |
| 99 | + "parameterLocation": "any", |
| 100 | + "valueType": "user-input", |
| 101 | + "dataType": "alpha-numeric", |
| 102 | + "decodeValueAsBase64": "required" |
| 103 | + } |
| 104 | + ], |
| 105 | + "json-profiles": [ |
| 106 | + { |
| 107 | + "name": "Default", |
| 108 | + "defenseAttributes": { |
| 109 | + "tolerateJSONParsingWarnings": true |
| 110 | + }, |
| 111 | + "handleJsonValuesAsParameters": true, |
| 112 | + "validationFiles": [] |
| 113 | + } |
| 114 | + ] |
| 115 | + } |
| 116 | +} |
| 117 | +``` |
| 118 | + |
| 119 | +{{< call-out "note" >}} |
| 120 | +Defining a JSON or XML profile in a policy has no effect until you assign it to a URL or Parameter you defined in that policy. Profiles can be shared by more than one URL and/or Parameter. |
| 121 | +{{< /call-out >}} |
| 122 | + |
| 123 | +### Applying a JSON schema |
| 124 | + |
| 125 | +If a schema for the JSON payload exists, you can attach it to the JSON profile. F5 WAF for NGINX will then enforce the schema rules in addition to the other restrictions defined in the profile. |
| 126 | + |
| 127 | +Here is an example JSON schema for a registration form, which includes basic personal details: |
| 128 | + |
| 129 | +```json |
| 130 | +{ |
| 131 | + "$schema": "http://json-schema.org/draft-07/schema#", |
| 132 | + "title": "Person", |
| 133 | + "type": "object", |
| 134 | + "additionalProperties": false, |
| 135 | + "properties": { |
| 136 | + "firstName": { |
| 137 | + "type": "string", |
| 138 | + "description": "The person's first name." |
| 139 | + }, |
| 140 | + "lastName": { |
| 141 | + "type": "string", |
| 142 | + "description": "The person's last name." |
| 143 | + }, |
| 144 | + "age": { |
| 145 | + "description": "Age in years which must be equal to or greater than zero.", |
| 146 | + "type": "integer", |
| 147 | + "minimum": 0 |
| 148 | + } |
| 149 | + } |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +Embedding the schema into the `reg_form_prof` JSON profile should be done as follows: |
| 154 | + |
| 155 | +- Add an object containing the JSON schema to the `json-validation-files` array. This array lists all JSON schema validation files available in the profile. A unique `fileName` must be specified, and the escaped contents of the JSON schema provided using the `contents` keyword. |
| 156 | +- Associate the JSON schema with the `reg_form_prof` profile by adding a `validationFiles` array object, setting the `fileName` in the `jsonValidationFile` object to match the schema fileName. |
| 157 | +- All JSON schema files, including external references, must be added to the `json-validation-files` array and linked to the JSON profile. The `isPrimary` flag should be set on the object containing the primary JSON schema. |
| 158 | + |
| 159 | +This produces the following policy: |
| 160 | + |
| 161 | +```json |
| 162 | +{ |
| 163 | + "policy": { |
| 164 | + "name": "json_form_policy_inline_schema", |
| 165 | + "template": { |
| 166 | + "name": "POLICY_TEMPLATE_NGINX_BASE" |
| 167 | + }, |
| 168 | + "json-validation-files": [ |
| 169 | + { |
| 170 | + "fileName": "person_schema.json", |
| 171 | + "contents": "{\r\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n \"title\": \"Person\",\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"firstName\": {\r\n \"type\": \"string\",\r\n \"description\": \"The person's first name.\"\r\n },\r\n \"lastName\": {\r\n \"type\": \"string\",\r\n \"description\": \"The person's last name.\"\r\n },\r\n \"age\": {\r\n \"description\": \"Age in years which must be equal to or greater than zero.\",\r\n \"type\": \"integer\",\r\n \"minimum\": 0\r\n }\r\n }\r\n}" |
| 172 | + } |
| 173 | + ], |
| 174 | + "json-profiles": [ |
| 175 | + { |
| 176 | + "name": "reg_form_prof", |
| 177 | + "defenseAttributes": { |
| 178 | + "maximumArrayLength": "any", |
| 179 | + "maximumStructureDepth": "any", |
| 180 | + "maximumTotalLengthOfJSONData": 1000, |
| 181 | + "maximumValueLength": "any", |
| 182 | + "tolerateJSONParsingWarnings": false |
| 183 | + }, |
| 184 | + "validationFiles": [ |
| 185 | + { |
| 186 | + "isPrimary": true, |
| 187 | + "jsonValidationFile": { |
| 188 | + "fileName": "person_schema.json" |
| 189 | + } |
| 190 | + } |
| 191 | + ] |
| 192 | + } |
| 193 | + ], |
| 194 | + "urls": [ |
| 195 | + { |
| 196 | + "name": "/register", |
| 197 | + "type": "explicit", |
| 198 | + "method": "POST", |
| 199 | + "attackSignaturesCheck": true, |
| 200 | + "clickjackingProtection": false, |
| 201 | + "disallowFileUploadOfExecutables": false, |
| 202 | + "isAllowed": true, |
| 203 | + "mandatoryBody": false, |
| 204 | + "methodsOverrideOnUrlCheck": false, |
| 205 | + "urlContentProfiles": [ |
| 206 | + { |
| 207 | + "contentProfile": { |
| 208 | + "name": "reg_form_prof" |
| 209 | + }, |
| 210 | + "headerName": "*", |
| 211 | + "headerOrder": "default", |
| 212 | + "headerValue": "*", |
| 213 | + "type": "json" |
| 214 | + } |
| 215 | + ] |
| 216 | + } |
| 217 | + ] |
| 218 | + } |
| 219 | +} |
| 220 | +``` |
| 221 | + |
| 222 | +When a request to the `/register` URL is sent with JSON content that does not comply with the schema, the **VIOL_JSON_SCHEMA** violation is triggered. In the default base template, the `alarm` flag is enabled for this violation, meaning it contributes to the Violation Rating when triggered. You can also enable the `block` flag so that the request is blocked whenever this violation occurs. |
| 223 | + |
| 224 | +{{< call-out "note" >}} |
| 225 | +- The schema file is embedded as a quoted string, so all quotes inside the schema itself must be escaped. |
| 226 | +- The nesting depth check was removed from the JSON profile because it is already enforced by the schema. Keeping both checks is not technically incorrect, but in practice the schema usually provides more precise restrictions. Leaving the profile restriction may be redundant at best or cause false positives at worst. |
| 227 | +{{< /call-out >}} |
| 228 | + |
| 229 | +### Including an external JSON schema file |
| 230 | + |
| 231 | +Schema files are often developed as part of the application, independently from the F5 WAF for NGINX policy. It is usually preferable to keep them in separate files and reference them from the policy using a URL. As with all externally referenced policy sections, the JSON schema file can either reside in the NGINX file system (by default, `/etc/app_protect/conf` is assumed if only the filename is specified in the `file:` URL, for example `file:///my_schema.json` refers to `/etc/app_protect/conf/my_schema.json`), or on a remote web server such as your source control system. |
| 232 | + |
| 233 | +In this example, the file is located in the default directory: |
| 234 | + |
| 235 | +```json |
| 236 | +{ |
| 237 | + "policy": { |
| 238 | + "name": "json_form_policy_external_schema", |
| 239 | + "template": { |
| 240 | + "name": "POLICY_TEMPLATE_NGINX_BASE" |
| 241 | + }, |
| 242 | + "json-validation-files": [ |
| 243 | + { |
| 244 | + "fileName": "person_schema.json", |
| 245 | + "link": "file://person_schema.json" |
| 246 | + } |
| 247 | + ], |
| 248 | + "json-profiles": [ |
| 249 | + { |
| 250 | + "name": "reg_form_prof", |
| 251 | + "defenseAttributes": { |
| 252 | + "maximumArrayLength": "any", |
| 253 | + "maximumStructureDepth": "any", |
| 254 | + "maximumTotalLengthOfJSONData": 1000, |
| 255 | + "maximumValueLength": "any", |
| 256 | + "tolerateJSONParsingWarnings": false |
| 257 | + }, |
| 258 | + "validationFiles": [ |
| 259 | + { |
| 260 | + "isPrimary": true, |
| 261 | + "jsonValidationFile": { |
| 262 | + "fileName": "person_schema.json" |
| 263 | + } |
| 264 | + } |
| 265 | + ] |
| 266 | + } |
| 267 | + ], |
| 268 | + "urls": [ |
| 269 | + { |
| 270 | + "name": "/register", |
| 271 | + "type": "explicit", |
| 272 | + "method": "POST", |
| 273 | + "attackSignaturesCheck": true, |
| 274 | + "clickjackingProtection": false, |
| 275 | + "disallowFileUploadOfExecutables": false, |
| 276 | + "isAllowed": true, |
| 277 | + "mandatoryBody": false, |
| 278 | + "methodsOverrideOnUrlCheck": false, |
| 279 | + "urlContentProfiles": [ |
| 280 | + { |
| 281 | + "contentProfile": { |
| 282 | + "name": "reg_form_prof" |
| 283 | + }, |
| 284 | + "headerName": "*", |
| 285 | + "headerOrder": "default", |
| 286 | + "headerValue": "*", |
| 287 | + "type": "json" |
| 288 | + } |
| 289 | + ] |
| 290 | + } |
| 291 | + ] |
| 292 | + } |
| 293 | +} |
| 294 | +``` |
| 295 | + |
| 296 | +The schema file is identified by the `filename` property. It is good practice to keep the filename identical to the one in the URL path, but it is not an error if they differ. |
| 297 | + |
| 298 | +If you want to reference the file externally, replace the content of the `link` property with an HTTP or HTTPS URL: |
| 299 | + |
| 300 | +```json |
| 301 | +{ |
| 302 | + "json-validation-files": [ |
| 303 | + { |
| 304 | + "fileName": "person_schema.json", |
| 305 | + "link": "https://git.mydomain.com/my_app/person_schema.json" |
| 306 | + } |
| 307 | + ] |
| 308 | +} |
| 309 | +``` |
0 commit comments