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(); if (source.matches(".+[\\./\\\\].+")) { sink(source); // $ hasTaintFlow } else { sink(source); // Safe } } { String source = (String) source(); // does not match whole string if (source.matches("[\\./\\\\]+")) { sink(source); // $ hasTaintFlow } else { sink(source); // $ hasTaintFlow } } { 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 } } }