Skip to content

Commit 774e379

Browse files
authored
Merge pull request #9742 from smehta23/feat/SM/java_partial_path_traversal_vulnerability
[JAVA] Partial Path Traversal Vuln Query
2 parents 0adb588 + 1a3dc1d commit 774e379

15 files changed

+490
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/** Provides classes to reason about partial path traversal vulnerabilities. */
2+
3+
import java
4+
private import semmle.code.java.dataflow.DataFlow
5+
private import semmle.code.java.environment.SystemProperty
6+
7+
private class MethodStringStartsWith extends Method {
8+
MethodStringStartsWith() {
9+
this.getDeclaringType() instanceof TypeString and
10+
this.hasName("startsWith")
11+
}
12+
}
13+
14+
private class MethodFileGetCanonicalPath extends Method {
15+
MethodFileGetCanonicalPath() {
16+
this.getDeclaringType() instanceof TypeFile and
17+
this.hasName("getCanonicalPath")
18+
}
19+
}
20+
21+
private class MethodAccessFileGetCanonicalPath extends MethodAccess {
22+
MethodAccessFileGetCanonicalPath() { this.getMethod() instanceof MethodFileGetCanonicalPath }
23+
}
24+
25+
abstract private class FileSeparatorExpr extends Expr { }
26+
27+
private class SystemPropFileSeparatorExpr extends FileSeparatorExpr {
28+
SystemPropFileSeparatorExpr() { this = getSystemProperty("file.separator") }
29+
}
30+
31+
private class StringLiteralFileSeparatorExpr extends FileSeparatorExpr, StringLiteral {
32+
StringLiteralFileSeparatorExpr() {
33+
this.getValue().matches("%/") or this.getValue().matches("%\\")
34+
}
35+
}
36+
37+
private class CharacterLiteralFileSeparatorExpr extends FileSeparatorExpr, CharacterLiteral {
38+
CharacterLiteralFileSeparatorExpr() { this.getValue() = "/" or this.getValue() = "\\" }
39+
}
40+
41+
private class FileSeparatorAppend extends AddExpr {
42+
FileSeparatorAppend() { this.getRightOperand() instanceof FileSeparatorExpr }
43+
}
44+
45+
private predicate isSafe(Expr expr) {
46+
DataFlow::localExprFlow(any(Expr e |
47+
e instanceof FileSeparatorAppend or e instanceof FileSeparatorExpr
48+
), expr)
49+
}
50+
51+
/**
52+
* A method access that returns a boolean that incorrectly guards against Partial Path Traversal.
53+
*/
54+
class PartialPathTraversalMethodAccess extends MethodAccess {
55+
PartialPathTraversalMethodAccess() {
56+
this.getMethod() instanceof MethodStringStartsWith and
57+
DataFlow::localExprFlow(any(MethodAccessFileGetCanonicalPath gcpma), this.getQualifier()) and
58+
not isSafe(this.getArgument(0))
59+
}
60+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/** Provides taint tracking configurations to be used in partial path traversal queries. */
2+
3+
import java
4+
import semmle.code.java.security.PartialPathTraversal
5+
import semmle.code.java.dataflow.DataFlow
6+
import semmle.code.java.dataflow.ExternalFlow
7+
import semmle.code.java.dataflow.TaintTracking
8+
import semmle.code.java.dataflow.FlowSources
9+
10+
/**
11+
* A taint-tracking configuration for unsafe user input
12+
* that is used to validate against path traversal, but is insufficient
13+
* and remains vulnerable to Partial Path Traversal.
14+
*/
15+
class PartialPathTraversalFromRemoteConfig extends TaintTracking::Configuration {
16+
PartialPathTraversalFromRemoteConfig() { this = "PartialPathTraversalFromRemoteConfig" }
17+
18+
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
19+
20+
override predicate isSink(DataFlow::Node node) {
21+
any(PartialPathTraversalMethodAccess ma).getQualifier() = node.asExpr()
22+
}
23+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>A common way to check that a user-supplied path <code>SUBDIR</code> falls inside a directory <code>DIR</code>
7+
is to use <code>getCanonicalPath()</code> to remove any path-traversal elements and then check that <code>DIR</code>
8+
is a prefix. However, if <code>DIR</code> is not slash-terminated, this can unexpectedly allow access to siblings of <code>DIR</code>.</p>
9+
10+
<p>See also <code>java/partial-path-traversal-from-remote</code>, which is similar to this query but only flags instances with evidence of remote exploitability.</p>
11+
</overview>
12+
13+
<include src="PartialPathTraversalRemainder.inc.qhelp"/>
14+
15+
</qhelp>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @name Partial path traversal vulnerability
3+
* @description A prefix used to check that a canonicalised path falls within another must be slash-terminated.
4+
* @kind problem
5+
* @problem.severity error
6+
* @security-severity 9.3
7+
* @precision medium
8+
* @id java/partial-path-traversal
9+
* @tags security
10+
* external/cwe/cwe-023
11+
*/
12+
13+
import semmle.code.java.security.PartialPathTraversal
14+
15+
from PartialPathTraversalMethodAccess ma
16+
select ma, "Partial Path Traversal Vulnerability due to insufficient guard against path traversal"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
public class PartialPathTraversalBad {
2+
public void example(File dir, File parent) throws IOException {
3+
if (!dir.getCanonicalPath().startsWith(parent.getCanonicalPath())) {
4+
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
5+
}
6+
}
7+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>A common way to check that a user-supplied path <code>SUBDIR</code> falls inside a directory <code>DIR</code>
7+
is to use <code>getCanonicalPath()</code> to remove any path-traversal elements and then check that <code>DIR</code>
8+
is a prefix. However, if <code>DIR</code> is not slash-terminated, this can unexpectedly allow accessing siblings of <code>DIR</code>.</p>
9+
10+
<p>See also <code>java/partial-path-traversal</code>, which is similar to this query,
11+
but may also flag non-remotely-exploitable instances of partial path traversal vulnerabilities.</p>
12+
</overview>
13+
14+
<include src="PartialPathTraversalRemainder.inc.qhelp"/>
15+
16+
</qhelp>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @name Partial path traversal vulnerability from remote
3+
* @description A prefix used to check that a canonicalised path falls within another must be slash-terminated.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @security-severity 9.3
7+
* @precision high
8+
* @id java/partial-path-traversal-from-remote
9+
* @tags security
10+
* external/cwe/cwe-023
11+
*/
12+
13+
import semmle.code.java.security.PartialPathTraversalQuery
14+
import DataFlow::PathGraph
15+
16+
from DataFlow::PathNode source, DataFlow::PathNode sink
17+
where any(PartialPathTraversalFromRemoteConfig config).hasFlowPath(source, sink)
18+
select sink.getNode(), source, sink,
19+
"Partial Path Traversal Vulnerability due to insufficient guard against path traversal from user-supplied data"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
public class PartialPathTraversalGood {
2+
public void example(File dir, File parent) throws IOException {
3+
if (!dir.getCanonicalPath().toPath().startsWith(parent.getCanonicalPath().toPath())) {
4+
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
5+
}
6+
}
7+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<recommendation>
7+
8+
<p>If the user should only access items within a certain directory <code>DIR</code>, ensure that <code>DIR</code> is slash-terminated
9+
before checking that <code>DIR</code> is a prefix of the user-provided path, <code>SUBDIR</code>. Note, Java's <code>getCanonicalPath()</code>
10+
returns a <b>non</b>-slash-terminated path string, so a slash must be added to <code>DIR</code> if that method is used.</p>
11+
12+
</recommendation>
13+
<example>
14+
15+
<p>
16+
17+
18+
In this example, the <code>if</code> statement checks if <code>parent.getCanonicalPath()</code>
19+
is a prefix of <code>dir.getCanonicalPath()</code>. However, <code>parent.getCanonicalPath()</code> is
20+
not slash-terminated. This means that users that supply <code>dir</code> may be also allowed to access siblings of <code>parent</code>
21+
and not just children of <code>parent</code>, which is a security issue.
22+
23+
</p>
24+
25+
<sample src="PartialPathTraversalBad.java" />
26+
27+
<p>
28+
29+
In this example, the <code>if</code> statement checks if <code>parent.getCanonicalPath() + File.separator </code>
30+
is a prefix of <code>dir.getCanonicalPath()</code>. Because <code>parent.getCanonicalPath().toPath()</code> is
31+
indeed slash-terminated, the user supplying <code>dir</code> can only access children of
32+
<code>parent</code>, as desired.
33+
34+
</p>
35+
36+
<sample src="PartialPathTraversalGood.java" />
37+
38+
</example>
39+
<references>
40+
41+
<li>OWASP:
42+
<a href="https://owasp.org/www-community/attacks/Path_Traversal">Partial Path Traversal</a>.</li>
43+
<li>CVE-2022-23457:
44+
<a href="https://github.com/ESAPI/esapi-java-legacy/blob/develop/documentation/GHSL-2022-008_The_OWASP_Enterprise_Security_API.md"> ESAPI Vulnerability Report</a>.</li>
45+
46+
</references>
47+
48+
49+
</qhelp>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: newQuery
3+
---
4+
* A new query `java/partial-path-traversal` finds partial path traversal vulnerabilities resulting from incorrectly using
5+
`String#startsWith` to compare canonical paths.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
| PartialPathTraversalTest.java:10:14:10:73 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
2+
| PartialPathTraversalTest.java:17:9:17:72 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
3+
| PartialPathTraversalTest.java:29:14:29:58 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
4+
| PartialPathTraversalTest.java:35:14:35:63 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
5+
| PartialPathTraversalTest.java:42:14:42:64 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
6+
| PartialPathTraversalTest.java:49:14:49:64 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
7+
| PartialPathTraversalTest.java:53:14:53:65 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
8+
| PartialPathTraversalTest.java:61:14:61:64 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
9+
| PartialPathTraversalTest.java:64:14:64:65 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
10+
| PartialPathTraversalTest.java:75:14:75:64 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
11+
| PartialPathTraversalTest.java:94:14:94:63 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
12+
| PartialPathTraversalTest.java:102:14:102:63 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
13+
| PartialPathTraversalTest.java:105:14:105:64 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
14+
| PartialPathTraversalTest.java:173:14:173:63 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
15+
| PartialPathTraversalTest.java:191:18:191:87 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
16+
| PartialPathTraversalTest.java:209:14:209:64 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE/CWE-023/PartialPathTraversal.ql

java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.expected

Whitespace-only changes.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import java
2+
import TestUtilities.InlineFlowTest
3+
import semmle.code.java.security.PartialPathTraversalQuery
4+
5+
class TestRemoteSource extends RemoteFlowSource {
6+
TestRemoteSource() { this.asParameter().hasName(["dir", "path"]) }
7+
8+
override string getSourceType() { result = "TestSource" }
9+
}
10+
11+
class Test extends InlineFlowTest {
12+
override DataFlow::Configuration getValueFlowConfig() { none() }
13+
14+
override TaintTracking::Configuration getTaintFlowConfig() {
15+
result instanceof PartialPathTraversalFromRemoteConfig
16+
}
17+
}

0 commit comments

Comments
 (0)