From 30d9ebabda02c9df100d783b7de5a3153a3de3b7 Mon Sep 17 00:00:00 2001 From: kingthorin Date: Fri, 29 May 2026 11:05:42 -0400 Subject: [PATCH] Clarify Proxy Disclosure alert details Signed-off-by: kingthorin --- addOns/ascanrulesBeta/CHANGELOG.md | 3 +- .../ProxyDisclosureScanRule.java | 93 +++++++++++++------ .../resources/help/contents/ascanbeta.html | 3 +- .../resources/Messages.properties | 5 +- .../ProxyDisclosureScanRuleUnitTest.java | 25 +++++ 5 files changed, 97 insertions(+), 32 deletions(-) diff --git a/addOns/ascanrulesBeta/CHANGELOG.md b/addOns/ascanrulesBeta/CHANGELOG.md index 75f0091a44f..46aa518e864 100644 --- a/addOns/ascanrulesBeta/CHANGELOG.md +++ b/addOns/ascanrulesBeta/CHANGELOG.md @@ -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 diff --git a/addOns/ascanrulesBeta/src/main/java/org/zaproxy/zap/extension/ascanrulesBeta/ProxyDisclosureScanRule.java b/addOns/ascanrulesBeta/src/main/java/org/zaproxy/zap/extension/ascanrulesBeta/ProxyDisclosureScanRule.java index e106e036b47..61e90cf619b 100644 --- a/addOns/ascanrulesBeta/src/main/java/org/zaproxy/zap/extension/ascanrulesBeta/ProxyDisclosureScanRule.java +++ b/addOns/ascanrulesBeta/src/main/java/org/zaproxy/zap/extension/ascanrulesBeta/ProxyDisclosureScanRule.java @@ -245,6 +245,8 @@ public void scan() { // point they fit into the topology // that we can otherwise document using OPTIONS/TRACE + Max-Forwards. Set silentProxySet = new HashSet<>(); + String detectedProxyHeaderName = null; + String proxyRequestHeaderEvidence = null; boolean endToEndTraceEnabled = false; boolean proxyTraceEnabled = false; @@ -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); } } @@ -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(); } @@ -687,7 +695,9 @@ private AlertBuilder buildProxyDisclosureAlert( int step2numberOfNodes, String[] nodeServers, Set 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(); @@ -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 @@ -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 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()); } @@ -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; diff --git a/addOns/ascanrulesBeta/src/main/javahelp/org/zaproxy/zap/extension/ascanrulesBeta/resources/help/contents/ascanbeta.html b/addOns/ascanrulesBeta/src/main/javahelp/org/zaproxy/zap/extension/ascanrulesBeta/resources/help/contents/ascanbeta.html index ffdfbff6fe3..8f85eb191b1 100644 --- a/addOns/ascanrulesBeta/src/main/javahelp/org/zaproxy/zap/extension/ascanrulesBeta/resources/help/contents/ascanbeta.html +++ b/addOns/ascanrulesBeta/src/main/javahelp/org/zaproxy/zap/extension/ascanrulesBeta/resources/help/contents/ascanbeta.html @@ -129,8 +129,9 @@

Proxy Disclosure

  • A list of targets for an attack against the application.
  • Potential vulnerabilities on the proxy servers that service the application.
  • -
  • The presence or absence of any proxy-based components that might cause attacks against the application to be detected, prevented, or mitigated.
  • +
  • The presence of any proxy-based components that could detect, prevent, or mitigate attacks against the application.
+

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.

Latest code: ProxyDisclosureScanRule.java
diff --git a/addOns/ascanrulesBeta/src/main/resources/org/zaproxy/zap/extension/ascanrulesBeta/resources/Messages.properties b/addOns/ascanrulesBeta/src/main/resources/org/zaproxy/zap/extension/ascanrulesBeta/resources/Messages.properties index 4d5d56a89e1..fe0cfaf11a7 100644 --- a/addOns/ascanrulesBeta/src/main/resources/org/zaproxy/zap/extension/ascanrulesBeta/resources/Messages.properties +++ b/addOns/ascanrulesBeta/src/main/resources/org/zaproxy/zap/extension/ascanrulesBeta/resources/Messages.properties @@ -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} diff --git a/addOns/ascanrulesBeta/src/test/java/org/zaproxy/zap/extension/ascanrulesBeta/ProxyDisclosureScanRuleUnitTest.java b/addOns/ascanrulesBeta/src/test/java/org/zaproxy/zap/extension/ascanrulesBeta/ProxyDisclosureScanRuleUnitTest.java index abe3b302c9c..1c6149ce9ce 100644 --- a/addOns/ascanrulesBeta/src/test/java/org/zaproxy/zap/extension/ascanrulesBeta/ProxyDisclosureScanRuleUnitTest.java +++ b/addOns/ascanrulesBeta/src/test/java/org/zaproxy/zap/extension/ascanrulesBeta/ProxyDisclosureScanRuleUnitTest.java @@ -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; @@ -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"))); } }