Files
codeql/java/ql/test/library-tests/pathsanitizer/Test.java
2025-03-17 16:02:13 -04:00

847 lines
29 KiB
Java

import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import android.net.Uri;
public class Test {
Object source() {
return null;
}
void sink(Object o) {}
private void exactPathMatchGuardValidation(String path) throws Exception {
if (!path.equals("/safe/path"))
throw new Exception();
}
public void exactPathMatchGuard() throws Exception {
{
String source = (String) source();
if (source.equals("/safe/path"))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
if ("/safe/path".equals(source))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
URI source = (URI) source();
if (source.equals(new URI("http://safe/uri")))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
File source = (File) source();
if (source.equals(new File("/safe/file")))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
Uri source = (Uri) source();
if (source.equals(Uri.parse("http://safe/uri")))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
exactPathMatchGuardValidation(source);
sink(source); // Safe
}
}
private void allowListGuardValidation(String path) throws Exception {
if (path.contains("..") || !path.startsWith("/safe"))
throw new Exception();
}
public void allowListGuard() throws Exception {
// Prefix check by itself is not enough
{
String source = (String) source();
if (source.startsWith("/safe")) {
sink(source); // $ hasTaintFlow
} else
sink(source); // $ hasTaintFlow
}
// PathTraversalGuard + allowListGuard
{
String source = (String) source();
if (!source.contains("..") && source.startsWith("/safe"))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
if (source.indexOf("..") == -1 && source.startsWith("/safe"))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
if (source.lastIndexOf("..") == -1 && source.startsWith("/safe"))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
// PathTraversalSanitizer + allowListGuard
{
File source = (File) source();
String normalized = source.getCanonicalPath();
if (normalized.startsWith("/safe")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
File source = (File) source();
String normalized = source.getCanonicalFile().toString();
if (normalized.startsWith("/safe")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
Path normalized = Paths.get(source).normalize();
if (normalized.startsWith("/safe")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (normalized.startsWith("/safe")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (normalized.regionMatches(0, "/safe", 0, 5)) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (normalized.matches("/safe/.*")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
// validation method
{
String source = (String) source();
allowListGuardValidation(source);
sink(source); // Safe
}
// PathInjectionSanitizer + partial string match is considered unsafe
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (normalized.contains("/safe")) {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (normalized.regionMatches(1, "/safe", 0, 5)) {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (normalized.matches(".*/safe/.*")) {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
}
private void dotDotCheckGuardValidation(String path) throws Exception {
if (!path.startsWith("/safe") || path.contains(".."))
throw new Exception();
}
public void dotDotCheckGuard() throws Exception {
// dot dot check by itself is not enough
{
String source = (String) source();
if (!source.contains("..")) {
sink(source); // $ hasTaintFlow
} else
sink(source); // $ hasTaintFlow
}
// allowListGuard + dotDotCheckGuard
{
String source = (String) source();
if (source.startsWith("/safe") && !source.contains(".."))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
if (source.startsWith("/safe") && source.indexOf("..") == -1)
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
if (!source.startsWith("/safe") || source.indexOf("..") != -1)
sink(source); // $ hasTaintFlow
else
sink(source); // Safe
}
{
String source = (String) source();
if (source.startsWith("/safe") && source.lastIndexOf("..") == -1)
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
// blockListGuard + dotDotCheckGuard
{
String source = (String) source();
if (!source.startsWith("/data") && !source.contains(".."))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
if (!source.startsWith("/data") && source.indexOf("..") == -1)
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
if (source.startsWith("/data") || source.indexOf("..") != -1)
sink(source); // $ hasTaintFlow
else
sink(source); // Safe
}
{
String source = (String) source();
if (!source.startsWith("/data") && source.lastIndexOf("..") == -1)
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
// validation method
{
String source = (String) source();
dotDotCheckGuardValidation(source);
sink(source); // Safe
}
}
private void blockListGuardValidation(String path) throws Exception {
if (path.contains("..") || path.startsWith("/data"))
throw new Exception();
}
public void blockListGuard() throws Exception {
// Prefix check by itself is not enough
{
String source = (String) source();
if (!source.startsWith("/data")) {
sink(source); // $ hasTaintFlow
} else
sink(source); // $ hasTaintFlow
}
// PathTraversalGuard + blockListGuard
{
String source = (String) source();
if (!source.contains("..") && !source.startsWith("/data"))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
if (source.indexOf("..") == -1 && !source.startsWith("/data"))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
if (source.lastIndexOf("..") == -1 && !source.startsWith("/data"))
sink(source); // Safe
else
sink(source); // $ hasTaintFlow
}
// PathTraversalSanitizer + blockListGuard
{
File source = (File) source();
String normalized = source.getCanonicalPath();
if (!normalized.startsWith("/data")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
File source = (File) source();
String normalized = source.getCanonicalFile().toString();
if (!normalized.startsWith("/data")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
Path normalized = Paths.get(source).normalize();
if (!normalized.startsWith("/data")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (!normalized.startsWith("/data")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (!normalized.regionMatches(0, "/data", 0, 5)) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (!normalized.matches("/data/.*")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
// validation method
{
String source = (String) source();
blockListGuardValidation(source);
sink(source); // Safe
}
// PathInjectionSanitizer + partial string match with disallowed words
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (!normalized.contains("/")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (!normalized.regionMatches(1, "/", 0, 5)) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (!normalized.matches("/")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
// PathInjectionSanitizer + partial string match with disallowed prefixes
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (!normalized.contains("/data")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (!normalized.regionMatches(1, "/data", 0, 5)) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
if (!normalized.matches(".*/data/.*")) {
sink(source); // Safe
sink(normalized); // Safe
} else {
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
}
private void fileConstructorValidation(String path) throws Exception {
// Use `indexOf` instead of `contains` for this test since a `contains`
// call in this scenario will already be sanitized due to the inclusion
// of `ValidatedVariableAccess` nodes in `defaultTaintSanitizer`.
if (path.indexOf("..") != -1)
throw new Exception();
}
public void fileConstructorSanitizer() throws Exception {
// PathTraversalGuard
{
String source = (String) source();
File f1 = new File("safe/file.txt");
if (!source.contains("..")) {
File f2 = new File(f1, source);
sink(f2); // Safe
sink(source); // $ hasTaintFlow
} else {
File f3 = new File(f1, source);
sink(f3); // $ hasTaintFlow
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
File f1Tainted = (File) source();
if (!source.contains("..")) {
// `f2` is unsafe if `f1` is tainted
File f2 = new File(f1Tainted, source);
sink(f2); // $ hasTaintFlow
sink(source); // $ hasTaintFlow
} else {
File f3 = new File(f1Tainted, source);
sink(f3); // $ hasTaintFlow
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
File f1Null = null;
if (!source.contains("..")) {
// `f2` is unsafe if `f1` is null
File f2 = new File(f1Null, source);
sink(f2); // $ hasTaintFlow
sink(source); // $ hasTaintFlow
} else {
File f3 = new File(f1Null, source);
sink(f3); // $ hasTaintFlow
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
File f1 = new File("safe/file.txt");
if (source.indexOf("..") == -1) {
File f2 = new File(f1, source);
sink(f2); // Safe
sink(source); // $ hasTaintFlow
} else {
File f3 = new File(f1, source);
sink(f3); // $ hasTaintFlow
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
File f1 = new File("safe/file.txt");
if (source.indexOf("..") != -1) {
File f2 = new File(f1, source);
sink(f2); // $ hasTaintFlow
sink(source); // $ hasTaintFlow
} else {
File f3 = new File(f1, source);
sink(f3); // Safe
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
File f1 = new File("safe/file.txt");
if (source.lastIndexOf("..") == -1) {
File f2 = new File(f1, source);
sink(f2); // Safe
sink(source); // $ hasTaintFlow
} else {
File f3 = new File(f1, source);
sink(f3); // $ hasTaintFlow
sink(source); // $ hasTaintFlow
}
}
// validation method
{
String source = (String) source();
File f1 = new File("safe/file.txt");
fileConstructorValidation(source);
File f2 = new File(f1, source);
sink(f2); // Safe
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
File f1 = new File("safe/file.txt");
if (source.contains("..")) {
throw new Exception();
} else {
File f2 = new File(f1, source);
sink(f2); // Safe
sink(source); // $ hasTaintFlow
}
}
// PathNormalizeSanitizer
{
File source = (File) source();
String normalized = source.getCanonicalPath();
File f1 = new File("safe/file.txt");
File f2 = new File(f1, normalized);
sink(f2); // Safe
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
{
File source = (File) source();
String normalized = source.getCanonicalFile().toString();
File f1 = new File("safe/file.txt");
File f2 = new File(f1, normalized);
sink(f2); // Safe
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
{
String source = (String) source();
String normalized = Paths.get(source).normalize().toString();
File f1 = new File("safe/file.txt");
File f2 = new File(f1, normalized);
sink(f2); // Safe
sink(source); // $ hasTaintFlow
sink(normalized); // $ hasTaintFlow
}
}
private void directoryCharsValidation(String path) throws Exception {
if (!path.matches("[0-9a-fA-F]{20,}")) {
throw new Exception();
}
}
public void directoryCharsSanitizer() throws Exception {
// DirectoryCharactersGuard
// Ensures that directory characters (/, \ and ..) cannot possibly be in the payload
// branch = true
{
String source = (String) source();
if (source.matches("[0-9a-fA-F]{20,}")) {
sink(source); // Safe
} else {
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
if (source.matches("[0-9a-fA-F]*")) {
sink(source); // Safe
} else {
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
if (source.matches("[0-9a-fA-F]+")) {
sink(source); // Safe
} else {
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
if (source.matches("[0-9a-fA-F\\.]+")) {
sink(source); // $ hasTaintFlow
} else {
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
if (source.matches("[0-9a-fA-F/]+")) {
sink(source); // $ hasTaintFlow
} else {
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
if (source.matches("[0-9a-fA-F\\\\]+")) {
sink(source); // $ hasTaintFlow
} else {
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
// exclude '.', '/', '\'
if (source.matches("[^0-9./\\\\a-f]+")) {
sink(source); // Safe
} else {
sink(source); // $ hasTaintFlow
}
}
{
String source = (String) source();
// '/' is not excluded
if (source.matches("[^0-9.\\\\a-f]+")) {
sink(source); // $ hasTaintFlow
} else {
sink(source); // $ hasTaintFlow
}
}
// branch = false
{
String source = (String) source();
if (source.matches("[\\./\\\\]+")) {
sink(source); // $ hasTaintFlow
} else {
sink(source); // $ Safe
}
}
{
String source = (String) source();
// not a complete sanitizer since it doesn't protect against absolute path injection
if (source.matches("[\\.]+")) {
sink(source); // $ hasTaintFlow
} else {
sink(source); // $ hasTaintFlow
}
}
// validation method
{
String source = (String) source();
directoryCharsValidation(source);
sink(source); // Safe
}
// ReplaceDirectoryCharactersSanitizer
// Removes ".." sequences and path separators from the payload
// single `replaceAll` call
{
String source = (String) source();
source = source.replaceAll("\\.\\.|[/\\\\]", "");
sink(source); // Safe
}
{
String source = (String) source();
source = source.replaceAll("\\.|[/\\\\]", "-");
sink(source); // Safe
}
{
String source = (String) source();
source = source.replaceAll("[.][.]|[/\\\\]", "_");
sink(source); // Safe
}
{
String source = (String) source();
// test a not-accepted replacement character
source = source.replaceAll("[.][.]|[/\\\\]", "/");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
source = source.replaceAll(".|[/\\\\]", "");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
source = source.replaceAll("\\.|/|\\\\", "");
sink(source); // Safe
}
{
String source = (String) source();
source = source.replaceAll("[\\./\\\\]", "");
sink(source); // Safe
}
{
String source = (String) source();
source = source.replaceAll("[\\.\\\\]", "");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
source = source.replaceAll("[^\\.\\\\/]", "");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
// Bypassable with ".../...//"
source = source.replaceAll("\\.\\./", "");
sink(source); // $ hasTaintFlow
}
// multiple `replaceAll` or `replace` calls
{
String source = (String) source();
source = source.replaceAll("\\.", "").replaceAll("/", "");
sink(source); // Safe
}
{
String source = (String) source();
// test a not-accepted replacement character in each call
source = source.replaceAll("\\.", "/").replaceAll("/", ".");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
// test a not-accepted replacement character in first call
source = source.replaceAll("\\.", "/").replaceAll("/", "-");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
// test a not-accepted replacement character in second call
source = source.replaceAll("\\.", "_").replaceAll("/", ".");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
source = source.replaceAll("\\.", "").replaceAll("/", "").replaceAll("\\\\", "");
sink(source); // Safe
}
{
String source = (String) source();
// '/' or '\' are not replaced
source = source.replaceAll("\\.", "").replaceAll("\\.", "");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
// '.' is not replaced
source = source.replaceAll("/", "").replaceAll("\\\\", "");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
// Bypassable with ".....///"
source = source.replaceAll("\\.\\./", "").replaceAll("\\./", "");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
source = source.replace(".", "").replace("/", "");
sink(source); // Safe
}
{
String source = (String) source();
source = source.replace(".", "").replace("/", "").replace("\\", "");
sink(source); // Safe
}
{
String source = (String) source();
// '/' or '\' are not replaced
source = source.replace(".", "").replace(".", "");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
// '.' is not replaced
source = source.replace("/", "").replace("\\", "");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
// Bypassable with ".....///"
source = source.replace("../", "").replace("./", "");
sink(source); // $ hasTaintFlow
}
{
String source = (String) source();
// Bypassable with ".../...//"
source = source.replace("../", "");
sink(source); // $ hasTaintFlow
}
}
}