mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-21 13:55:33 +02:00
fix(security): escape dockerImage in database service remote docker pull
The deploy functions for postgres/mysql/mariadb/mongo/redis/libsql interpolated
the user-settable dockerImage field unquoted into 'docker pull ${dockerImage}'
executed via execAsyncRemote (SSH) on the remote server path. Now passed through
shell-quote. The local path already used pullImage() (execFile-based) and is
unaffected.
Closes GHSA-6jrh-8qmg-jj3p
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { existsSync, rmSync } from "node:fs";
|
||||
import { parse, quote } from "shell-quote";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
// The six database deploy functions (postgres/mysql/mariadb/mongo/redis/libsql)
|
||||
// build `docker pull ${quote([dockerImage])}` for the remote (execAsyncRemote)
|
||||
// path. `docker` is replaced by `:` so only the injection surface is exercised.
|
||||
const MARK = `/tmp/dokploy_dbimg_pwned_${process.pid}`;
|
||||
|
||||
const PAYLOADS = [
|
||||
"$(touch %MARK%)",
|
||||
"`touch %MARK%`",
|
||||
"redis:7; touch %MARK%",
|
||||
"redis:7 && touch %MARK%",
|
||||
"redis:7 | touch %MARK%",
|
||||
];
|
||||
|
||||
describe("database service dockerImage command injection", () => {
|
||||
it("does not execute injected commands from dockerImage", () => {
|
||||
for (const template of PAYLOADS) {
|
||||
if (existsSync(MARK)) rmSync(MARK);
|
||||
const dockerImage = template.replace("%MARK%", MARK);
|
||||
const command = `: pull ${quote([dockerImage])}`;
|
||||
try {
|
||||
execSync(command, { shell: "/bin/sh", stdio: "ignore" });
|
||||
} catch {}
|
||||
expect(existsSync(MARK)).toBe(false);
|
||||
}
|
||||
if (existsSync(MARK)) rmSync(MARK);
|
||||
});
|
||||
|
||||
it("keeps a legitimate image tag intact", () => {
|
||||
expect(parse(quote(["postgres:16.4-alpine"]))).toEqual([
|
||||
"postgres:16.4-alpine",
|
||||
]);
|
||||
expect(parse(quote(["ghcr.io/org/db:latest"]))).toEqual([
|
||||
"ghcr.io/org/db:latest",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
import { quote } from "shell-quote";
|
||||
import type { z } from "zod";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
|
||||
@@ -140,7 +141,7 @@ export const deployLibsql = async (
|
||||
if (libsql.serverId) {
|
||||
await execAsyncRemote(
|
||||
libsql.serverId,
|
||||
`docker pull ${libsql.dockerImage}`,
|
||||
`docker pull ${quote([libsql.dockerImage])}`,
|
||||
onData,
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
import { quote } from "shell-quote";
|
||||
import type { z } from "zod";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
|
||||
@@ -145,7 +146,7 @@ export const deployMariadb = async (
|
||||
if (mariadb.serverId) {
|
||||
await execAsyncRemote(
|
||||
mariadb.serverId,
|
||||
`docker pull ${mariadb.dockerImage}`,
|
||||
`docker pull ${quote([mariadb.dockerImage])}`,
|
||||
onData,
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
import { quote } from "shell-quote";
|
||||
import type { z } from "zod";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
|
||||
@@ -160,7 +161,7 @@ export const deployMongo = async (
|
||||
if (mongo.serverId) {
|
||||
await execAsyncRemote(
|
||||
mongo.serverId,
|
||||
`docker pull ${mongo.dockerImage}`,
|
||||
`docker pull ${quote([mongo.dockerImage])}`,
|
||||
onData,
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
import { quote } from "shell-quote";
|
||||
import type { z } from "zod";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
|
||||
@@ -143,7 +144,7 @@ export const deployMySql = async (
|
||||
if (mysql.serverId) {
|
||||
await execAsyncRemote(
|
||||
mysql.serverId,
|
||||
`docker pull ${mysql.dockerImage}`,
|
||||
`docker pull ${quote([mysql.dockerImage])}`,
|
||||
onData,
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
import { quote } from "shell-quote";
|
||||
import type { z } from "zod";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
|
||||
@@ -155,7 +156,7 @@ export const deployPostgres = async (
|
||||
if (postgres.serverId) {
|
||||
await execAsyncRemote(
|
||||
postgres.serverId,
|
||||
`docker pull ${postgres.dockerImage}`,
|
||||
`docker pull ${quote([postgres.dockerImage])}`,
|
||||
onData,
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { pullImage } from "@dokploy/server/utils/docker/utils";
|
||||
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { quote } from "shell-quote";
|
||||
import type { z } from "zod";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
|
||||
@@ -110,7 +111,7 @@ export const deployRedis = async (
|
||||
if (redis.serverId) {
|
||||
await execAsyncRemote(
|
||||
redis.serverId,
|
||||
`docker pull ${redis.dockerImage}`,
|
||||
`docker pull ${quote([redis.dockerImage])}`,
|
||||
onData,
|
||||
);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user