diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000..9def09c4 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2024-05-13 - Command Injection in Docker Logs via URL Parameters +**Vulnerability:** Command injection and potential flag injection due to using `execSync` with unsanitized URL parameters (`tail` and `id`) string-concatenated in `docker logs`. +**Learning:** Even internal toolings or APIs querying basic container information can be vectors for critical command injections if parameters derived from `url.searchParams` are concatenated into strings executed by the shell. +**Prevention:** Always use `execFileSync`, `execFile`, or `spawn` with an array of arguments, and ensure input parameters do not start with a hyphen (`-`) to mitigate flag injection vulnerabilities in CLI tools like docker. Also, catch blocks should sanitize error outputs rather than throwing raw internal stack traces directly. diff --git a/src/pages/api/docker-logs.ts b/src/pages/api/docker-logs.ts index cb72e09e..aa841b5d 100644 --- a/src/pages/api/docker-logs.ts +++ b/src/pages/api/docker-logs.ts @@ -1,5 +1,5 @@ import type { APIRoute } from 'astro'; -import { execSync } from 'child_process'; +import { execFileSync } from 'child_process'; export const GET: APIRoute = async ({ url }) => { try { @@ -21,18 +21,25 @@ export const GET: APIRoute = async ({ url }) => { }); } + // Validation des entrées pour éviter l'injection de flags + if (containerId.startsWith('-') || tail.startsWith('-')) { + return new Response(JSON.stringify({ error: "Paramètres invalides" }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); + } + // Commande Docker pour récupérer les logs - const command = `docker logs --tail ${tail} ${containerId}`; let logs = []; try { - const output = execSync(command, { stdio: ['pipe', 'pipe', 'pipe'] }).toString(); + const output = execFileSync('docker', ['logs', '--tail', tail, containerId], { stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf8' }); logs = output.trim().split('\n'); } catch (err: any) { // Certains logs sortent sur stderr, checkons stderr si stdout est vide ou si erreur if (err.stderr) { logs = err.stderr.toString().trim().split('\n'); } else { - throw err; + throw new Error("Erreur d'exécution de la commande"); } } @@ -41,7 +48,8 @@ export const GET: APIRoute = async ({ url }) => { headers: { 'Content-Type': 'application/json' } }); } catch (error: any) { - return new Response(JSON.stringify({ error: "Logs indisponibles: " + error.message }), { + // Ne pas fuiter les stack traces internes + return new Response(JSON.stringify({ error: "Logs indisponibles" }), { status: 500, headers: { 'Content-Type': 'application/json' } });