Partial Path Traversal split into 2 queries

This commit is contained in:
Shyam Mehta
2022-07-20 17:39:57 -04:00
parent b7e522749f
commit 09ec37943c
7 changed files with 125 additions and 69 deletions

View File

@@ -0,0 +1,55 @@
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.environment.SystemProperty
class MethodStringStartsWith extends Method {
MethodStringStartsWith() {
this.getDeclaringType() instanceof TypeString and
this.hasName("startsWith")
}
}
class MethodFileGetCanonicalPath extends Method {
MethodFileGetCanonicalPath() {
this.getDeclaringType() instanceof TypeFile and
this.hasName("getCanonicalPath")
}
}
class MethodAccessFileGetCanonicalPath extends MethodAccess {
MethodAccessFileGetCanonicalPath() { this.getMethod() instanceof MethodFileGetCanonicalPath }
}
abstract class FileSeparatorExpr extends Expr { }
class SystemPropFileSeparatorExpr extends FileSeparatorExpr {
SystemPropFileSeparatorExpr() { this = getSystemProperty("file.separator") }
}
class StringLiteralFileSeparatorExpr extends FileSeparatorExpr, StringLiteral {
StringLiteralFileSeparatorExpr() {
this.getValue().matches("%/") or this.getValue().matches("%\\")
}
}
class CharacterLiteralFileSeparatorExpr extends FileSeparatorExpr, CharacterLiteral {
CharacterLiteralFileSeparatorExpr() { this.getValue() = "/" or this.getValue() = "\\" }
}
class FileSeparatorAppend extends AddExpr {
FileSeparatorAppend() { this.getRightOperand() instanceof FileSeparatorExpr }
}
predicate isSafe(Expr expr) {
DataFlow::localExprFlow(any(Expr e |
e instanceof FileSeparatorAppend or e instanceof FileSeparatorExpr
), expr)
}
class PartialPathTraversalMethodAccess extends MethodAccess {
PartialPathTraversalMethodAccess() {
this.getMethod() instanceof MethodStringStartsWith and
DataFlow::localExprFlow(any(MethodAccessFileGetCanonicalPath gcpma), this.getQualifier()) and
not isSafe(this.getArgument(0))
}
}

View File

@@ -0,0 +1,16 @@
import java
import semmle.code.java.security.PartialPathTraversal
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
class PartialPathTraversalFromRemoteConfig extends TaintTracking::Configuration {
PartialPathTraversalFromRemoteConfig() { this = "PartialPathTraversalFromRemoteConfig" }
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node node) {
any(PartialPathTraversalMethodAccess ma).getQualifier() = node.asExpr()
}
}

View File

@@ -10,57 +10,7 @@
* external/cwe/cwe-023
*/
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.environment.SystemProperty
import semmle.code.java.security.PartialPathTraversal
class MethodStringStartsWith extends Method {
MethodStringStartsWith() {
this.getDeclaringType() instanceof TypeString and
this.hasName("startsWith")
}
}
class MethodFileGetCanonicalPath extends Method {
MethodFileGetCanonicalPath() {
this.getDeclaringType() instanceof TypeFile and
this.hasName("getCanonicalPath")
}
}
class MethodAccessFileGetCanonicalPath extends MethodAccess {
MethodAccessFileGetCanonicalPath() { this.getMethod() instanceof MethodFileGetCanonicalPath }
}
abstract class FileSeparatorExpr extends Expr { }
class SystemPropFileSeparatorExpr extends FileSeparatorExpr {
SystemPropFileSeparatorExpr() { this = getSystemProperty("file.separator") }
}
class StringLiteralFileSeparatorExpr extends FileSeparatorExpr, StringLiteral {
StringLiteralFileSeparatorExpr() {
this.getValue().matches("%/") or this.getValue().matches("%\\")
}
}
class CharacterLiteralFileSeparatorExpr extends FileSeparatorExpr, CharacterLiteral {
CharacterLiteralFileSeparatorExpr() { this.getValue() = "/" or this.getValue() = "\\" }
}
class FileSeparatorAppend extends AddExpr {
FileSeparatorAppend() { this.getRightOperand() instanceof FileSeparatorExpr }
}
predicate isSafe(Expr expr) {
DataFlow::localExprFlow(any(Expr e |
e instanceof FileSeparatorAppend or e instanceof FileSeparatorExpr
), expr)
}
from MethodAccess ma
where
ma.getMethod() instanceof MethodStringStartsWith and
DataFlow::localExprFlow(any(MethodAccessFileGetCanonicalPath gcpma), ma.getQualifier()) and
not isSafe(ma.getArgument(0))
from PartialPathTraversalMethodAccess ma
select ma, "Partial Path Traversal Vulnerability due to insufficient guard against path traversal"

View File

@@ -0,0 +1,18 @@
/**
* @name Partial Path Traversal Vulnerability From Remote
* @description A prefix used to check that a canonicalised path falls within another must be slash-terminated.
* @kind path-problem
* @problem.severity error
* @security-severity 9.3
* @precision high
* @id java/partial-path-traversal-from-remote
* @tags security
* external/cwe/cwe-023
*/
import semmle.code.java.security.PartialPathTraversalQuery
from DataFlow::PathNode source, DataFlow::PathNode sink
where any(PartialPathTraversalFromRemoteConfig config).hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Partial Path Traversal Vulnerability due to insufficient guard against path traversal from user-supplied data"

View File

