Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion addOns/ascanrulesBeta/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ 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

### Changed
- Clarified details used in alerts raised by the Proxy Disclosure rule (Issue 8556).

## [66] - 2026-05-06
### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ public void scan() {
// point they fit into the topology
// that we can otherwise document using OPTIONS/TRACE + Max-Forwards.
Set<String> silentProxySet = new HashSet<>();
String detectedProxyHeaderName = null;
String proxyRequestHeaderEvidence = null;
boolean endToEndTraceEnabled = false;
boolean proxyTraceEnabled = false;

Expand Down Expand Up @@ -346,12 +348,13 @@ public void scan() {
Matcher proxyHeaderMatcher =
proxyHeaderPattern.matcher(traceResponseBody);
if (proxyHeaderMatcher.find()) {
String proxyHeaderName = proxyHeaderMatcher.group(1);
detectedProxyHeaderName = proxyHeaderMatcher.group(1);
proxyRequestHeaderEvidence = proxyHeaderMatcher.group(0).trim();
proxyActuallyFound = true;
LOGGER.debug(
"TRACE with \"Max-Forwards: {}\" indicates that there is *NO* proxy in place, but a known proxy request header ({}, which indicates proxy server '{}') in the response body contradicts this..",
MAX_FORWARDS_MAXIMUM,
proxyHeaderName,
detectedProxyHeaderName,
proxyServer);
}
}
Expand Down Expand Up @@ -671,7 +674,12 @@ public void scan() {
// bingo with the list of nodes (proxies+origin web server) that we detected.
boolean traceEnabled = endToEndTraceEnabled || proxyTraceEnabled;
buildProxyDisclosureAlert(
step2numberOfNodes, nodeServers, silentProxySet, traceEnabled)
step2numberOfNodes,
nodeServers,
silentProxySet,
traceEnabled,
detectedProxyHeaderName,
proxyRequestHeaderEvidence)
.setMessage(getBaseMsg())
.raise();
}
Expand All @@ -687,7 +695,9 @@ private AlertBuilder buildProxyDisclosureAlert(
int step2numberOfNodes,
String[] nodeServers,
Set<String> silentProxySet,
boolean traceEnabled) {
boolean traceEnabled,
String detectedProxyHeaderName,
String evidence) {
int proxyCountForDescription = step2numberOfNodes - 1 + silentProxySet.size();
String unknown = Constant.messages.getString(MESSAGE_PREFIX + "extrainfo.unknown");
StringBuilder otherInfo = new StringBuilder();
Expand All @@ -698,27 +708,24 @@ private AlertBuilder buildProxyDisclosureAlert(
MESSAGE_PREFIX + "extrainfo.proxyserver.header"))
.append('\n');
for (int nodei = 0; nodei < step2numberOfNodes - 1; nodei++) {
otherInfo
.append(
Constant.messages.getString(
MESSAGE_PREFIX + "extrainfo.proxyserver",
nodeServers[nodei].equals("")
? unknown
: nodeServers[nodei]))
.append('\n');
appendNodeOtherInfo(
otherInfo,
nodeServers[nodei],
unknown,
detectedProxyHeaderName,
MESSAGE_PREFIX + "extrainfo.proxyserver");
}
otherInfo
.append(
Constant.messages.getString(
MESSAGE_PREFIX + "extrainfo.webserver.header"))
.append('\n')
.append(
Constant.messages.getString(
MESSAGE_PREFIX + "extrainfo.webserver",
nodeServers[step2numberOfNodes - 1].equals("")
? unknown
: nodeServers[step2numberOfNodes - 1]))
.append('\n');
appendNodeOtherInfo(
otherInfo,
nodeServers[step2numberOfNodes - 1],
unknown,
detectedProxyHeaderName,
MESSAGE_PREFIX + "extrainfo.webserver");
}
if (silentProxySet.size() > 0) {
otherInfo
Expand Down Expand Up @@ -746,18 +753,52 @@ private AlertBuilder buildProxyDisclosureAlert(
.setDescription(
Constant.messages.getString(
MESSAGE_PREFIX + "desc", proxyCountForDescription))
.setAttack(getAttack())
.setOtherInfo(otherInfo.toString());
.setOtherInfo(otherInfo.toString())
.setEvidence(evidence);
}

private static void appendNodeOtherInfo(
StringBuilder otherInfo,
String nodeServer,
String unknown,
String detectedProxyHeaderName,
String nodeMessageKey) {
boolean isUnknown = nodeServer.equals("");
otherInfo
.append(
Constant.messages.getString(
nodeMessageKey, isUnknown ? unknown : nodeServer))
.append('\n');
if (isUnknown && detectedProxyHeaderName != null) {
otherInfo
.append(
Constant.messages.getString(
MESSAGE_PREFIX + "extrainfo.identifiedviaheader",
detectedProxyHeaderName))
.append('\n');
}
}

@Override
public List<Alert> getExampleAlerts() {
String[] exampleNodeServers = new String[] {"nginx/1.22", "Apache/2.4.58"};
String[] exampleNodeServers = new String[] {"", "Apache/2.4.58"};
int exampleStep2Nodes = exampleNodeServers.length;
return List.of(
buildProxyDisclosureAlert(exampleStep2Nodes, exampleNodeServers, Set.of(), true)
buildProxyDisclosureAlert(
exampleStep2Nodes,
exampleNodeServers,
Set.of(),
true,
"X-Forwarded-For",
"X-Forwarded-For: 10.0.0.1")
.build(),
buildProxyDisclosureAlert(exampleStep2Nodes, exampleNodeServers, Set.of(), false)
buildProxyDisclosureAlert(
exampleStep2Nodes,
exampleNodeServers,
Set.of(),
false,
"X-Forwarded-For",
"X-Forwarded-For: 10.0.0.1")
.build());
}

Expand All @@ -773,10 +814,6 @@ private static String getPath(URI uri) {
return "/";
}

private String getAttack() {
return Constant.messages.getString(MESSAGE_PREFIX + "attack");
}

@Override
public int getRisk() {
return Alert.RISK_MEDIUM;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,9 @@ <H2 id="id-40025">Proxy Disclosure</H2>
<ul>
<li>A list of targets for an attack against the application.</li>
<li>Potential vulnerabilities on the proxy servers that service the application.</li>
<li>The presence or absence of any proxy-based components that might cause attacks against the application to be detected, prevented, or mitigated.</li>
<li>The presence of any proxy-based components that could detect, prevent, or mitigate attacks against the application.</li>
</ul>
<p>When proxy request headers such as X-Forwarded-For or Via are echoed in TRACE responses, they are reported as alert evidence. If a node cannot be fingerprinted, other information identifies which header triggered detection.
<p>
Latest code: <a href="https://github.com/zaproxy/zap-extensions/blob/main/addOns/ascanrulesBeta/src/main/java/org/zaproxy/zap/extension/ascanrulesBeta/ProxyDisclosureScanRule.java">ProxyDisclosureScanRule.java</a>
<br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ ascanbeta.noanticsrftokens.name = Absence of Anti-CSRF Tokens
ascanbeta.oobxss.name = Out of Band XSS
ascanbeta.oobxss.skipped = no Active Scan OAST service is selected

ascanbeta.proxydisclosure.attack = TRACE, OPTIONS methods with 'Max-Forwards' header. TRACK method.
ascanbeta.proxydisclosure.desc = {0} proxy server(s) were detected or fingerprinted. This information helps a potential attacker to determine\n- A list of targets for an attack against the application.\n - Potential vulnerabilities on the proxy servers that service the application.\n - The presence or absence of any proxy-based components that might cause attacks against the application to be detected, prevented, or mitigated.

ascanbeta.proxydisclosure.desc = {0} proxy server(s) were detected or fingerprinted. This information helps a potential attacker to determine\n- A list of targets for an attack against the application.\n- Potential vulnerabilities on the proxy servers that service the application.\n- The presence of any proxy-based components that could detect, prevent, or mitigate attacks against the application.
ascanbeta.proxydisclosure.extrainfo.identifiedviaheader = Identified via proxy request header: {0}
ascanbeta.proxydisclosure.extrainfo.proxyserver = - {0}
ascanbeta.proxydisclosure.extrainfo.proxyserver.header = Using the TRACE, OPTIONS, and TRACK methods, the following proxy servers have been identified between ZAP and the application/web server:
ascanbeta.proxydisclosure.extrainfo.silentproxyserver = - {0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
package org.zaproxy.zap.extension.ascanrulesBeta;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;

import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -80,10 +83,32 @@ void shouldHaveExpectedExampleAlerts() {
// Then
assertThat(alerts, hasSize(2));
Alert highRiskAlert = alerts.get(0);
assertThat(highRiskAlert.getAlertRef(), is(equalTo("40025-1")));
assertThat(highRiskAlert.getRisk(), is(equalTo(Alert.RISK_HIGH)));
assertThat(highRiskAlert.getConfidence(), is(equalTo(Alert.CONFIDENCE_MEDIUM)));
assertThat(highRiskAlert.getEvidence(), is(equalTo("X-Forwarded-For: 10.0.0.1")));
assertThat(highRiskAlert.getAttack(), is(emptyOrNullString()));
assertThat(
highRiskAlert.getDescription(),
containsString(
"The presence of any proxy-based components that could detect, prevent,"
+ " or mitigate attacks against the application."));
assertThat(
highRiskAlert.getOtherInfo(),
containsString("Identified via proxy request header: X-Forwarded-For"));
assertThat(highRiskAlert.getOtherInfo(), containsString("- Unknown"));
assertThat(highRiskAlert.getOtherInfo(), containsString("- Apache/2.4.58"));
Alert mediumRiskAlert = alerts.get(1);
assertThat(mediumRiskAlert.getAlertRef(), is(equalTo("40025-2")));
assertThat(mediumRiskAlert.getRisk(), is(equalTo(Alert.RISK_MEDIUM)));
assertThat(mediumRiskAlert.getConfidence(), is(equalTo(Alert.CONFIDENCE_MEDIUM)));
assertThat(mediumRiskAlert.getEvidence(), is(equalTo("X-Forwarded-For: 10.0.0.1")));
assertThat(mediumRiskAlert.getAttack(), is(emptyOrNullString()));
assertThat(
mediumRiskAlert.getOtherInfo(),
containsString("Identified via proxy request header: X-Forwarded-For"));
assertThat(
mediumRiskAlert.getOtherInfo(),
not(containsString("The 'TRACE' method is enabled")));
}
}
Loading