UNPKG

@aikidosec/firewall

Version:

Zen by Aikido is an embedded Web Application Firewall that autonomously protects Node.js apps against common and critical attacks

175 lines (174 loc) 4.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.containsShellSyntax = containsShellSyntax; const escapeStringRegexp_1 = require("../../helpers/escapeStringRegexp"); const dangerousChars = [ "#", "!", '"', "$", "&", "'", "(", ")", "*", ";", "<", "=", ">", "?", "[", "\\", "]", "^", "`", "{", "|", "}", " ", "\n", "\t", "~", ]; const commands = [ "sleep", "shutdown", "reboot", "poweroff", "halt", "ifconfig", "chmod", "chown", "ping", "ssh", "scp", "curl", "wget", "telnet", "kill", "killall", "rm", "mv", "cp", "touch", "echo", "cat", "head", "tail", "grep", "find", "awk", "sed", "sort", "uniq", "wc", "ls", "env", "ps", "who", "whoami", "id", "w", "df", "du", "pwd", "uname", "hostname", "netstat", "passwd", "arch", "printenv", "logname", "pstree", "hostnamectl", "set", "lsattr", "killall5", "dmesg", "history", "free", "uptime", "finger", "top", "shopt", // Colon is a null command // it might occur in URLs that are passed as arguments to a binary // we should flag if it's surrounded by separators ":", ]; const pathPrefixes = [ "/bin/", "/sbin/", "/usr/bin/", "/usr/sbin/", "/usr/local/bin/", "/usr/local/sbin/", ]; const separators = [" ", "\t", "\n", ";", "&", "|", "(", ")", "<", ">"]; // "killall" should be matched before "kill" function byLength(a, b) { return b.length - a.length; } const commandsRegex = new RegExp(`([/.]*(${pathPrefixes.map(escapeStringRegexp_1.escapeStringRegexp).join("|")})?(${commands.slice().sort(byLength).join("|")}))`, "gi"); function matchAll(str, regex) { // Reset the regex so that the next call to `exec` starts from the beginning // As the regex is global, it will remember the last index regex.lastIndex = 0; const matches = []; let match; while ((match = regex.exec(str)) !== null) { matches.push(match); } return matches; } function containsShellSyntax(command, userInput) { const allWhitespace = /^\s*$/.test(userInput); if (allWhitespace) { return false; } if (dangerousChars.find((c) => userInput.includes(c))) { return true; } // The command is the same as the user input // Rare case, but it's possible // e.g. command is `shutdown` and user input is `shutdown` // (`shutdown -h now` will be caught by the dangerous chars as it contains a space) if (command === userInput) { // Reset the regex so that the next call to `exec` starts from the beginning // As the regex is global, it will remember the last index commandsRegex.lastIndex = 0; const match = commandsRegex.exec(command); return match ? match.index === 0 && match[0].length === command.length : false; } // Check if the command contains a commonly used command for (const match of matchAll(command, commandsRegex)) { // We found a command like `rm` or `/sbin/shutdown` in the command // Check if the command is the same as the user input // If it's not the same, continue searching if (userInput !== match[0]) { continue; } // Otherwise, we'll check if the command is surrounded by separators // These separators are used to separate commands and arguments // e.g. `rm<space>-rf` // e.g. `ls<newline>whoami` // e.g. `echo<tab>hello` const charBefore = command[match.index - 1]; const charAfter = command[match.index + match[0].length]; // e.g. `<separator>rm<separator>` if (separators.includes(charBefore) && separators.includes(charAfter)) { return true; } // e.g. `<separator>rm` if (separators.includes(charBefore) && charAfter === undefined) { return true; } // e.g. `rm<separator>` if (charBefore === undefined && separators.includes(charAfter)) { return true; } } return false; }