Skip to content

Commit

Permalink
Pscanbeta - add polyfill.io scan rule
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Bennetts <psiinon@gmail.com>
  • Loading branch information
psiinon committed Jun 27, 2024
1 parent 77fdd6d commit d2c5e16
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 0 deletions.
3 changes: 3 additions & 0 deletions addOns/pscanrulesBeta/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this add-on will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased
### Added
- Polyfill.io script detection

### Changed
- Update minimum ZAP version to 2.15.0.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2024 The ZAP Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.zap.extension.pscanrulesBeta;

import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import net.htmlparser.jericho.Element;
import net.htmlparser.jericho.HTMLElementName;
import net.htmlparser.jericho.Source;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.addon.commonlib.CommonAlertTag;
import org.zaproxy.zap.extension.pscan.PluginPassiveScanner;

public class PolyfillCdnScriptScanRule extends PluginPassiveScanner
implements CommonPassiveScanRuleInfo {

/** Prefix for internationalised messages used by this rule */
private static final String MESSAGE_PREFIX = "pscanbeta.polyfillcdnscript.";

private static final Map<String, String> ALERT_TAGS =
CommonAlertTag.toMap(
CommonAlertTag.OWASP_2021_A06_VULN_COMP,
CommonAlertTag.OWASP_2017_A09_VULN_COMP);

private static final int PLUGIN_ID = 10115;

private static Pattern POLYFILL_IO =
Pattern.compile("http[s]://.*polyfill\\.io/.*", Pattern.CASE_INSENSITIVE);

@Override
public void scanHttpResponseReceive(HttpMessage msg, int id, Source source) {
if (msg.getResponseBody().length() > 0 && msg.getResponseHeader().isHtml()) {
List<Element> sourceElements = source.getAllElements(HTMLElementName.SCRIPT);
if (sourceElements != null) {
for (Element sourceElement : sourceElements) {
String src = sourceElement.getAttributeValue("src");
if (src != null && POLYFILL_IO.matcher(src).matches()) {
this.raiseAlert(msg, id, src, sourceElement.toString());
}
}
}
}
}

private AlertBuilder createAlert(String crossDomainScript, String evidence) {
return newAlert()
.setRisk(getRisk())
.setConfidence(Alert.CONFIDENCE_HIGH)
.setDescription(getDescription())
.setParam(crossDomainScript)
.setSolution(getSolution())
.setReference(getReference())
.setEvidence(evidence)
.setCweId(getCweId())
.setWascId(getWascId());
}

private void raiseAlert(HttpMessage msg, int id, String crossDomainScript, String evidence) {
createAlert(crossDomainScript, evidence).raise();
}

@Override
public List<Alert> getExampleAlerts() {
return List.of(
createAlert(
"https://cdn.polyfill.io/malicious.js",
"<script type=\"text/javascript\" src=\"https://cdn.polyfill.io/malicious.js\"></script>")
.build());
}

@Override
public int getPluginId() {
return PLUGIN_ID;
}

public int getRisk() {
return Alert.RISK_HIGH;
}

@Override
public String getName() {
return Constant.messages.getString(MESSAGE_PREFIX + "name");
}

public String getDescription() {
return Constant.messages.getString(MESSAGE_PREFIX + "desc");
}

public String getSolution() {
return Constant.messages.getString(MESSAGE_PREFIX + "soln");
}

public String getReference() {
return Constant.messages.getString(MESSAGE_PREFIX + "refs");
}

@Override
public Map<String, String> getAlertTags() {
return ALERT_TAGS;
}

public int getCweId() {
return 829; // CWE Id 829 - Inclusion of Functionality from Untrusted Control Sphere
}

public int getWascId() {
return 15; // WASC-15: Application Misconfiguration)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ <H2 id="id-10063">Permissions Policy Header Not Set</H2>
Latest code: <a href="https://github.com/zaproxy/zap-extensions/blob/main/addOns/pscanrulesBeta/src/main/java/org/zaproxy/zap/extension/pscanrulesBeta/PermissionsPolicyScanRule.java">PermissionsPolicyScanRule.java</a><br>
Alert ID: <a href="https://www.zaproxy.org/docs/alerts/10063/">10063</a>

<H2 id="id-10115">Script served from malicious polyfill.io domain</H2>
This checks for scripts being served from the polyfill.io domain, which is known to have been compromised.
<p>
Latest code: <a href="https://github.com/zaproxy/zap-extensions/blob/main/addOns/pscanrules/src/main/java/org/zaproxy/zap/extension/pscanrules/PolyfillCdnScriptScanRule.java">PolyfillCdnScriptScanRule.java</a>
<br>
Alert ID: <a href="https://www.zaproxy.org/docs/alerts/10115/">10115</a>.

<H2 id="id-90004">Site Isolation Scan Rule</H2>
Spectre is a side-channel attack allowing an attacker to read data
from memory. One of the counter-measures is to prevent sensitive data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ pscanbeta.permissionspolicymissing.name = Permissions Policy Header Not Set
pscanbeta.permissionspolicymissing.refs = https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy\nhttps://developer.chrome.com/blog/feature-policy/\nhttps://scotthelme.co.uk/a-new-security-header-feature-policy/\nhttps://w3c.github.io/webappsec-feature-policy/\nhttps://www.smashingmagazine.com/2018/12/feature-policy/
pscanbeta.permissionspolicymissing.soln = Ensure that your web server, application server, load balancer, etc. is configured to set the Permissions-Policy header.

pscanbeta.polyfillcdnscript.desc = The page includes one or more script files from the polyfill.io domain.\nThis is not associated with the polyfill.js library and is known to serve malicious content.
pscanbeta.polyfillcdnscript.name = Script served from malicious polyfill.io domain
pscanbeta.polyfillcdnscript.refs = https://sansec.io/research/polyfill-supply-chain-attack\nhttps://x.com/triblondon/status/1761852117579427975
pscanbeta.polyfillcdnscript.soln = Change all scripts to use a known good source based on their documentation.

pscanbeta.servletparameterpollution.desc = Unspecified form action: HTTP parameter override attack potentially possible. This is a known problem with Java Servlets but other platforms may also be vulnerable.
pscanbeta.servletparameterpollution.name = HTTP Parameter Override
pscanbeta.servletparameterpollution.refs = https://download.oracle.com/javaee-archive/servlet-spec.java.net/jsr340-experts/att-0317/OnParameterPollutionAttacks.pdf
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2024 The ZAP Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.zap.extension.pscanrulesBeta;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

import java.util.Map;
import org.junit.jupiter.api.Test;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.addon.commonlib.CommonAlertTag;
import org.zaproxy.zap.utils.ZapXmlConfiguration;

/** Unit test for {@link PolyfillCdnScriptScanRule}. */
class PolyfillCdnScriptScanRuleUnitTest extends PassiveScannerTest<PolyfillCdnScriptScanRule> {

@Override
protected PolyfillCdnScriptScanRule createScanner() {
PolyfillCdnScriptScanRule rule = new PolyfillCdnScriptScanRule();
rule.setConfig(new ZapXmlConfiguration());
return rule;
}

// @Test
void shouldReturnExpectedMappings() {
// Given / When
int cwe = rule.getCweId();
int wasc = rule.getWascId();
Map<String, String> tags = rule.getAlertTags();
// Then
assertThat(cwe, is(equalTo(829)));
assertThat(wasc, is(equalTo(15)));
assertThat(tags.size(), is(equalTo(1)));
assertThat(
tags.containsKey(CommonAlertTag.OWASP_2021_A08_INTEGRITY_FAIL.getTag()),
is(equalTo(true)));
assertThat(
tags.get(CommonAlertTag.OWASP_2021_A08_INTEGRITY_FAIL.getTag()),
is(equalTo(CommonAlertTag.OWASP_2021_A08_INTEGRITY_FAIL.getValue())));
}

@Test
void noScripts() throws HttpMalformedHeaderException {

HttpMessage msg = new HttpMessage();
msg.setRequestHeader("GET http://www.example.com/test/ HTTP/1.1");

msg.setResponseBody("<html></html>");
msg.setResponseHeader(
"HTTP/1.1 200 OK\r\n"
+ "Server: Apache-Coyote/1.1\r\n"
+ "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ "Content-Length: "
+ msg.getResponseBody().length()
+ "\r\n");
scanHttpResponseReceive(msg);

assertThat(alertsRaised.size(), equalTo(0));
}

@Test
void noPollyfillScripts() throws HttpMalformedHeaderException {

HttpMessage msg = new HttpMessage();
msg.setRequestHeader("GET https://www.example.com/test/ HTTP/1.1");

msg.setResponseBody(
"<html>"
+ "<head>"
+ "<script src=\"https://www.example.com/script1\"/>"
+ "<script src=\"https://www.example.com/script2\"/>"
+ "</head>"
+ "</html>");
msg.setResponseHeader(
"HTTP/1.1 200 OK\r\n"
+ "Server: Apache-Coyote/1.1\r\n"
+ "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ "Content-Length: "
+ msg.getResponseBody().length()
+ "\r\n");
scanHttpResponseReceive(msg);

assertThat(alertsRaised.size(), equalTo(0));
}

@Test
void polyfillScriptInHeader() throws HttpMalformedHeaderException {

HttpMessage msg = new HttpMessage();
msg.setRequestHeader("GET https://www.example.com/test/ HTTP/1.1");
msg.setResponseBody(
"<html>"
+ "<head>"
+ "<script src=\"https://www.example.com/script1\"/>"
+ "<script src=\"https://www.polyfill.io/script2\"/>"
+ "</head>"
+ "</html>");
msg.setResponseHeader(
"HTTP/1.1 200 OK\r\n"
+ "Server: Apache-Coyote/1.1\r\n"
+ "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ "Content-Length: "
+ msg.getResponseBody().length()
+ "\r\n");
scanHttpResponseReceive(msg);

assertThat(alertsRaised.size(), equalTo(1));
assertThat(alertsRaised.get(0).getParam(), equalTo("https://www.polyfill.io/script2"));
assertThat(
alertsRaised.get(0).getEvidence(),
equalTo("<script src=\"https://www.polyfill.io/script2\"/>"));
}

@Test
void polyfillScriptInHeaderMixedCase() throws HttpMalformedHeaderException {

HttpMessage msg = new HttpMessage();
msg.setRequestHeader("GET https://www.example.com/test/ HTTP/1.1");
msg.setResponseBody(
"<html>"
+ "<head>"
+ "<script src=\"https://www.example.com/script1\"/>"
+ "<script src=\"https://CdN.PolYfIll.io/script2\"/>"
+ "</head>"
+ "</html>");
msg.setResponseHeader(
"HTTP/1.1 200 OK\r\n"
+ "Server: Apache-Coyote/1.1\r\n"
+ "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ "Content-Length: "
+ msg.getResponseBody().length()
+ "\r\n");
scanHttpResponseReceive(msg);

assertThat(alertsRaised.size(), equalTo(1));
assertThat(alertsRaised.get(0).getParam(), equalTo("https://CdN.PolYfIll.io/script2"));
assertThat(
alertsRaised.get(0).getEvidence(),
equalTo("<script src=\"https://CdN.PolYfIll.io/script2\"/>"));
}

@Test
void polyfillScriptInBody() throws HttpMalformedHeaderException {

HttpMessage msg = new HttpMessage();
msg.setRequestHeader("GET https://www.example.com/test/ HTTP/1.1");
msg.setResponseBody(
"<html>"
+ "<head>"
+ "<script src=\"https://www.example.com/script1\"/>"
+ "</head>"
+ "<body>"
+ "<script src=\"https://www.polyfill.io/script2\"/>"
+ "</body>"
+ "</html>");
msg.setResponseHeader(
"HTTP/1.1 200 OK\r\n"
+ "Server: Apache-Coyote/1.1\r\n"
+ "Content-Type: text/html;charset=ISO-8859-1\r\n"
+ "Content-Length: "
+ msg.getResponseBody().length()
+ "\r\n");
scanHttpResponseReceive(msg);

assertThat(alertsRaised.size(), equalTo(1));
assertThat(alertsRaised.get(0).getParam(), equalTo("https://www.polyfill.io/script2"));
assertThat(
alertsRaised.get(0).getEvidence(),
equalTo("<script src=\"https://www.polyfill.io/script2\"/>"));
}
}

0 comments on commit d2c5e16

Please sign in to comment.