VYPR
Moderate severityNVD Advisory· Published Sep 18, 2024· Updated Sep 18, 2024

SSRF Loopback IP filter bypass in directus

CVE-2024-46990

Description

Directus is a real-time API and App dashboard for managing SQL database content. When relying on blocking access to localhost using the default 0.0.0.0 filter a user may bypass this block by using other registered loopback devices (like 127.0.0.2 - 127.127.127.127). This issue has been addressed in release versions 10.13.3 and 11.1.0. Users are advised to upgrade. Users unable to upgrade may block this bypass by manually adding the 127.0.0.0/8 CIDR range which will block access to any 127.X.X.X ip instead of just 127.0.0.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
directusnpm
< 10.13.310.13.3
directusnpm
>= 11.0.0, < 11.1.011.1.0
@directus/apinpm
< 21.0.021.0.0
@directus/apinpm
>= 22.0.0, < 22.1.122.1.1

Affected products

1

Patches

4
769fa22797bf

Merge commit from fork

https://github.com/directus/directusRijk van ZantenAug 30, 2024via ghsa
2 files changed · +6 0
  • api/src/middleware/respond.ts+1 0 modified
    @@ -27,6 +27,7 @@ export const respond: RequestHandler = asyncHandler(async (req, res) => {
     
     	if (
     		(req.method.toLowerCase() === 'get' || req.originalUrl?.startsWith('/graphql')) &&
    +		req.originalUrl?.startsWith('/auth') === false &&
     		env['CACHE_ENABLED'] === true &&
     		cache &&
     		!req.sanitizedQuery.export &&
    
  • .changeset/orange-suns-fry.md+5 0 added
    @@ -0,0 +1,5 @@
    +---
    +'@directus/api': patch
    +---
    +
    +Fixed an issue that could cause the response cache to misbehave
    
4aace0bbe572

Merge commit from fork

https://github.com/directus/directusRijk van ZantenAug 30, 2024via ghsa
2 files changed · +6 0
  • api/src/middleware/respond.ts+1 0 modified
    @@ -27,6 +27,7 @@ export const respond: RequestHandler = asyncHandler(async (req, res) => {
     
     	if (
     		(req.method.toLowerCase() === 'get' || req.originalUrl?.startsWith('/graphql')) &&
    +		req.originalUrl?.startsWith('/auth') === false &&
     		env['CACHE_ENABLED'] === true &&
     		cache &&
     		!req.sanitizedQuery.export &&
    
  • .changeset/orange-suns-fry.md+5 0 added
    @@ -0,0 +1,5 @@
    +---
    +'@directus/api': patch
    +---
    +
    +Fixed an issue that could cause the response cache to misbehave
    
c1f3ccc68159

Merge commit from fork

https://github.com/directus/directusRijk van ZantenAug 30, 2024via ghsa
3 files changed · +34 2
  • api/src/request/is-denied-ip.test.ts+23 1 modified
    @@ -108,7 +108,7 @@ test(`Returns true if IP matches resolved local network interface address`, asyn
     				netmask: '255.0.0.0',
     				family: 'IPv4',
     				mac: '00:00:00:00:00:00',
    -				internal: true,
    +				internal: false,
     				cidr: '127.0.0.1/8',
     			},
     		],
    @@ -118,3 +118,25 @@ test(`Returns true if IP matches resolved local network interface address`, asyn
     
     	expect(result).toBe(true);
     });
    +
    +test(`Returns true if IP matches resolved to local loopback devices`, async () => {
    +	vi.mocked(useEnv).mockReturnValue({ IMPORT_IP_DENY_LIST: ['0.0.0.0'] });
    +
    +	vi.mocked(os.networkInterfaces).mockReturnValue({
    +		fa0: undefined,
    +		lo0: [
    +			{
    +				address: '127.0.0.1',
    +				netmask: '255.0.0.0',
    +				family: 'IPv4',
    +				mac: '00:00:00:00:00:00',
    +				internal: true,
    +				cidr: '127.0.0.1/8',
    +			},
    +		],
    +	});
    +
    +	expect(isDeniedIp('127.0.0.1')).toBe(true);
    +	expect(isDeniedIp('127.8.16.32')).toBe(true);
    +	expect(isDeniedIp('127.127.127.127')).toBe(true);
    +});
    
  • api/src/request/is-denied-ip.ts+6 1 modified
    @@ -1,4 +1,5 @@
     import { useEnv } from '@directus/env';
    +import { matches } from 'ip-matching';
     import os from 'node:os';
     import { useLogger } from '../logger/index.js';
     import { ipInNetworks } from '../utils/ip-in-networks.js';
    @@ -29,7 +30,11 @@ export function isDeniedIp(ip: string): boolean {
     			if (!networkInfo) continue;
     
     			for (const info of networkInfo) {
    -				if (info.address === ip) return true;
    +				if (info.internal && info.cidr) {
    +					if (matches(ip, info.cidr)) return true;
    +				} else if (info.address === ip) {
    +					return true;
    +				}
     			}
     		}
     	}
    
  • .changeset/silly-months-protect.md+5 0 added
    @@ -0,0 +1,5 @@
    +---
    +'@directus/env': patch
    +---
    +
    +Expanded `0.0.0.0` matching of loopback ranges
    
8cbf943b65fd

Merge commit from fork

https://github.com/directus/directusBrainslugAug 30, 2024via ghsa
3 files changed · +34 2
  • api/src/request/is-denied-ip.test.ts+23 1 modified
    @@ -108,7 +108,7 @@ test(`Returns true if IP matches resolved local network interface address`, asyn
     				netmask: '255.0.0.0',
     				family: 'IPv4',
     				mac: '00:00:00:00:00:00',
    -				internal: true,
    +				internal: false,
     				cidr: '127.0.0.1/8',
     			},
     		],
    @@ -118,3 +118,25 @@ test(`Returns true if IP matches resolved local network interface address`, asyn
     
     	expect(result).toBe(true);
     });
    +
    +test(`Returns true if IP matches resolved to local loopback devices`, async () => {
    +	vi.mocked(useEnv).mockReturnValue({ IMPORT_IP_DENY_LIST: ['0.0.0.0'] });
    +
    +	vi.mocked(os.networkInterfaces).mockReturnValue({
    +		fa0: undefined,
    +		lo0: [
    +			{
    +				address: '127.0.0.1',
    +				netmask: '255.0.0.0',
    +				family: 'IPv4',
    +				mac: '00:00:00:00:00:00',
    +				internal: true,
    +				cidr: '127.0.0.1/8',
    +			},
    +		],
    +	});
    +
    +	expect(isDeniedIp('127.0.0.1')).toBe(true);
    +	expect(isDeniedIp('127.8.16.32')).toBe(true);
    +	expect(isDeniedIp('127.127.127.127')).toBe(true);
    +});
    
  • api/src/request/is-denied-ip.ts+6 1 modified
    @@ -1,5 +1,6 @@
     import { useEnv } from '@directus/env';
     import os from 'node:os';
    +import { matches } from 'ip-matching';
     import { useLogger } from '../logger/index.js';
     import { ipInNetworks } from '../utils/ip-in-networks.js';
     
    @@ -29,7 +30,11 @@ export function isDeniedIp(ip: string): boolean {
     			if (!networkInfo) continue;
     
     			for (const info of networkInfo) {
    -				if (info.address === ip) return true;
    +				if (info.internal && info.cidr) {
    +					if (matches(ip, info.cidr)) return true;
    +				} else if (info.address === ip) { 
    +					return true;
    +				}
     			}
     		}
     	}
    
  • .changeset/silly-months-protect.md+5 0 added
    @@ -0,0 +1,5 @@
    +---
    +'@directus/env': patch
    +---
    +
    +Expanded `0.0.0.0` matching of loopback ranges
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.