CVE-2024-23340 (published 2024-01-22T23:15Z)

@hono/node-server is an adapter that allows users to run Hono applications on Node.js. Since v1.3.0, @hono/node-server has used its own Request object with `url` behavior that is unexpected. In the standard API, if the URL contains `..`, here called "double dots", the URL string returned by Request will be in the resolved path. However, the `url` in @hono/node-server\'s Request as does not resolve double dots, so `http://localhost/static/.. /foo.txt` is returned. This causes vulnerabilities when using `serveStatic`. Modern web browsers and a latest `curl` command resolve double dots on the client side, so this issue doesn\'t affect those using either of those tools. However, problems may occur if accessed by a client that does not resolve them. Version 1.4.1 includes the change to fix this issue. As a workaround, don\'t use `serveStatic`.\n\n

CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/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/honojs/node-server
commit_details: repo_url: https://github.com/honojs/node-server
author: Yusuke Wada
author_date: 2024-01-21 16:54:03+09:00
msg: fix(request): handle "double dots" in URL (#124)
repository: repo_url: https://github.com/honojs/node-server
repo_name: honojs/node-server
description: Node.js Server for Hono
repo_language: TypeScript
owner: honojs
file_changes: filename: request.ts
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 
 import type { IncomingMessage } from 'node:http'
 import type { Http2ServerRequest } from 'node:http2'
+import { resolve } from 'node:path'
 import { Readable } from 'node:stream'
 
 const newRequestFromIncoming = (
@@ -41,7 +42,13 @@ const requestPrototype: Record<string | symbol, any> = {
   },
 
   get url() {
-    return `http://${this[incomingKey].headers.host}${this[incomingKey].url}`
+    let path = this[incomingKey]['path']
+    if (!path) {
+      const originalPath = this[incomingKey].url
+      path = /\.\./.test(originalPath) ? resolve(originalPath) : originalPath
+      this[incomingKey]['path'] = path
+    }
+    return `http://${this[incomingKey].headers.host}${path}`
   },
 
   [getRequestCache]() {

programming_language: TypeScript
filename: secret.txt
change_type: ModificationType.ADD
diff: @@ -0,0 +1 @@
+secret
\ No newline at end of file

programming_language: SQL
filename: request.test.ts
change_type: ModificationType.MODIFY
diff: @@ -17,4 +17,17 @@ describe('Request', () => {
     expect(req.url).toBe('http://localhost/')
     expect(req.headers.get('host')).toBe('localhost')
   })
+
+  it('Should resolve double dots in URL', async () => {
+    const req = newRequest({
+      headers: {
+        host: 'localhost',
+      },
+      url: '/static/../foo.txt',
+    } as IncomingMessage)
+    expect(req).toBeInstanceOf(global.Request)
+    expect(req.url).toBe('http://localhost/foo.txt')
+    // Check if cached value is returned correctly
+    expect(req.url).toBe('http://localhost/foo.txt')
+  })
 })

programming_language: TypeScript
filename: serve-static.test.ts
change_type: ModificationType.MODIFY
diff: @@ -129,4 +129,9 @@ describe('Serve Static Middleware', () => {
       './not-found/on-not-found/foo.txt is not found, request to /on-not-found/foo.txt'
     )
   })
+
+  it('Should handle double dots in URL', async () => {
+    const res = await request(server).get('/static/../secret.txt')
+    expect(res.status).toBe(404)
+  })
 })

programming_language: CoffeeScript