VYPR
Moderate severityNVD Advisory· Published Jun 22, 2022· Updated Sep 17, 2024

directus - SSRF which leads to internal port scan

CVE-2022-23080

Description

In directus versions v9.0.0-beta.2 through 9.6.0 are vulnerable to server-side request forgery (SSRF) in the media upload functionality which allows a low privileged user to perform internal network port scans.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
directusnpm
>= 9.0.0-beta.2, < 9.7.09.7.0

Affected products

1

Patches

1
6da3f1ed5034

Add support for import ip deny list (#12025)

https://github.com/directus/directusRijk van ZantenMar 7, 2022via ghsa
3 files changed · +60 3
  • api/src/env.ts+3 0 modified
    @@ -77,6 +77,8 @@ const defaults: Record<string, any> = {
     	IP_TRUST_PROXY: true,
     	IP_CUSTOM_HEADER: false,
     
    +	IMPORT_IP_DENY_LIST: '0.0.0.0',
    +
     	SERVE_APP: true,
     
     	RELATIONAL_BATCH_SIZE: 25000,
    @@ -95,6 +97,7 @@ const typeMap: Record<string, string> = {
     	DB_PORT: 'number',
     
     	DB_EXCLUDE_TABLES: 'array',
    +	IMPORT_IP_DENY_LIST: 'array',
     };
     
     let env: Record<string, any> = {
    
  • api/src/services/files.ts+56 3 modified
    @@ -5,7 +5,9 @@ import { clone } from 'lodash';
     import { extension } from 'mime-types';
     import path from 'path';
     import sharp from 'sharp';
    -import url from 'url';
    +import url, { URL } from 'url';
    +import { promisify } from 'util';
    +import { lookup } from 'dns';
     import emitter from '../emitter';
     import env from '../env';
     import { ForbiddenException, ServiceUnavailableException } from '../exceptions';
    @@ -14,6 +16,10 @@ import storage from '../storage';
     import { AbstractServiceOptions, File, PrimaryKey, MutationOptions } from '../types';
     import { toArray } from '@directus/shared/utils';
     import { ItemsService } from './items';
    +import net from 'net';
    +import os from 'os';
    +
    +const lookupDNS = promisify(lookup);
     
     export class FilesService extends ItemsService {
     	constructor(options: AbstractServiceOptions) {
    @@ -161,15 +167,62 @@ export class FilesService extends ItemsService {
     			throw new ForbiddenException();
     		}
     
    +		let resolvedUrl;
    +
    +		try {
    +			resolvedUrl = new URL(importURL);
    +		} catch (err: any) {
    +			logger.warn(err, `Requested URL ${importURL} isn't a valid URL`);
    +			throw new ServiceUnavailableException(`Couldn't fetch file from url "${importURL}"`, {
    +				service: 'external-file',
    +			});
    +		}
    +
    +		let ip = resolvedUrl.hostname;
    +
    +		if (net.isIP(ip) === 0) {
    +			try {
    +				ip = (await lookupDNS(ip)).address;
    +			} catch (err: any) {
    +				logger.warn(err, `Couldn't lookup the DNS for url ${importURL}`);
    +				throw new ServiceUnavailableException(`Couldn't fetch file from url "${importURL}"`, {
    +					service: 'external-file',
    +				});
    +			}
    +		}
    +
    +		if (env.IMPORT_IP_DENY_LIST.includes('0.0.0.0')) {
    +			const networkInterfaces = os.networkInterfaces();
    +
    +			for (const networkInfo of Object.values(networkInterfaces)) {
    +				if (!networkInfo) continue;
    +
    +				for (const info of networkInfo) {
    +					if (info.address === ip) {
    +						logger.warn(`Requested URL ${importURL} resolves to localhost.`);
    +						throw new ServiceUnavailableException(`Couldn't fetch file from url "${importURL}"`, {
    +							service: 'external-file',
    +						});
    +					}
    +				}
    +			}
    +		}
    +
    +		if (env.IMPORT_IP_DENY_LIST.includes(ip)) {
    +			logger.warn(`Requested URL ${importURL} resolves to a denied IP address.`);
    +			throw new ServiceUnavailableException(`Couldn't fetch file from url "${importURL}"`, {
    +				service: 'external-file',
    +			});
    +		}
    +
     		let fileResponse: AxiosResponse<NodeJS.ReadableStream>;
     
     		try {
     			fileResponse = await axios.get<NodeJS.ReadableStream>(importURL, {
     				responseType: 'stream',
     			});
     		} catch (err: any) {
    -			logger.warn(`Couldn't fetch file from url "${importURL}"`);
    -			logger.warn(err);
    +			logger.warn(err, `Couldn't fetch file from url "${importURL}"`);
     			throw new ServiceUnavailableException(`Couldn't fetch file from url "${importURL}"`, {
     				service: 'external-file',
     			});
    
  • docs/configuration/config-options.md+1 0 modified
    @@ -280,6 +280,7 @@ All the `DB_POOL_` prefixed options are passed to [`tarn.js`](https://github.com
     | `IP_CUSTOM_HEADER`               | What custom request header to use for the IP address                                                                                                             | false                    |
     | `CONTENT_SECURITY_POLICY`        | Custom overrides for the Content-Security-Policy header. See [helmet's documentation](https://helmetjs.github.io) for more information.                          | --                       |
     | `ASSETS_CONTENT_SECURITY_POLICY` | Custom overrides for the Content-Security-Policy header for the /assets endpoint. See [helmet's documentation](https://helmetjs.github.io) for more information. | --                       |
    +| `IMPORT_IP_DENY_LIST`            | Deny importing files from these IP addresses. Use `0.0.0.0` for any local IP address                                                                             | `0.0.0.0`                |
     
     ::: tip Cookie Strictness
     
    

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

4

News mentions

0

No linked articles in our index yet.