Skip to content

Commit 9ee8b26

Browse files
committed
ascanrules: Path Traversal add details for dir match Alerts & reduce FPs
- CHANGELOG > Added change note. - Message.properties > Added key/value pair supporting the new Alert details. - PathTraversalScanRule > Updated to include Other Info on Alerts when applicable, and pre-check the original message response to reduce false positives. - PathTraversalScanRuleUnitTest > Updated to assert Other Info or lack thereof where applicable, also assure appropriate skipping due to pre-conditions. Signed-off-by: kingthorin <[email protected]>
1 parent f2c298d commit 9ee8b26

File tree

4 files changed

+188
-47
lines changed

4 files changed

+188
-47
lines changed

addOns/ascanrules/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1212
- Rules (as applicable) have been tagged in relation to HIPAA and PCI DSS.
1313
- The Cloud Metadata Potentially Exposed scan rules now has a CWE reference.
1414
- Scan rules which execute time based attacks now include the "TEST_TIMING" alert tag.
15+
- The Path Traversal scan rule now includes further details when directory matches are made and pre-checks the original message to reduce false positives (Issue 8379).
16+
1517

1618
## [72] - 2025-06-20
1719
### Added

addOns/ascanrules/src/main/java/org/zaproxy/zap/extension/ascanrules/PathTraversalScanRule.java

