CVE-2021-32643 (published 2021-05-27T18:15Z)

Http4s is a Scala interface for HTTP services. `StaticFile.fromUrl` can leak the presence of a directory on a server when the `URL` scheme is not `file://`, and the URL points to a fetchable resource under its scheme and authority. The function returns `F[None]`, indicating no resource, if `url.getFile` is a directory, without first checking the scheme or authority of the URL. If a URL connection to the scheme and URL would return a stream, and the path in the URL exists as a directory on the server, the presence of the directory on the server could be inferred from the 404 response. The contents and other metadata about the directory are not exposed. This affects http4s versions: 0.21.7 through 0.21.23, 0.22.0-M1 through 0.22.0-M8, 0.23.0-M1, and 1.0.0-M1 through 1.0.0-M22. The [patch](https://github.com/http4s/http4s/commit/52e1890665410b4385e37b96bc49c5e3c708e4e9) is available in the following versions: v0.21.24, v0.22.0-M9, v0.23.0-M2, v1.0.0-M23. As a workaround users can avoid calling `StaticFile.fromUrl` with non-file URLs.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:P/I:N/A:N)
CVSS v3 base score: 5.8, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/http4s/http4s
commit_details: repo_url: https://github.com/http4s/http4s
author: Ross A. Baker
author_date: 2021-05-26 00:38:03-04:00
msg: Fix directory leak in StaticFile.fromUrl
repository: repo_url: https://github.com/http4s/http4s
repo_name: http4s/http4s
description: A minimal, idiomatic Scala interface for HTTP
repo_language: Scala
owner: http4s
file_changes: filename: StaticFile.scala
change_type: ModificationType.MODIFY
diff: @@ -80,7 +80,7 @@ object StaticFile {
     val fileUrl = url.getFile()
     val file = new File(fileUrl)
     OptionT.apply(F.defer {
-      if (file.isDirectory())
+      if (url.getProtocol === "file" && file.isDirectory)
         F.pure(None)
       else {
         val urlConn = url.openConnection

programming_language: Scala
filename: StaticFileSuite.scala
change_type: ModificationType.MODIFY
diff: @@ -277,7 +277,7 @@ class StaticFileSuite extends Http4sSuite {
     len.assertEquals(Some(24005L))
   }
 
-  test("return none from a URL that is a directory") {
+  test("return none from a file URL that is a directory") {
     // val url = getClass.getResource("/foo")
     StaticFile
       .fromURL[IO](getClass.getResource("/foo"), testBlocker)
@@ -285,6 +285,20 @@ class StaticFileSuite extends Http4sSuite {
       .assertEquals(None)
   }
 
+  test("not return none from an HTTP URL whose path is a directory") {
+    // We need a universal directory that also exists as a resource on
+    // a server.  Creating a temp directory would be better, but then
+    // we need an HTTP server that responds to a wildcard path.
+    //
+    // Or we can be lazy and just use `/`.
+    assume(new File("/").isDirectory, "/ is not a directory")
+    StaticFile
+      .fromURL[IO](new URL("https://github.com//"), testBlocker)
+      .value
+      .map(_.fold(Status.NotFound)(_.status))
+      .assertEquals(Status.Ok)
+  }
+
   test("return none from a URL that points to a resource that does not exist") {
     StaticFile
       .fromURL[IO](new URL("https://github.com/http4s/http4s/fooz"), testBlocker)

programming_language: Scala