Ver Fonte

fix: 支持白名单域名反向代理

samien há 2 meses atrás
pai
commit
e1a7ccb13e
2 ficheiros alterados com 25 adições e 5 exclusões
  1. 9 3
      src/server/security.ts
  2. 16 2
      tests/server/security.test.ts

+ 9 - 3
src/server/security.ts

@@ -29,8 +29,9 @@ export function isAllowedHostHeader(value: string | undefined, config: ServerCon
   if (!value) return false
   const parsed = splitHostPort(value)
   if (!parsed) return false
-  if (parsed.port !== String(config.port)) return false
-  return isPrivateHost(parsed.hostname) || config.allowedHosts.has(parsed.hostname)
+  if (config.allowedHosts.has(parsed.hostname)) return true
+  if (!isPrivateHost(parsed.hostname)) return false
+  return parsed.port === String(config.port) || parsed.port === undefined || parsed.port === '80' || parsed.port === '443'
 }
 
 function canonicalOrigin(origin: string): string | null {
@@ -56,7 +57,12 @@ export function isExactOrigin(
 ): boolean {
   if (!origin || !host) return false
   const expected = expectedRequestOrigin(host, secure)
-  return expected !== null && canonicalOrigin(origin) === expected
+  const actual = canonicalOrigin(origin)
+  if (expected === null || actual === null) return false
+  if (actual === expected) return true
+  // A TLS-terminating reverse proxy forwards HTTP internally while the
+  // browser's same-origin request correctly carries an https Origin.
+  return !secure && actual === expectedRequestOrigin(host, true)
 }
 
 export function appendSetCookies(ctx: Koa.Context, values: readonly string[]): void {

+ 16 - 2
tests/server/security.test.ts

@@ -30,14 +30,28 @@ describe('server security boundary', () => {
     expect(isAllowedHostHeader('192.168.10.8:8800', value)).toBe(true)
     expect(isAllowedHostHeader('yaoyao.internal:8800', value)).toBe(true)
     expect(isAllowedHostHeader('127.0.0.1:9999', value)).toBe(false)
-    expect(isAllowedHostHeader('127.0.0.1', value)).toBe(false)
+    expect(isAllowedHostHeader('127.0.0.1', value)).toBe(true)
+    expect(isAllowedHostHeader('192.168.10.8:443', value)).toBe(true)
+    expect(isAllowedHostHeader('yaoyao.internal', value)).toBe(true)
     expect(isAllowedHostHeader('attacker.example:8800', value)).toBe(false)
     expect(isAllowedHostHeader('127.0.0.1@attacker.example', value)).toBe(false)
   })
 
+  it('accepts the configured production reverse-proxy domain on public ports', () => {
+    const value = config({ allowedHosts: new Set(['yaoyao-lc.samien.cn']) })
+    expect(isAllowedHostHeader('yaoyao-lc.samien.cn', value)).toBe(true)
+    expect(isAllowedHostHeader('yaoyao-lc.samien.cn:443', value)).toBe(true)
+    expect(isAllowedHostHeader('other.samien.cn', value)).toBe(false)
+  })
+
+  it('accepts an HTTPS browser Origin behind an HTTP TLS-terminating proxy', () => {
+    expect(isExactOrigin('https://yaoyao.internal', 'yaoyao.internal', false)).toBe(true)
+    expect(isExactOrigin('https://attacker.example', 'yaoyao.internal', false)).toBe(false)
+  })
+
   it('requires an exact scheme and host Origin', () => {
     expect(isExactOrigin('http://127.0.0.1:8800', '127.0.0.1:8800', false)).toBe(true)
-    expect(isExactOrigin('https://127.0.0.1:8800', '127.0.0.1:8800', false)).toBe(false)
+    expect(isExactOrigin('https://127.0.0.1:8800', '127.0.0.1:8800', false)).toBe(true)
     expect(isExactOrigin('http://127.0.0.1:8801', '127.0.0.1:8800', false)).toBe(false)
     expect(isExactOrigin('null', '127.0.0.1:8800', false)).toBe(false)
   })