diff --git a/rules/S6096/java/how-to-fix-it/java-se.adoc b/rules/S6096/java/how-to-fix-it/java-io-api.adoc similarity index 98% rename from rules/S6096/java/how-to-fix-it/java-se.adoc rename to rules/S6096/java/how-to-fix-it/java-io-api.adoc index 0eccd72edf..8451de72be 100644 --- a/rules/S6096/java/how-to-fix-it/java-se.adoc +++ b/rules/S6096/java/how-to-fix-it/java-io-api.adoc @@ -1,4 +1,4 @@ -== How to fix it in Java SE +== How to fix it in Java I/O API === Code examples diff --git a/rules/S6096/java/rule.adoc b/rules/S6096/java/rule.adoc index b9ecb7a904..b6d97f2f66 100644 --- a/rules/S6096/java/rule.adoc +++ b/rules/S6096/java/rule.adoc @@ -4,7 +4,7 @@ include::../rationale.adoc[] include::../impact.adoc[] -include::how-to-fix-it/java-se.adoc[] +include::how-to-fix-it/java-io-api.adoc[] == Resources diff --git a/rules/S6096/kotlin/how-to-fix-it/java-io-api.adoc b/rules/S6096/kotlin/how-to-fix-it/java-io-api.adoc new file mode 100644 index 0000000000..64c75080c7 --- /dev/null +++ b/rules/S6096/kotlin/how-to-fix-it/java-io-api.adoc @@ -0,0 +1,90 @@ +== How to fix it in Java I/O API + +=== Code examples + +:canonicalization_function1: java.io.File.getCanonicalFile +:canonicalization_function2: java.io.File.getCanonicalPath + +include::../../common/fix/code-rationale.adoc[] + +==== Noncompliant code example + +[source,kotlin,diff-id=1,diff-type=noncompliant] +---- +class Example { + companion object { + private const val TARGET_DIRECTORY = "/example/directory/" + } + fun extractEntry(zipFile: ZipFile) { + val entries = zipFile.entries() + val entry = entries.nextElement() + val inputStream = zipFile.getInputStream(entry) + val file = File(TARGET_DIRECTORY + entry.name) + inputStream.copyTo(file.outputStream()) + } +} +---- + +==== Compliant solution + +[source,kotlin,diff-id=1,diff-type=compliant] +---- +class Example { + companion object { + private const val TARGET_DIRECTORY = "/example/directory/" + } + fun extractEntry(zipFile: ZipFile) { + val entries = zipFile.entries() + val entry = entries.nextElement() + val inputStream = zipFile.getInputStream(entry) + val file = File(TARGET_DIRECTORY + entry.name) + val canonicalDestinationPath = file.canonicalPath + if (canonicalDestinationPath.startsWith(TARGET_DIRECTORY)) { + inputStream.copyTo(file.outputStream()) + } + } +} +---- + +=== How does this work? + +include::../../common/fix/how-does-this-work.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 const val targetDirectory = "/Users/John" +} + +fun ExtractEntry(zipFile: ZipFile) { + val entries = zipFile.entries() + val entry = entries.nextElement() + val inputStream = zipFile.getInputStream(entry) + + val file = File(entry.name) + + val canonicalDestinationPath = file.canonicalPath + if (canonicalDestinationPath.startsWith(targetDirectory)) { + Files.copy(inputStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS) + } +} +---- + +This check can be bypassed because `"/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.] diff --git a/rules/S6096/kotlin/metadata.json b/rules/S6096/kotlin/metadata.json new file mode 100644 index 0000000000..6cc25baa8b --- /dev/null +++ b/rules/S6096/kotlin/metadata.json @@ -0,0 +1,34 @@ +{ + "securityStandards": { + "CWE": [ + 20, + 22 + ], + "OWASP": [ + "A5", + "A1" + ], + "OWASP Top 10 2021": [ + "A1", + "A3" + ], + "OWASP Mobile Top 10 2024": [ + "M4" + ], + "PCI DSS 3.2": [ + "6.5.1", + "6.5.8" + ], + "PCI DSS 4.0": [ + "6.2.4" + ], + "ASVS 4.0": [ + "12.3.4", + "5.1.3", + "5.1.4" + ], + "STIG ASD_V5R3": [ + "V-222609" + ] + } +} diff --git a/rules/S6096/kotlin/rule.adoc b/rules/S6096/kotlin/rule.adoc new file mode 100644 index 0000000000..b6d97f2f66 --- /dev/null +++ b/rules/S6096/kotlin/rule.adoc @@ -0,0 +1,23 @@ +== Why is this an issue? + +include::../rationale.adoc[] + +include::../impact.adoc[] + +include::how-to-fix-it/java-io-api.adoc[] + +== Resources + +include::../common/resources/articles.adoc[] + +include::../common/resources/standards-mobile.adoc[] + +ifdef::env-github,rspecator-view[] + +''' +== Implementation Specification +(visible only on this page) + +include::../message.adoc[] + +endif::env-github,rspecator-view[]