Skip to content

Commit 169fc3b

Browse files
authored
Merge pull request #2728 from ricekot/blog/script-scan-rules
blog: Add post on script scan rules
2 parents 710e653 + b2c1f1f commit 169fc3b

File tree

2 files changed

+221
-0
lines changed

2 files changed

+221
-0
lines changed
325 KB
Loading
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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

Comments
 (0)