CVE-2024-32869 (published 2024-04-23T21:15Z)

Hono is a Web application framework that provides support for any JavaScript runtime. Prior to version 4.2.7, when using serveStatic with deno, it is possible to traverse the directory where `main.ts` is located. This can result in retrieval of unexpected files. Version 4.2.7 contains a patch for the issue.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/honojs/hono
commit_details: repo_url: https://github.com/honojs/hono
author: Yusuke Wada
author_date: 2024-04-23 17:14:16+09:00
msg: Merge pull request from GHSA-3mpf-rcc7-5347
repository: repo_url: https://github.com/honojs/hono
repo_name: honojs/hono
description: Web framework built on Web Standards
repo_language: TypeScript
owner: honojs
file_changes: filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -30,9 +30,8 @@ export const serveStatic = <E extends Env = Env>(
       await next()
       return
     }
-    const url = new URL(c.req.url)
 
-    let filename = options.path ?? decodeURI(url.pathname)
+    let filename = options.path ?? decodeURI(c.req.path)
     filename = options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename
     const root = options.root
 

programming_language: TypeScript
filename: middleware.test.tsx
change_type: ModificationType.MODIFY
diff: @@ -117,6 +117,13 @@ Deno.test('Serve Static middleware', async () => {
   assertEquals(res.status, 200)
   assertEquals(await res.text(), 'Deno!!')
   assertSpyCalls(onNotFound, 1)
+
+  res = await app.fetch({
+    method: 'GET',
+    url: 'http://localhost/static/%2e%2e/static/plain.txt',
+  } as Request)
+  assertEquals(res.status, 404)
+  assertEquals(await res.text(), '404 Not Found')
 })
 
 Deno.test('JWT Authentication middleware', async () => {

programming_language: TypeScript
filename: index.test.ts
change_type: ModificationType.ADD
diff: @@ -0,0 +1,59 @@
+import { createMiddleware } from '../../helper'
+import { Hono } from '../../hono'
+import { serveStatic as baseServeStatic } from '.'
+
+describe('Serve Static Middleware', () => {
+  const app = new Hono()
+
+  const serveStatic = createMiddleware(async (c, next) => {
+    const mw = baseServeStatic({
+      getContent: (path) => {
+        if (path.endsWith('not-found.txt')) {
+          return undefined
+        }
+        return `Hello in ${path}`
+      },
+      pathResolve: (path) => {
+        return `./${path}`
+      },
+    })
+    return await mw(c, next)
+  })
+
+  app.get('/static/*', serveStatic)
+
+  it('Should return 200 response - /static/hello.html', async () => {
+    const res = await app.request('/static/hello.html')
+    expect(res.status).toBe(200)
+    expect(res.headers.get('Content-Type')).toMatch(/^text\/html/)
+    expect(await res.text()).toBe('Hello in ./static/hello.html')
+  })
+
+  it('Should return 200 response - /static/sub', async () => {
+    const res = await app.request('/static/sub')
+    expect(res.status).toBe(200)
+    expect(res.headers.get('Content-Type')).toMatch(/^text\/html/)
+    expect(await res.text()).toBe('Hello in ./static/sub/index.html')
+  })
+
+  it('Should decode URI strings - /static/%E7%82%8E.txt', async () => {
+    const res = await app.request('/static/%E7%82%8E.txt')
+    expect(res.status).toBe(200)
+    expect(await res.text()).toBe('Hello in ./static/炎.txt')
+  })
+
+  it('Should return 404 response - /static/not-found', async () => {
+    const res = await app.request('/static/not-found.txt')
+    expect(res.status).toBe(404)
+    expect(await res.text()).toBe('404 Not Found')
+  })
+
+  it('Should not allow a directory traversal - /static/%2e%2e/static/hello.html', async () => {
+    const res = await app.fetch({
+      method: 'GET',
+      url: 'http://localhost/static/%2e%2e/static/hello.html',
+    } as Request)
+    expect(res.status).toBe(404)
+    expect(await res.text()).toBe('404 Not Found')
+  })
+})

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -30,9 +30,8 @@ export const serveStatic = <E extends Env = Env>(
       await next()
       return
     }
-    const url = new URL(c.req.url)
 
-    let filename = options.path ?? decodeURI(url.pathname)
+    let filename = options.path ?? decodeURI(c.req.path)
     filename = options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename
     const root = options.root
 

programming_language: TypeScript