|
| 1 | +--- |
| 2 | +# This plan is the equivalent of the Packaged API scan https://www.zaproxy.org/docs/docker/api-scan/ |
| 3 | +# The plan that will not do anything until you: |
| 4 | +# Set a "ZAP_TARGET" env var (or change the plan of course). |
| 5 | +# Define at least one graphql, openapi, or soap endpoint, then you can delete the API jobs that don't have one. |
| 6 | +env: |
| 7 | + contexts: |
| 8 | + - name: "Example" |
| 9 | + urls: |
| 10 | + - "${ZAP_TARGET}" |
| 11 | + includePaths: [] |
| 12 | + excludePaths: [] |
| 13 | + parameters: |
| 14 | + failOnError: true |
| 15 | + failOnWarning: false |
| 16 | + progressToStdout: true |
| 17 | + vars: {} |
| 18 | +jobs: |
| 19 | +- parameters: |
| 20 | + scanOnlyInScope: true |
| 21 | + enableTags: false |
| 22 | + rules: [] |
| 23 | + name: "passiveScan-config" |
| 24 | + type: "passiveScan-config" |
| 25 | + |
| 26 | +- type: script |
| 27 | + parameters: |
| 28 | + action: add |
| 29 | + type: httpsender |
| 30 | + engine: "ECMAScript : Graal.js" |
| 31 | + name: AlertOnHttpResponseCodeErrors.js |
| 32 | + inline: | |
| 33 | + var Pattern = Java.type("java.util.regex.Pattern") |
| 34 | + |
| 35 | + pluginid = 100000 // https://github.com/zaproxy/zaproxy/blob/main/docs/scanners.md |
| 36 | + |
| 37 | + function sendingRequest(msg, initiator, helper) { |
| 38 | + // Nothing to do |
| 39 | + } |
| 40 | + |
| 41 | + function responseReceived(msg, initiator, helper) { |
| 42 | + if (isGloballyExcluded(msg)) { |
| 43 | + // Not of interest. |
| 44 | + return |
| 45 | + } |
| 46 | + |
| 47 | + var extensionAlert = control.getExtensionLoader().getExtension(org.zaproxy.zap.extension.alert.ExtensionAlert.NAME) |
| 48 | + if (extensionAlert != null) { |
| 49 | + var code = msg.getResponseHeader().getStatusCode() |
| 50 | + if (code < 400 || code >= 600) { |
| 51 | + // Do nothing |
| 52 | + } else { |
| 53 | + var risk = 0 // Info |
| 54 | + var title = "A Client Error response code was returned by the server" |
| 55 | + if (code >= 500) { |
| 56 | + // Server error |
| 57 | + risk = 1 // Low |
| 58 | + title = "A Server Error response code was returned by the server" |
| 59 | + } |
| 60 | + // CONFIDENCE_HIGH = 3 (we can be pretty sure we're right) |
| 61 | + var alert = new org.parosproxy.paros.core.scanner.Alert(pluginid, risk, 3, title) |
| 62 | + var ref = msg.getHistoryRef() |
| 63 | + if (ref != null && org.parosproxy.paros.model.HistoryReference.getTemporaryTypes().contains( |
| 64 | + java.lang.Integer.valueOf(ref.getHistoryType()))) { |
| 65 | + // Dont use temporary types as they will get deleted |
| 66 | + ref = null |
| 67 | + } |
| 68 | + if (ref == null) { |
| 69 | + // map the initiator |
| 70 | + var type |
| 71 | + switch (initiator) { |
| 72 | + case 1: // PROXY_INITIATOR |
| 73 | + type = 1 // Proxied |
| 74 | + break |
| 75 | + case 2: // ACTIVE_SCANNER_INITIATOR |
| 76 | + type = 3 // Scanner |
| 77 | + break |
| 78 | + case 3: // SPIDER_INITIATOR |
| 79 | + type = 2 // Spider |
| 80 | + break |
| 81 | + case 4: // FUZZER_INITIATOR |
| 82 | + type = 8 // Fuzzer |
| 83 | + break |
| 84 | + case 5: // AUTHENTICATION_INITIATOR |
| 85 | + type = 15 // User |
| 86 | + break |
| 87 | + case 6: // MANUAL_REQUEST_INITIATOR |
| 88 | + type = 15 // User |
| 89 | + break |
| 90 | + case 8: // BEAN_SHELL_INITIATOR |
| 91 | + type = 15 // User |
| 92 | + break |
| 93 | + case 9: // ACCESS_CONTROL_SCANNER_INITIATOR |
| 94 | + type = 13 // Access control |
| 95 | + break |
| 96 | + default: |
| 97 | + type = 15 // User - fallback |
| 98 | + break |
| 99 | + } |
| 100 | + ref = new org.parosproxy.paros.model.HistoryReference(model.getSession(), type, msg) |
| 101 | + } |
| 102 | + alert.setMessage(msg) |
| 103 | + alert.setUri(msg.getRequestHeader().getURI().toString()) |
| 104 | + alert.setDescription("A response code of " + code + " was returned by the server.\n" + |
| 105 | + "This may indicate that the application is failing to handle unexpected input correctly.\n" + |
| 106 | + "Raised by the 'Alert on HTTP Response Code Error' script"); |
| 107 | + // Use a regex to extract the evidence from the response header |
| 108 | + var regex = new RegExp("^HTTP.*" + code) |
| 109 | + alert.setEvidence(msg.getResponseHeader().toString().match(regex)) |
| 110 | + alert.setCweId(388) // CWE CATEGORY: Error Handling |
| 111 | + alert.setWascId(20) // WASC Improper Input Handling |
| 112 | + extensionAlert.alertFound(alert , ref) |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + function isGloballyExcluded(msg) { |
| 118 | + var url = msg.getRequestHeader().getURI().toString() |
| 119 | + var regexes = model.getSession().getGlobalExcludeURLRegexs() |
| 120 | + for (var i in regexes) { |
| 121 | + if (Pattern.compile(regexes[i], Pattern.CASE_INSENSITIVE).matcher(url).matches()) { |
| 122 | + return true |
| 123 | + } |
| 124 | + } |
| 125 | + return false |
| 126 | + } |
| 127 | +
|
| 128 | +- type: script |
| 129 | + parameters: |
| 130 | + action: add |
| 131 | + type: httpsender |
| 132 | + engine: "ECMAScript : Graal.js" |
| 133 | + name: AlertOnUnexpectedContentTypes.js |
| 134 | + inline: | |
| 135 | + var Pattern = Java.type("java.util.regex.Pattern") |
| 136 | + |
| 137 | + var pluginid = 100001 // https://github.com/zaproxy/zaproxy/blob/main/docs/scanners.md |
| 138 | + |
| 139 | + var extensionAlert = control.getExtensionLoader().getExtension(org.zaproxy.zap.extension.alert.ExtensionAlert.NAME) |
| 140 | + |
| 141 | + var expectedTypes = [ |
| 142 | + "application/octet-stream", |
| 143 | + "text/plain" |
| 144 | + ] |
| 145 | + |
| 146 | + var expectedTypeGroups = ["json", "yaml", "xml"] |
| 147 | + |
| 148 | + function sendingRequest(msg, initiator, helper) { |
| 149 | + // Nothing to do |
| 150 | + } |
| 151 | + |
| 152 | + function responseReceived(msg, initiator, helper) { |
| 153 | + if (isGloballyExcluded(msg)) { |
| 154 | + // Not of interest. |
| 155 | + return |
| 156 | + } |
| 157 | + |
| 158 | + if (extensionAlert != null) { |
| 159 | + var ctype = msg.getResponseHeader().getHeader("Content-Type") |
| 160 | + if (ctype != null) { |
| 161 | + if (ctype.indexOf(";") > 0) { |
| 162 | + ctype = ctype.substring(0, ctype.indexOf(";")) |
| 163 | + } |
| 164 | + if (!msg.getResponseHeader().hasContentType(expectedTypeGroups) && expectedTypes.indexOf(ctype) < 0) { |
| 165 | + // Another rule will complain if theres no type |
| 166 | + |
| 167 | + var risk = 1 // Low |
| 168 | + var title = "Unexpected Content-Type was returned" |
| 169 | + // CONFIDENCE_HIGH = 3 (we can be pretty sure we're right) |
| 170 | + var alert = new org.parosproxy.paros.core.scanner.Alert(pluginid, risk, 3, title) |
| 171 | + var ref = msg.getHistoryRef() |
| 172 | + if (ref != null && org.parosproxy.paros.model.HistoryReference.getTemporaryTypes().contains( |
| 173 | + java.lang.Integer.valueOf(ref.getHistoryType()))) { |
| 174 | + // Dont use temporary types as they will get deleted |
| 175 | + ref = null |
| 176 | + } |
| 177 | + if (ref == null) { |
| 178 | + // map the initiator |
| 179 | + var type |
| 180 | + switch (initiator) { |
| 181 | + case 1: // PROXY_INITIATOR |
| 182 | + type = 1 // Proxied |
| 183 | + break |
| 184 | + case 2: // ACTIVE_SCANNER_INITIATOR |
| 185 | + type = 3 // Scanner |
| 186 | + break |
| 187 | + case 3: // SPIDER_INITIATOR |
| 188 | + type = 2 // Spider |
| 189 | + break |
| 190 | + case 4: // FUZZER_INITIATOR |
| 191 | + type = 8 // Fuzzer |
| 192 | + break |
| 193 | + case 5: // AUTHENTICATION_INITIATOR |
| 194 | + type = 15 // User |
| 195 | + break |
| 196 | + case 6: // MANUAL_REQUEST_INITIATOR |
| 197 | + type = 15 // User |
| 198 | + break |
| 199 | + case 8: // BEAN_SHELL_INITIATOR |
| 200 | + type = 15 // User |
| 201 | + break |
| 202 | + case 9: // ACCESS_CONTROL_SCANNER_INITIATOR |
| 203 | + type = 13 // Access control |
| 204 | + break |
| 205 | + default: |
| 206 | + type = 15 // User - fallback |
| 207 | + break |
| 208 | + } |
| 209 | + ref = new org.parosproxy.paros.model.HistoryReference(model.getSession(), type, msg) |
| 210 | + } |
| 211 | + alert.setMessage(msg) |
| 212 | + alert.setUri(msg.getRequestHeader().getURI().toString()) |
| 213 | + alert.setDescription("A Content-Type of " + ctype + " was returned by the server.\n" + |
| 214 | + "This is not one of the types expected to be returned by an API.\n" + |
| 215 | + "Raised by the 'Alert on Unexpected Content Types' script"); |
| 216 | + alert.setEvidence(ctype) |
| 217 | + extensionAlert.alertFound(alert , ref) |
| 218 | + } |
| 219 | + } |
| 220 | + } |
| 221 | + } |
| 222 | + |
| 223 | + function isGloballyExcluded(msg) { |
| 224 | + var url = msg.getRequestHeader().getURI().toString() |
| 225 | + var regexes = model.getSession().getGlobalExcludeURLRegexs() |
| 226 | + for (var i in regexes) { |
| 227 | + if (Pattern.compile(regexes[i], Pattern.CASE_INSENSITIVE).matcher(url).matches()) { |
| 228 | + return true |
| 229 | + } |
| 230 | + } |
| 231 | + return false |
| 232 | + } |
| 233 | +
|
| 234 | +- type: "graphql" |
| 235 | + parameters: |
| 236 | + endpoint: # String: the endpoint URL, default: null, no schema is imported |
| 237 | + schemaUrl: # String: URL pointing to a GraphQL Schema, default: null, import using introspection on endpoint |
| 238 | + schemaFile: # String: Local file path of a GraphQL Schema, default: null, import using schemaUrl |
| 239 | + |
| 240 | +- type: "openapi" |
| 241 | + parameters: |
| 242 | + apiFile: # String: Local file containing the OpenAPI definition, default: null, no definition will be imported |
| 243 | + apiUrl: # String: URL containing the OpenAPI definition, default: null, no definition will be imported |
| 244 | + targetUrl: # String: URL which overrides the target defined in the definition, default: null, the target will not be overridden |
| 245 | + |
| 246 | +- type: "soap" |
| 247 | + parameters: |
| 248 | + wsdlFile: # String: Local file path of the WSDL, default: null, no definition will be imported |
| 249 | + wsdlUrl: # String: URL pointing to the WSDL, default: null, no definition will be imported |
| 250 | + |
| 251 | +- parameters: |
| 252 | + policyDefinition: |
| 253 | + defaultStrength: "medium" |
| 254 | + defaultThreshold: "Off" |
| 255 | + rules: |
| 256 | + - id: 0 |
| 257 | + name: "Directory Browsing" |
| 258 | + threshold: "medium" |
| 259 | + - id: 7 |
| 260 | + name: "Remote File Inclusion" |
| 261 | + threshold: "medium" |
| 262 | + - id: 20019 |
| 263 | + name: "External Redirect" |
| 264 | + threshold: "medium" |
| 265 | + - id: 30001 |
| 266 | + name: "Buffer Overflow" |
| 267 | + threshold: "medium" |
| 268 | + - id: 30002 |
| 269 | + name: "Format String Error" |
| 270 | + threshold: "medium" |
| 271 | + - id: 30003 |
| 272 | + name: "Integer Overflow Error" |
| 273 | + threshold: "medium" |
| 274 | + - id: 40003 |
| 275 | + name: "CRLF Injection" |
| 276 | + threshold: "medium" |
| 277 | + - id: 40008 |
| 278 | + name: "Parameter Tampering" |
| 279 | + threshold: "medium" |
| 280 | + - id: 40009 |
| 281 | + name: "Server Side Include" |
| 282 | + threshold: "medium" |
| 283 | + - id: 40018 |
| 284 | + name: " SQL Injection" |
| 285 | + threshold: "medium" |
| 286 | + - id: 40042 |
| 287 | + name: "Spring Actuator Information Leak" |
| 288 | + threshold: "medium" |
| 289 | + - id: 40044 |
| 290 | + name: "Exponential Entity Expansion (Billion Laughs Attack)" |
| 291 | + threshold: "medium" |
| 292 | + - id: 90017 |
| 293 | + name: "XSLT Injection" |
| 294 | + threshold: "medium" |
| 295 | + - id: 90019 |
| 296 | + name: "Server Side Code Injection" |
| 297 | + threshold: "medium" |
| 298 | + - id: 90020 |
| 299 | + name: "Remote OS Command Injection" |
| 300 | + threshold: "medium" |
| 301 | + - id: 90021 |
| 302 | + name: "XPath Injection" |
| 303 | + threshold: "medium" |
| 304 | + - id: 90023 |
| 305 | + name: "XML External Entity Attack" |
| 306 | + threshold: "medium" |
| 307 | + - id: 90025 |
| 308 | + name: "Expression Language Injection" |
| 309 | + threshold: "medium" |
| 310 | + - id: 90026 |
| 311 | + name: "SOAP Action Spoofing" |
| 312 | + threshold: "medium" |
| 313 | + - id: 90029 |
| 314 | + name: "SOAP XML Injection" |
| 315 | + threshold: "medium" |
| 316 | + - id: 90034 |
| 317 | + name: "Cloud Metadata Potentially Exposed" |
| 318 | + - id: 90035 |
| 319 | + name: "Server Side Template Injection" |
| 320 | + threshold: "medium" |
| 321 | + - id: 90036 |
| 322 | + name: "Server Side Template Injection (Blind)" |
| 323 | + threshold: "medium" |
| 324 | + |
| 325 | + name: "activeScan" |
| 326 | + type: "activeScan" |
| 327 | +- parameters: {} |
| 328 | + name: "passiveScan-wait-pre-report" |
| 329 | + type: "passiveScan-wait" |
| 330 | +- parameters: |
| 331 | + template: "modern" |
| 332 | + reportTitle: "ZAP Scanning Report" |
| 333 | + reportDescription: "" |
| 334 | + name: "report" |
| 335 | + type: "report" |
0 commit comments