@@ -0,0 +1,17 @@
import java
import TestUtilities.InlineFlowTest
import semmle.code.java.security.PartialPathTraversalQuery
class TestRemoteSource extends RemoteFlowSource {
TestRemoteSource() { this.asParameter().hasName(["dir", "path"]) }
override string getSourceType() { result = "TestSource" }
}
class Test extends InlineFlowTest {
override DataFlow::Configuration getValueFlowConfig() { none() }
override TaintTracking::Configuration getTaintFlowConfig() {
result instanceof PartialPathTraversalFromRemoteConfig
}
}

View File

@@ -7,14 +7,14 @@ import java.nio.file.Files;
public class PartialPathTraversalTest {
public void esapiExample(File dir, File parent) throws IOException {
if (!dir.getCanonicalPath().startsWith(parent.getCanonicalPath())) {
if (!dir.getCanonicalPath().startsWith(parent.getCanonicalPath())) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
void foo1(File dir, File parent) throws IOException {
(dir.getCanonicalPath()).startsWith((parent.getCanonicalPath()));
(dir.getCanonicalPath()).startsWith((parent.getCanonicalPath())); // $hasTaintFlow
}
void foo2(File dir, File parent) throws IOException {
@@ -26,31 +26,31 @@ public class PartialPathTraversalTest {
void foo3(File dir, File parent) throws IOException {
String parentPath = parent.getCanonicalPath();
if (!dir.getCanonicalPath().startsWith(parentPath)) {
if (!dir.getCanonicalPath().startsWith(parentPath)) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
}
void foo4(File dir) throws IOException {
if (!dir.getCanonicalPath().startsWith("/usr" + "/dir")) {
if (!dir.getCanonicalPath().startsWith("/usr" + "/dir")) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
}
void foo5(File dir, File parent) throws IOException {
String canonicalPath = dir.getCanonicalPath();
if (!canonicalPath.startsWith(parent.getCanonicalPath())) {
if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
}
void foo6(File dir, File parent) throws IOException {
String canonicalPath = dir.getCanonicalPath();
if (!canonicalPath.startsWith(parent.getCanonicalPath())) {
if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
String canonicalPath2 = dir.getCanonicalPath();
if (!canonicalPath2.startsWith(parent.getCanonicalPath())) {
if (!canonicalPath2.startsWith(parent.getCanonicalPath())) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
}
@@ -58,10 +58,10 @@ public class PartialPathTraversalTest {
void foo7(File dir, File parent) throws IOException {
String canonicalPath = dir.getCanonicalPath();
String canonicalPath2 = dir.getCanonicalPath();
if (!canonicalPath.startsWith(parent.getCanonicalPath())) {
if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
if (!canonicalPath2.startsWith(parent.getCanonicalPath())) {
if (!canonicalPath2.startsWith(parent.getCanonicalPath())) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
}
@@ -72,7 +72,7 @@ public class PartialPathTraversalTest {
void foo8(File parent) throws IOException {
String canonicalPath = getChild().getCanonicalPath();
if (!canonicalPath.startsWith(parent.getCanonicalPath())) {
if (!canonicalPath.startsWith(parent.getCanonicalPath())) {
throw new IOException("Invalid directory: " + getChild().getCanonicalPath());
}
}
@@ -91,7 +91,7 @@ public class PartialPathTraversalTest {
void foo11(File dir, File parent) throws IOException {
String parentCanonical = parent.getCanonicalPath();
if (!dir.getCanonicalPath().startsWith(parentCanonical)) {
if (!dir.getCanonicalPath().startsWith(parentCanonical)) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
}
@@ -99,10 +99,10 @@ public class PartialPathTraversalTest {
void foo12(File dir, File parent) throws IOException {
String parentCanonical = parent.getCanonicalPath();
String parentCanonical2 = parent.getCanonicalPath();
if (!dir.getCanonicalPath().startsWith(parentCanonical)) {
if (!dir.getCanonicalPath().startsWith(parentCanonical)) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
if (!dir.getCanonicalPath().startsWith(parentCanonical2)) {
if (!dir.getCanonicalPath().startsWith(parentCanonical2)) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
}
@@ -116,7 +116,7 @@ public class PartialPathTraversalTest {
void foo14(File dir, File parent) throws IOException {
String parentCanonical = parent.getCanonicalPath() + separatorChar;
if (!dir.getCanonicalPath().startsWith(parentCanonical)) {
if (!dir.getCanonicalPath().startsWith(parentCanonical)) {
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
}
@@ -170,7 +170,7 @@ public class PartialPathTraversalTest {
void foo19(File dir, File parent) throws IOException {
String parentCanonical = parent.getCanonicalPath() + "/potato";
if (!dir.getCanonicalPath().startsWith(parentCanonical)) {
if (!dir.getCanonicalPath().startsWith(parentCanonical)) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
}
@@ -188,7 +188,7 @@ public class PartialPathTraversalTest {
String filePath = sb.toString();
File encodedFile = new File(filePath);
try {
if (!encodedFile.getCanonicalPath().startsWith(cacheDir.getCanonicalPath())) {
if (!encodedFile.getCanonicalPath().startsWith(cacheDir.getCanonicalPath())) { // $hasTaintFlow
return null;
}
return Files.newInputStream(encodedFile.toPath());
@@ -206,7 +206,7 @@ public class PartialPathTraversalTest {
void foo22(File dir, File dir2, File parent, boolean conditional) throws IOException {
String canonicalPath = conditional ? dir.getCanonicalPath() : dir2.getCanonicalPath();
if (!canonicalPath.startsWith(parent.getCanonicalPath())) {
if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $hasTaintFlow
throw new IOException("Invalid directory: " + dir.getCanonicalPath());
}
}