Lines changed: 74 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ public class PathTraversalScanRule extends AbstractAppParamPlugin
8383
* Windows local file targets and detection pattern
8484
*/
8585
private static final ContentsMatcher WIN_PATTERN =
86-
new PatternContentsMatcher(Pattern.compile("\\[drivers\\]"));
86+
new PatternContentsMatcher(Pattern.compile("\\[drivers\\]"), Tech.Windows);
87+
private static final String WIN_DIR_EVIDENCE = "Windows";
8788
private static final String[] WIN_LOCAL_FILE_TARGETS = {
8889
// Absolute Windows file retrieval (we suppose C:\\)
8990
"c:/Windows/system.ini",
@@ -132,7 +133,8 @@ public class PathTraversalScanRule extends AbstractAppParamPlugin
132133
*/
133134
// Dot used to match 'x' or '!' (used in AIX)
134135
private static final ContentsMatcher NIX_PATTERN =
135-
new PatternContentsMatcher(Pattern.compile("root:.:0:0"));
136+
new PatternContentsMatcher(Pattern.compile("root:.:0:0"), Tech.Linux);
137+
private static final String NIX_DIR_EVIDENCE = "etc";
136138
private static final String[] NIX_LOCAL_FILE_TARGETS = {
137139
// Absolute file retrieval
138140
"/etc/passwd",
@@ -158,10 +160,9 @@ public class PathTraversalScanRule extends AbstractAppParamPlugin
158160
// ..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252Fetc%252Fpasswd%2500.jpg
159161
};
160162

161-
/*
162-
* Windows/Unix/Linux/etc. local directory targets and detection pattern
163-
*/
164-
private static final ContentsMatcher DIR_PATTERN = new DirNamesContentsMatcher();
163+
private static final ContentsMatcher NIX_DIR_MATCHER = new DirNamesContentsMatcher(Tech.Linux);
164+
private static final ContentsMatcher WIN_DIR_MATCHER =
165+
new DirNamesContentsMatcher(Tech.Windows);
165166
private static final String[] WIN_LOCAL_DIR_TARGETS = {
166167
"c:/",
167168
"c:\\",
@@ -183,17 +184,19 @@ public class PathTraversalScanRule extends AbstractAppParamPlugin
183184
"/",
184185
"../../../../../../../../../../../../../../../../",
185186
"/../../../../../../../../../../../../../../../../",
186-
"file:///",
187+
"file:///"
187188
};
188189

189190
private static final ContentsMatcher WAR_PATTERN =
190-
new PatternContentsMatcher(Pattern.compile("</web-app>"));
191+
new PatternContentsMatcher(Pattern.compile("</web-app>"), Tech.Tomcat);
191192

192193
/*
193194
* Standard local file prefixes
194195
*/
195196
private static final String[] LOCAL_FILE_RELATIVE_PREFIXES = {"", "/", "\\"};
196197

198+
private static final List<String> DIR_EVIDENCE_LIST =
199+
List.of(NIX_DIR_EVIDENCE, WIN_DIR_EVIDENCE);
197200
/*
198201
* details of the vulnerability which we are attempting to find
199202
*/
@@ -344,7 +347,6 @@ public void scan(HttpMessage msg, String param, String value) {
344347

345348
// Check 2: Start detection for *NIX patterns
346349
if (inScope(Tech.Linux) || inScope(Tech.MacOS)) {
347-
348350
for (int h = 0; h < nixCount; h++) {
349351

350352
// Check if a there was a finding or the scan has been stopped
@@ -386,10 +388,9 @@ public void scan(HttpMessage msg, String param, String value) {
386388
// Check 3: Detect if this page is a directory browsing component
387389
if (inScope(Tech.Linux) || inScope(Tech.MacOS)) {
388390
for (int h = 0; h < nixDirCount; h++) {
389-
390391
// Check if a there was a finding or the scan has been stopped
391392
// if yes dispose resources and exit
392-
if (sendAndCheckPayload(param, NIX_LOCAL_DIR_TARGETS[h], DIR_PATTERN, 3)
393+
if (sendAndCheckPayload(param, NIX_LOCAL_DIR_TARGETS[h], NIX_DIR_MATCHER, 3)
393394
|| isStop()) {
394395
// Dispose all resources
395396
// Exit the scan rule
@@ -399,7 +400,7 @@ public void scan(HttpMessage msg, String param, String value) {
399400
}
400401
if (inScope(Tech.Windows)) {
401402
for (int h = 0; h < winDirCount; h++) {
402-
if (sendAndCheckPayload(param, WIN_LOCAL_DIR_TARGETS[h], DIR_PATTERN, 3)
403+
if (sendAndCheckPayload(param, WIN_LOCAL_DIR_TARGETS[h], WIN_DIR_MATCHER, 3)
403404
|| isStop()) {
404405
// Dispose all resources
405406
// Exit the scan rule
@@ -661,12 +662,23 @@ private AlertBuilder createUnmatchedAlert(String param, String attack) {
661662

662663
private AlertBuilder createMatchedAlert(
663664
String param, String attack, String evidence, int check) {
664-
return newAlert()
665-
.setConfidence(Alert.CONFIDENCE_MEDIUM)
666-
.setParam(param)
667-
.setAttack(attack)
668-
.setEvidence(evidence)
669-
.setAlertRef(getId() + "-" + check);
665+
AlertBuilder builder =
666+
newAlert()
667+
.setConfidence(Alert.CONFIDENCE_MEDIUM)
668+
.setParam(param)
669+
.setAttack(attack)
670+
.setEvidence(evidence)
671+
.setAlertRef(getId() + "-" + check);
672+
if (DIR_EVIDENCE_LIST.contains(evidence)) {
673+
builder.setOtherInfo(
674+
Constant.messages.getString(
675+
MESSAGE_PREFIX + "info",
676+
evidence,
677+
evidence.equals(WIN_DIR_EVIDENCE)
678+
? DirNamesContentsMatcher.WIN_MATCHES
679+
: DirNamesContentsMatcher.NIX_MATCHES));
680+
}
681+
return builder;
670682
}
671683

672684
@Override
@@ -692,7 +704,7 @@ private static class PatternContentsMatcher implements ContentsMatcher {
692704

693705
private final Pattern pattern;
694706

695-
public PatternContentsMatcher(Pattern pattern) {
707+
public PatternContentsMatcher(Pattern pattern, Tech tech) {
696708
this.pattern = pattern;
697709
}
698710

@@ -708,46 +720,61 @@ public String match(String contents) {
708720

709721
private static class DirNamesContentsMatcher implements ContentsMatcher {
710722

723+
private static final String NIX_MATCHES =
724+
String.join(", ", List.of("proc", NIX_DIR_EVIDENCE, "boot", "tmp", "home"));
725+
private static final String WIN_MATCHES =
726+
String.join(", ", List.of(WIN_DIR_EVIDENCE, "Program Files"));
727+
private static final Pattern PROC_PATT =
728+
Pattern.compile(
729+
"(?:^|\\W)proc(?:\\W|$)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
730+
private static final Pattern ETC_PATT =
731+
Pattern.compile(
732+
"(?:^|\\W)etc(?:\\W|$)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
733+
private static final Pattern BOOT_PATT =
734+
Pattern.compile(
735+
"(?:^|\\W)boot(?:\\W|$)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
736+
private static final Pattern TMP_PATT =
737+
Pattern.compile(
738+
"(?:^|\\W)tmp(?:\\W|$)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
739+
private static final Pattern HOME_PATT =
740+
Pattern.compile(
741+
"(?:^|\\W)home(?:\\W|$)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
742+
743+
private Tech tech;
744+
745+
public DirNamesContentsMatcher(Tech tech) {
746+
this.tech = tech;
747+
}
748+
711749
@Override
712750
public String match(String contents) {
713-
String result = matchNixDirectories(contents);
714-
if (result != null) {
715-
return result;
751+
if (this.tech == Tech.Linux) {
752+
return matchNixDirectories(contents);
753+
}
754+
if (this.tech == Tech.Windows) {
755+
return matchWinDirectories(contents);
716756
}
717-
return matchWinDirectories(contents);
757+
return null;
718758
}
719759

720760
private static String matchNixDirectories(String contents) {
721-
Pattern procPattern =
722-
Pattern.compile("(?:^|\\W)proc(?:\\W|$)", Pattern.CASE_INSENSITIVE);
723-
Pattern etcPattern = Pattern.compile("(?:^|\\W)etc(?:\\W|$)", Pattern.CASE_INSENSITIVE);
724-
Pattern bootPattern =
725-
Pattern.compile("(?:^|\\W)boot(?:\\W|$)", Pattern.CASE_INSENSITIVE);
726-
Pattern tmpPattern = Pattern.compile("(?:^|\\W)tmp(?:\\W|$)", Pattern.CASE_INSENSITIVE);
727-
Pattern homePattern =
728-
Pattern.compile("(?:^|\\W)home(?:\\W|$)", Pattern.CASE_INSENSITIVE);
729-
730-
Matcher procMatcher = procPattern.matcher(contents);
731-
Matcher etcMatcher = etcPattern.matcher(contents);
732-
Matcher bootMatcher = bootPattern.matcher(contents);
733-
Matcher tmpMatcher = tmpPattern.matcher(contents);
734-
Matcher homeMatcher = homePattern.matcher(contents);
735-
736-
if (procMatcher.find()
737-
&& etcMatcher.find()
738-
&& bootMatcher.find()
739-
&& tmpMatcher.find()
740-
&& homeMatcher.find()) {
741-
return "etc";
761+
if (PROC_PATT.matcher(contents).find()
762+
&& ETC_PATT.matcher(contents).find()
763+
&& BOOT_PATT.matcher(contents).find()
764+
&& TMP_PATT.matcher(contents).find()
765+
&& HOME_PATT.matcher(contents).find()) {
766+
return NIX_DIR_EVIDENCE;
742767
}
743768

744769
return null;
745770
}
746771

747772
private static String matchWinDirectories(String contents) {
748-
if (contents.contains("Windows")
749-
&& Pattern.compile("Program\\sFiles").matcher(contents).find()) {
750-
return "Windows";
773+
if (contents.contains(WIN_DIR_EVIDENCE)
774+
&& Pattern.compile("Program\\sFiles", Pattern.CASE_INSENSITIVE)
775+
.matcher(contents)
776+
.find()) {
777+
return WIN_DIR_EVIDENCE;
751778
}
752779

753780
return null;

addOns/ascanrules/src/main/resources/org/zaproxy/zap/extension/ascanrules/resources/Messages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ ascanrules.parametertamper.desc = Parameter manipulation caused an error page or
114114
ascanrules.parametertamper.name = Parameter Tampering
115115
ascanrules.parametertamper.soln = Identify the cause of the error and fix it. Do not trust client side input and enforce a tight check in the server side. Besides, catch the exception properly. Use a generic 500 error page for internal server error.
116116

117+
ascanrules.pathtraversal.info = While the evidence field indicates {0}, the rule actually checked that the response contains matches for all of the following: {1}.
117118
ascanrules.pathtraversal.name = Path Traversal
118119

119120
ascanrules.payloader.desc = Provides support for custom payloads in scan rules.

0 commit comments

Comments
 (0)