|
| 1 | +--- |
| 2 | +title: "ZAP Scripts are now Full Scan Rules!" |
| 3 | +summary: "ZAP scripts can now do everything that scan rules can." |
| 4 | +images: |
| 5 | +- https://www.zaproxy.org/blog/2024-07-17-script-scan-rules/images/cover.jpg |
| 6 | +type: post |
| 7 | +tags: |
| 8 | + - blog |
| 9 | + - scanrules |
| 10 | + - scripts |
| 11 | +date: "2024-07-17" |
| 12 | +authors: |
| 13 | + - akshath |
| 14 | +--- |
| 15 | + |
| 16 | +ZAP active and passive scripts are now first-class scan rules! 🎉 |
| 17 | + |
| 18 | +ZAP scripts may now optionally be exposed as scan rules, allowing you to include them in your scan policies. |
| 19 | + |
| 20 | +Existing scripts are not affected by this change. |
| 21 | + |
| 22 | +## How to Expose a Script as a Scan Rule |
| 23 | + |
| 24 | +Any active or passive script that implements the `getMetadata` function will be exposed as a scan rule to ZAP. |
| 25 | + |
| 26 | +```js |
| 27 | +const ScanRuleMetadata = Java.type("org.zaproxy.addon.commonlib.scanrules.ScanRuleMetadata"); |
| 28 | + |
| 29 | +function getMetadata() { |
| 30 | + return ScanRuleMetadata.fromYaml(` |
| 31 | +id: 12345 |
| 32 | +name: Passive Vulnerability Title |
| 33 | +description: Full description |
| 34 | +solution: The solution |
| 35 | +references: |
| 36 | + - https://example.org/reference-1 |
| 37 | + - https://example.org/reference-2 |
| 38 | +risk: INFO # info, low, medium, high |
| 39 | +confidence: LOW # false_positive, low, medium, high, user_confirmed |
| 40 | +cweId: 0 |
| 41 | +wascId: 0 |
| 42 | +alertTags: |
| 43 | + name1: value1 |
| 44 | + name2: value2 |
| 45 | +otherInfo: Any other info |
| 46 | +status: alpha |
| 47 | +`); |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +Specifying the metadata in this manner also means that you don't have to set all these details when raising an alert in the script. |
| 52 | +The alert raised by each script is automatically populated with the details from the metadata. |
| 53 | +However, you can still override metadata for particular alerts if you want. |
| 54 | +For example, |
| 55 | + |
| 56 | +```js |
| 57 | +function scan(helper, msg, src) { |
| 58 | + // ... |
| 59 | + foundKeys = ["my-secret-key", "my-other-secret-key"] |
| 60 | + helper |
| 61 | + .newAlert() |
| 62 | + .setEvidence(foundKeys[0]) |
| 63 | + .setOtherInfo(`Other instances: ${foundKeys.slice(1).toString()}`) |
| 64 | + .setMessage(msg) |
| 65 | + .raise(); |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +Each script that is exposed as a scan rule must have a unique ID, otherwise it will not be loaded. |
| 70 | +The IDs of the scan rules and scripts available via add-ons from the [ZAP Marketplace](/addons/) are maintained in the [`scanners.md` file](https://github.com/zaproxy/zaproxy/blob/main/docs/scanners.md) in the core ZAP repository. |
| 71 | + |
| 72 | +While the code examples in this blog post are written in JS, ZAP scripts written in any supported programming language (e.g. Python, Groovy, Kotlin, etc.) may be exposed as scan rules. |
| 73 | + |
| 74 | +## Community Scripts |
| 75 | + |
| 76 | +All the passive scripts and most of the active scripts in the [community-scripts](https://github.com/zaproxy/community-scripts) repository (and add-on) have been updated to implement the `getMetadata()` function. |
| 77 | + |
| 78 | +The [alerts page](/docs/alerts/) on this website (which is auto-generated from scan rules) now contains information about the alerts raised by the community scripts too. |
| 79 | + |
| 80 | +It should be noted that all these scripts will now need to use the GraalVM engine (provided by the `graaljs` add-on), as template literals are not supported by Nashorn. |
| 81 | + |
| 82 | +If you have any scripts written for Nashorn, we recommend you switch to using the GraalVM engine. |
| 83 | +You should be able to switch your engine for the majority of your scripts without having to modify them. |
| 84 | + |
| 85 | +If you do not want to use GraalVM, you can still expose your scripts as scan rules by constructing the [ScanRuleMetadata](https://github.com/zaproxy/zap-extensions/blob/839d5c7df7432c1f4748275daacd1d4e8812a51a/addOns/commonlib/src/main/java/org/zaproxy/addon/commonlib/scanrules/ScanRuleMetadata.java) object manually with its setters. |
| 86 | + |
| 87 | +## Rapid Prototyping |
| 88 | + |
| 89 | +Last month, we published a new passive scan rule to detect if a script in your webapp was being loaded from the malicious Polyfill.io domain. |
| 90 | +You can read the full blog post about it [here](/blog/2024-06-27-polyfill.io-script-detection/). |
| 91 | + |
| 92 | +The scan rule was [written in Java](https://github.com/zaproxy/zap-extensions/blob/839d5c7df7432c1f4748275daacd1d4e8812a51a/addOns/pscanrulesBeta/src/main/java/org/zaproxy/zap/extension/pscanrulesBeta/PolyfillCdnScriptScanRule.java) and included in the `pscanrulesBeta` add-on. |
| 93 | + |
| 94 | +Here's the same scan rule written as a script, included here as an example. |
| 95 | + |
| 96 | +```js |
| 97 | +const ScanRuleMetadata = Java.type( |
| 98 | + "org.zaproxy.addon.commonlib.scanrules.ScanRuleMetadata", |
| 99 | +); |
| 100 | +const Alert = Java.type("org.parosproxy.paros.core.scanner.Alert"); |
| 101 | + |
| 102 | +const PLUGIN_ID = 10115; |
| 103 | + |
| 104 | +function getMetadata() { |
| 105 | + return ScanRuleMetadata.fromYaml(` |
| 106 | +id: ${PLUGIN_ID} |
| 107 | +name: Script Served From Malicious Domain (polyfill) |
| 108 | +description: | |
| 109 | + The page includes one or more script files loaded from one of the 'polyfill' domains. |
| 110 | + These are not associated with the polyfill.js library and are known to serve malicious content. |
| 111 | +solution: > |
| 112 | + Change all scripts to use a known good source based on their documentation. |
| 113 | +risk: high |
| 114 | +confidence: high |
| 115 | +cweId: 829 # CWE-829: Inclusion of Functionality from Untrusted Control Sphere |
| 116 | +wascId: 15 # WASC-15: Application Misconfiguration |
| 117 | +alertTags: |
| 118 | + OWASP_2017_A09: "https://owasp.org/www-project-top-ten/2017/A9_2017-Using_Components_with_Known_Vulnerabilities.html" |
| 119 | + OWASP_2021_A06: "https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components/" |
| 120 | +status: beta |
| 121 | +`); |
| 122 | +} |
| 123 | + |
| 124 | +const transitiveScriptRefDescription = `The page includes one or more script which appear to include a reference to one of the 'polyfill' domains. |
| 125 | +These are not associated with the polyfill.js library and are known to serve malicious content. |
| 126 | +You should check to see if it is a safe reference (for example in a comment) or whether the script is loading content from that domain.`; |
| 127 | + |
| 128 | +const POLYFILL_IO = /https?:\/\/.*polyfill\.io\/.*/i; |
| 129 | +const BOOTCSS_COM = /https?:\/\/.*bootcss\.com\/.*/i; |
| 130 | +const BOOTCDN_NET = /https?:\/\/.*bootcdn\.net\/.*/i; |
| 131 | +const STATICFILE_NET = /https?:\/\/.*staticfile\.net\/.*/i; |
| 132 | +const STATICFILE_ORG = /https?:\/\/.*staticfile\.org\/.*/i; |
| 133 | +const UNIONADJS_COM = /https?:\/\/.*unionadjs\.com\/.*/i; |
| 134 | +const XHSBPZA_COM = /https?:\/\/.*xhsbpza\.com\/.*/i; |
| 135 | +const UNION_MACOMS_LA = /https?:\/\/.*union\.macoms\.la\/.*/i; |
| 136 | +const NEWCRBPC_COM = /https?:\/\/.*newcrbpc\.com\/.*/i; |
| 137 | + |
| 138 | +const ALL_DOMAINS = [ |
| 139 | + POLYFILL_IO, |
| 140 | + BOOTCSS_COM, |
| 141 | + BOOTCDN_NET, |
| 142 | + STATICFILE_NET, |
| 143 | + STATICFILE_ORG, |
| 144 | + UNIONADJS_COM, |
| 145 | + XHSBPZA_COM, |
| 146 | + UNION_MACOMS_LA, |
| 147 | + NEWCRBPC_COM, |
| 148 | +]; |
| 149 | + |
| 150 | +function scan(helper, msg, src) { |
| 151 | + if ( |
| 152 | + msg.getResponseBody().length() === 0 || |
| 153 | + !msg.getResponseHeader().isHtml() |
| 154 | + ) { |
| 155 | + return; |
| 156 | + } |
| 157 | + const sourceElements = src.getAllElements("script"); |
| 158 | + if (sourceElements == null) { |
| 159 | + return; |
| 160 | + } |
| 161 | + let alertRaised = false; |
| 162 | + for (let i = 0; i < sourceElements.length; i++) { |
| 163 | + const sourceElement = sourceElements[i]; |
| 164 | + const scriptSrc = sourceElement.getAttributeValue("src"); |
| 165 | + if (scriptSrc == null) { |
| 166 | + continue; |
| 167 | + } |
| 168 | + for (let j = 0; j < ALL_DOMAINS.length; j++) { |
| 169 | + if (ALL_DOMAINS[j].test(scriptSrc)) { |
| 170 | + helper |
| 171 | + .newAlert() |
| 172 | + .setParam(scriptSrc) |
| 173 | + .setEvidence(sourceElement.toString()) |
| 174 | + .setAlertRef(`${PLUGIN_ID}-1`) |
| 175 | + .raise(); |
| 176 | + alertRaised = true; |
| 177 | + } |
| 178 | + } |
| 179 | + } |
| 180 | + if (alertRaised) { |
| 181 | + // Definitely an issue, no point checking the script contents |
| 182 | + return; |
| 183 | + } |
| 184 | + // Check the script contents, in case they are loading scripts via JS |
| 185 | + for (let i = 0; i < sourceElements.length; i++) { |
| 186 | + const contents = sourceElements[i].getContent().toString(); |
| 187 | + for (let j = 0; j < ALL_DOMAINS.length; j++) { |
| 188 | + let match; |
| 189 | + if ((match = ALL_DOMAINS[j].exec(contents))) { |
| 190 | + helper |
| 191 | + .newAlert() |
| 192 | + .setConfidence(Alert.CONFIDENCE_LOW) |
| 193 | + .setEvidence(match[0]) |
| 194 | + .setDescription(transitiveScriptRefDescription) |
| 195 | + .setAlertRef(`${PLUGIN_ID}-2`) |
| 196 | + .raise(); |
| 197 | + break; |
| 198 | + } |
| 199 | + } |
| 200 | + } |
| 201 | +} |
| 202 | +``` |
| 203 | + |
| 204 | +## How will you use ZAP scripts? |
| 205 | + |
| 206 | +You can now write a full scan rule *while ZAP is running*, in fewer lines of code. |
| 207 | +You can create prototypes of your custom rules and iterate on them rapidly to beat them into shape. |
| 208 | + |
| 209 | +Moreover, the [automation framework](/docs/automate/automation-framework/) supports inline scripts, which means that you can now write and share automation framework plans that scan for specific issues. |
| 210 | + |
| 211 | +You can find an example of such a plan [in this GitHub Gist](https://gist.github.com/ricekot/f45eb4640485909d16bd7dc1d2ec9d5f#file-example-zap-af-plan-yml) and you can run it with the following command: |
| 212 | + |
| 213 | +```shell |
| 214 | +docker run --rm zaproxy/zap-nightly zap.sh -cmd -autorun https://gist.githubusercontent.com/ricekot/f45eb4640485909d16bd7dc1d2ec9d5f/raw/ |
| 215 | +``` |
| 216 | + |
| 217 | +Watch out for a full blog post on this topic soon. |
| 218 | + |
| 219 | +Until then, enjoy hacking and scripting with ZAP! |
| 220 | + |
| 221 | +If you have any feedback, comments, or ideas about ZAP Scripts, please reach out on the [ZAP Scripts Group](https://groups.google.com/group/zaproxy-scripts). |
0 commit comments