== How to fix it in Java I/O API === Code examples include::../../common/fix/code-rationale.adoc[] ==== Noncompliant code example [source,kotlin,diff-id=1,diff-type=noncompliant] ---- @Controller class ExampleController { companion object { private const val TARGET_DIRECTORY = "/path/to/target/directory/" } @GetMapping("/exists") fun exists(@RequestParam("filename") filename: String) { val file = File(TARGET_DIRECTORY + filename) if (!file.exists()) { // Noncompliant throw IOException("File does not exist in the target directory") } } } ---- ==== Compliant solution [source,kotlin,diff-id=1,diff-type=compliant] ---- @Controller class ExampleController { companion object { private const val TARGET_DIRECTORY = "/path/to/target/directory/" } @GetMapping("/exists") fun exists(@RequestParam("filename") filename: String) { val file = File(TARGET_DIRECTORY + filename) val canonicalDestinationPath = file.getCanonicalPath() if (!canonicalDestinationPath.startsWith(TARGET_DIRECTORY)) { throw IOException("Entry is outside of the target directory") } else if (!file.exists()) { throw IOException("File does not exist in the target directory") } } } ---- === How does this work? :canonicalization_function: java.io.File.getCanonicalPath include::../../common/fix/canonical-path-validation.adoc[] === Pitfalls include::../../common/pitfalls/partial-path-traversal.adoc[] For example, the following code is vulnerable to partial path injection. Note that the string `targetDirectory` does not end with a path separator: [source, kotlin] ---- companion object { private val TARGET_DIRECTORY = "/Users/John" } @GetMapping("/endpoint") fun endpoint(@RequestParam("folder") file: File) { val canonicalizedFileName = file.getCanonicalPath() if (!canonicalizedFileName.startsWith(TARGET_DIRECTORY)) { throw IOException("Entry is outside of the target directory") } } ---- This check can be bypassed if other directories start with `John`. For instance, `"/Users/Johnny".startsWith("/Users/John")` returns `true`. Thus, for validation, `"/Users/John"` should actually be `"/Users/John/"`. **Warning**: Some functions, such as `getCanonicalPath`, remove the terminating path separator in their return value. + The validation code should be tested to ensure that it cannot be impacted by this issue. https://github.com/aws/aws-sdk-java/security/advisories/GHSA-c28r-hw5m-5gv3[Here is a real-life example of this vulnerability.] :joining_docs: https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html :joining_func: java.nio.file.Path.resolve include::../../common/pitfalls/oob-specific-path-joining.adoc[]